diff --git a/.gitignore b/.gitignore
index 7168e57..6a562d9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ screenshots/
.env.*
!.env.example
__pycache__/
+/archives/web/snapshots/
+/ressources-pack/*.zip
diff --git a/AGENTS.md b/AGENTS.md
index 8c15f6c..5ce5249 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -12,7 +12,7 @@ La vision est dans `docs/vision.md` ; elle décrit aussi des fonctionnalités fu
- Garder les identifiants `sanctuary:*` stables et documenter toute évolution de la génération avec graine et version.
- Ajouter les libellés FR/EN des nouvelles interfaces. Vérifier les dépendances pour la version Minecraft exacte ; aucune compatibilité supposée à partir du nom d'un mod.
- Lancer `./gradlew check build` pour livrer du code, et `./gradlew assemblePack` si la distribution change. Ajouter seulement les tests utiles au comportement touché.
-- Pour chaque nouvelle livraison du mod, incrémenter `mod_version` et `pack_version` dans `gradle.properties`, synchroniser `packwiz/pack.toml` et documenter le changement. Une simple modification documentaire n'incrémente pas les binaires.
+- Pour chaque nouvelle livraison du mod, incrémenter le compteur `beta.xxx` (départ `beta.001`) dans `mod_version` et `pack_version` de `gradle.properties`, synchroniser `packwiz/pack.toml` et documenter le changement. Le tag reprend cette version exacte, sans préfixe `v` ; voir `docs/versioning.md`. Une simple modification documentaire n'incrémente pas les binaires.
- Le pack Beta suit un canal packwiz stable et une seule instance Prism. Pour une mise à jour demandée, suivre `docs/packwiz.md` : publier un artefact vérifié et immuable, avancer le canal, puis synchroniser l'instance existante en conservant ses sauvegardes et réglages.
- Ne pas versionner de secrets, mondes, JAR générés ou dépendances téléchargées. Le wrapper Gradle fait exception.
- Ne pas déployer dans une installation de jeu ou un serveur personnel sans demande correspondante. Les serveurs de test restent dans les dossiers de développement ignorés.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09ceeba..2d2317a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,764 @@
# Changelog
+## beta.060 — objets et blocs sur les mobs
+
+- Ajoute un emplacement de tête indépendant de l’armure native des mobs et
+ les gestes Maj + clic droit pour équiper ou récupérer, clic droit pour utiliser.
+- Réutilise les sessions natives des blocs portés : stockage public, cuisson,
+ transformations, sons, état visible et source redstone mobile.
+- Conserve l’IA, suit les parties animées des modèles et prend en compte les
+ bébés, la lumière dynamique et les familiers.
+- Sauvegarde les objets équipés, restitue les contenus ordinaires au retrait
+ ou à la mort et conserve les contenus Shulker dans leur exemplaire.
+- Vérifie en client natif les gestes, lampes, menus, sauvegarde/rechargement,
+ mort, four, distributeur, TNT et modèles ; build et archives locales validés.
+- [Contrat et vérifications](docs/mob-head-blocks-beta060.md).
+
+## beta.059 — bannières et cartes au trésor
+
+- Enregistre les bannières par clic droit, avec nom et couleur ; importe aussi
+ les bannières et points d'intérêt d'une carte Minecraft remplie, dans les deux mains.
+- Révèle un trésor après utilisation de sa carte ; inspecte le plan existant
+ en mode opérateur sans transmettre ses positions aux joueurs non autorisés.
+- Adapte les trois tables aériennes au format natif 26.3 (`modifier` / `type`),
+ afin que les quantités déjà déclarées soient effectivement appliquées.
+- Ajoute les cartes des îlots miniers aux tables de butin aériennes existantes,
+ sans remplir les coffres déjà ouverts ou déplacer les trésors.
+- Affiche les sprites de carte vanilla, les noms et les infobulles ; conserve
+ les plafonds, le brouillard personnel et l'affichage simplifié de beta.058.
+- Vérifie les interactions natives, le butin réel, le filtrage opérateur,
+ la persistance et les deux archives locales ; aucun déploiement personnel.
+- [Contrat et vérifications](docs/atlas-markers-beta059.md).
+
+## beta.058 — altitude de la carte
+
+- Ajoute les boutons ↑ / ↓ et les plafonds 0, 140, 280, 420, 560 et 640,
+ sans déplacer le cadrage ni changer le zoom.
+- Enregistre les coupes par habitant avec le brouillard d'exploration existant,
+ conserve les anciennes données de surface et borne les lectures aux chunks chargés.
+- Isole les paquets et les textures de chaque couche ; conserve la grille et
+ le rendu Demeure mis en cache, désormais désactivés par défaut.
+- Retire le cadre et les zones assombries de la carte ; conserve la place du nord.
+- [Contrat et vérifications](docs/atlas-altitude-beta058.md).
+
+## beta.057 — météo quotidienne et nouvelles ambiances
+
+- Ajoute les modes météo Real Time et Vanilla, indépendants du mode solaire,
+ avec commandes opérateur et consultation publique.
+- Conserve un profil quotidien lié au fuseau du monde : beau temps, ciel gris,
+ brouillard, averses alternées, pluie continue ou orage continu.
+- Synchronise le gris et le brouillard dans les couches d'environnement natives ;
+ conserve les précipitations, transitions et effets météo Minecraft.
+- Enregistre le profil quotidien avant application ; les reconnexions et
+ rechargements ne relancent pas le tirage. Proportions configurables.
+- Prévoit la Weather TNT dans un prochain ticket.
+- [Contrat et vérifications](docs/daily-weather-beta057.md).
+
+## beta.056 — étoiles dispersées et saturation
+
+- Répartit les étoiles principales et bonus sur toute la sphère céleste,
+ sans amas imposé autour de chaque accomplissement.
+- Conserve les identités et les liaisons ; les constellations existantes
+ suivent une nouvelle disposition, stable lors des ajouts et des reconnexions.
+- Fixe la saturation par défaut de Vanilla Light à 142 %, avec conservation
+ des valeurs déjà enregistrées et aide française/anglaise actualisée.
+- [Contrat et vérifications](docs/sky-spacing-beta056.md).
+
+## beta.055 — Force et portage de joueurs
+
+- Réduit le poids ressenti des joueurs d'une pile selon le niveau de Force du
+ porteur : charge divisée par deux avec Force I, par trois avec Force II.
+- Recalcule la pénalité lors des changements et à l'expiration de l'effet ;
+ conserve le cumul avec Vitesse, le poids des adultes et l'exemption des bébés.
+- Vérifie les montures natives, la pile, les effets du passager, le démontage
+ et la synchronisation de la vitesse avec le client.
+- [Contrat et vérifications](docs/strength-carry-beta055.md).
+
+## beta.054 — duels, mises et niveaux des familiers
+
+- Ajoute les profils de combat des 88 familiers, leur santé individuelle,
+ le K.-O., les ordres compacts sur H et les techniques sur G.
+- Permet un duel accepté entre deux joueurs, avec mise facultative d'une pile
+ chacun, double validation, résultat au K.-O. ou à l'abandon et bilan du combat.
+- Conserve les mises dans un journal avec reçus natifs : gains différés si
+ l'inventaire est plein, restitution après interruption, composants conservés.
+- Isole les attaques du duel, exclut les apprentissages et invalide les
+ projectiles après sa fin. Préserve les blessures et recharges réelles.
+- Ajoute le stockage et le retrait exacts d'XP dans l'individu, sans plafond
+ de niveau : +2,5 % de vie maximale et +1,25 % de dégâts par niveau, rareté
+ croissante. Interdit les transferts pendant une invitation ou un combat.
+- Préserve l'XP par journal et reçu natif, avec migration additive des anciens
+ individus ; une charge différente ne soigne pas le familier.
+- Tests natifs du combat et des transactions FR/EN ; la validation et
+ la finition de certaines variantes restent à poursuivre.
+- [Contrat, migration et vérifications](docs/familiar-combat-beta054.md).
+
+## beta.053 — brume progressive, saturation et accueil
+
+- Fait progresser la brume de Vanilla Light dès le point de vue, en conservant les brumes spéciales et les captures panoramiques techniques natives.
+- Ajoute un curseur de saturation de 0 à 200 % dans Options → Shaders, avec application immédiate, préférence locale et libellés FR/EN.
+- Aligne à gauche les trois choix de familier de Hello World avec les autres champs.
+- Build, parcours client natif FR/EN et archives vérifiés ; aucun changement de sauvegarde ou de génération.
+- [Contrat et vérifications](docs/progressive-fog-beta053.md).
+
+## beta.052 — nom du familier de départ
+
+- Ajoute un champ de nom facultatif sous les œufs de Hello World, avec
+ disposition compacte pour les petites fenêtres et libellés FR/EN.
+- Enregistre le nom avec le choix côté serveur et l'applique à l'œuf natif,
+ au compagnon et à la fiche habitant ; conserve les renommages ultérieurs.
+- Migre le registre des offres vers le schéma 2 avec sauvegarde du fichier
+ original, sans modifier les anciens choix ni remettre de familiers.
+- [Contrat, migration et vérifications](docs/starter-name-beta052.md).
+
+## beta.051 — visée du distributeur porté
+
+- Oriente les treize munitions projetées selon le regard du porteur au moment
+ du lancement, en conservant les paramètres de tir natifs.
+- Place l'origine du tir sur la tête réelle et attribue le projectile au
+ porteur pour éviter un impact immédiat contre lui lors d'un tir vers le bas.
+- Préserve les distributeurs posés, les droppers et les objets non projectiles.
+- [Contrat et vérifications](docs/head-dispenser-beta051.md).
+
+## beta.050 — bateaux compacts et redstone portée
+
+- Ajoute les formats 1 × 2 et 2 × 1 dans les onze bois : quatre passagers,
+ places principales d'abord, recettes correspondantes et collection Bateaux.
+- Corrige les clics sur les cases des bibliothèques sculptées et étagères,
+ y compris l'insertion de livres enchantés avec leurs enchantements intacts.
+- Affiche le contenu des étagères et le livre du pupitre ; ouvre celui-ci
+ à main vide et vérifie les échanges de fleurs des pots via le curseur.
+- Affiche les deux moitiés de tous les lits, y compris celui de paille, et
+ désactive entièrement le sommeil dans les lits portés.
+- Pose boutons et leviers sur la couronne, dégagés de la couche du skin.
+- Alimente les circuits réels proches avec torche de redstone, levier ou
+ bouton portés. Le signal suit la tête et disparaît avec la source.
+- Conserve les anciens identifiants, sauvegardes et données de génération.
+- [Contrat et vérifications](docs/head-blocks-beta050.md).
+
+## beta.049 — bateaux rectangulaires et blocs portés
+
+- Ajoute 44 variantes 3 × 1, 1 × 3, 2 × 3 et 3 × 2 avec recettes natives
+ de même bois, modèles par tuiles et plans dans la collection Bateaux.
+- Double les places par module, principales avant secondaires ; conserve
+ les moteurs, commandes collectives et l'équilibre
+ des anciens carrés ; les nouvelles collisions suivent le rectangle tourné.
+- Corrige l'exclusion sonore du joueur qui actionne une trappe portée.
+- Synchronise les aliments visibles et les événements de rendu, avec modèles
+ natifs pour les coffres, Shulkers, cloches, feux de camp et bateaux portés.
+- Oriente les façades et les têtes vers l'avant du personnage.
+- Ajoute dix ticks de signal au bloc porté après un coup ; les pistons
+ s'étendent localement sans déplacer le terrain.
+- Aucun changement de format de sauvegarde ni de génération.
+
+## beta.048
+
+- Ajoute les bateaux 2 × 2 et 3 × 3 dans les onze bois natifs, fabriqués avec
+ quatre ou neuf bateaux identiques ; recettes dans Bateaux et radeaux.
+- Partage les commandes entre les joueurs directement assis ; les personnes
+ portées alourdissent leur bateau. Maj + clic droit permet les piles sur les pilotes.
+- Différencie poule, chauve-souris, perroquet, abeille, Blaze, Allay et Breeze ; un clic
+ sur le bateau embarque un animal porté, y compris les chauves-souris.
+- Préserve le vol du bateau simple à une poule, double séparément le Happy Ghast.
+- Isole les collisions par pile : raccrochage des petites piles si libre, chute
+ du porteur touché avec son groupe à partir de quatre personnes.
+- Construit les modèles par tuiles de fond natives, avec rebords extérieurs
+ uniquement et deux rames, sans étirer les textures.
+- [Contrat et vérifications](docs/poultry-aeronautics-beta048.md).
+
+## beta.047
+
+- Affiche le nom, l'origine historique, la date et les constellations de l'étoile
+ observée à la longue-vue, avec libellés FR/EN et pagination à la molette.
+- Réserve le clic droit à l'observation ; Maj + clic droit retire le dernier
+ trait, sans toucher aux souvenirs ni aux règles de paiement.
+- Met en cache les positions et les appartenances ; consultation serveur seule,
+ sans migration des journaux ou modification de génération.
+- Affiche le texte des boutons actifs en jaune vif au survol.
+- [Contrat et vérifications](docs/spyglass-stars-beta047.md).
+
+## beta.046
+
+- Affiche les dix icônes d’aptitudes fournies, identiques dans le mod et le resource pack.
+
+- Donne deux places aux nouvelles factions pour dix niveaux : fondateur et invité.
+- Ajoute les constellations dessinées à la souris, longue-vue en main ; trois
+ niveaux par liaison, couleur stable de l’étoile mère, annulation sans remboursement.
+- Permet de voler en bateau avec une poule : Espace monte, relâcher descend.
+- Ajoute l’aptitude Zoom : C maintenu et molette, de 5° à Quake Pro.
+- Applique le nom et l’icône Sanctuary à la fenêtre et aux métadonnées de l’application.
+- [Factions et migration](docs/faction-two-slots-beta046.md) ·
+ [Ciel, bateau, zoom et application](docs/sky-flight-zoom-beta046.md).
+
+## beta.045
+
+- Affiche la version de Sanctuary en haut à gauche du menu principal.
+- Remplace Realms par Friends / Amis en bouton normal sous Multiplayer.
+- Retire les icônes compactes Friends, Language et Accessibility ; remonte
+ Options et Quit pour conserver un espacement régulier.
+- Conserve le parcours natif de la liste d’amis et les restrictions du compte.
+- [Contrat et vérifications](docs/title-menu-beta045.md).
+
+## beta.044
+
+- Icône officielle finale, bleu en haut, identique dans le mod et l'instance.
+- Commandes LAN : /sanctuary intro ne bloque plus les sous-commandes serveur ;
+ /sanctuary time vanilla passe depuis le chat. Contrôles opérateur conservés.
+- Audit positif des 88 œufs et catalogue FR/EN actualisé.
+- Slime : bloc réel sous le joueur, physique native, expiration après 100 ticks.
+- Explosions et projectiles natifs : Creeper, Blaze, Ghast, Wither, Dragon,
+ Breeze, Shulker, Lama et Golem de neige ; crocs réels et onde sonique murale.
+- Poison/Wither/Faiblesse/Lenteur natifs sur les coups et flèches concernés.
+- Golem de cuivre : destination visible reconnue ; Vex : minage indépendant
+ de Building ; secours du Piglin zombifié sans or en l'absence de cible.
+- Contrat et limites : docs/spawn-eggs-audit-beta044.md.
+
+
+## beta.043
+
+- Adapte les blocs portés aux interactions et menus natifs ; stockage public,
+ traitements, retrait sans perte, exceptions Shulker et EnderChest.
+- Conserve les traitements et délais natifs dans une sauvegarde additive.
+- Intègre l’icône officielle finale au mod, au MRpack et au modèle Prism.
+- Build et 41 tests serveur réussis ; parcours graphique supplémentaire non validé.
+- [Contrat et limites](docs/functional-head-blocks-beta043.md).
+
+## beta.042
+
+- Place le bassin des joueurs portés sur la tête du porteur, au lieu d'y placer
+ leurs pieds ; adapte les piles et contrôles de plafond aux nouvelles positions.
+- Prend en compte l'échelle de chaque joueur et l'accroupissement du porteur ;
+ conserve une pose assise pour le passager qui maintient Maj.
+- Vérifie l'activation tardive des commandes pour l'hôte d'un LAN via l'interface
+ native, l'exécution réelle d'une commande d'XP et sa révocation. Le signalement
+ de commandes inutilisables n'est pas reproduit ; le diagnostic reste ouvert.
+- [Contrat et vérifications](docs/lan-carry-beta042.md).
+
+## beta.041
+
+- Exempte les bébés mobs du ralentissement de portage, y compris les familiers
+ affichés en bébé ; un adulte miniature conserve son poids.
+- Recalcule le poids après croissance et dans les piles de joueurs, sans
+ changer les autres modificateurs de vitesse ou les données sauvegardées.
+- [Contrat et vérifications](docs/carry-babies-beta041.md).
+
+## beta.040
+
+- Corrige le crash du Hello World lors d'une connexion multijoueur à froid : les
+ cases de familiers utilisent leurs textures natives avant l'initialisation
+ des composants d'items.
+- Masque les icônes vides de l’armure du HUD ; conserve les pleines et les demies.
+- Lie les inspections et contrôles opérateur à Allow Commands en partie intégrée,
+ avec masquage et révocation côté serveur ; conserve les droits op sur serveur dédié.
+- Présente personnage et familier sur la fiche Habitant, avec un cadrage adapté
+ à la largeur et le nom personnalisé du compagnon.
+- Remplace les étoiles natives par la mémoire partagée des arrivées,
+ advancements, prestiges et premières factions de deux habitants ; conserve
+ leurs étoiles après dissolution et refuse les doublons d'un même duo.
+- Ajoute une rotation stellaire de sept jours réels et un maillage conservé entre
+ les images. Contrat additif : `docs/operator-inhabitant-stars-beta040.md`.
+- Conserve la passe native des étoiles pour préparer les moteurs de shaders et
+ ajoute Vanilla Light, un traitement de couleurs léger désactivable dans les
+ options. Iris pour 26.3-pre-2 reste indisponible et n'est pas embarqué.
+- Conserve les catalogues de recettes supérieurs à 64 Ko en fragments NBT,
+ avec lecture des anciennes chaînes : `docs/recipe-storage-beta040.md`.
+
+## beta.039 — Hello World, connaissance alimentaire et inventaires de mort
+
+- Ajoute trois cases de familiers sous les couleurs du Hello World, avec descriptions au survol et choix enregistrés avant affichage : trois communs, avec 10 % de chance d’un peu commun. Reprise après coupure, choix définitif et remise unique de l’œuf équipé.
+- Ajoute Connaissance alimentaire à quatre niveaux : dégustation requise sinon pour révéler nutrition et saturation ; mémoire serveur conservée à la mort et au prestige.
+- Place l’épuisement sous les sprites natifs de la barre de faim.
+- Ajoute le mode de capture panoramique : six faces à 90°, profondeur native float32 et PNG, masque de brume reconstitué et métadonnées ; corrige la sélection du buffer OpenGL entre lectures de profondeur et de couleur.
+- Nomme les nouvelles têtes avec le pseudo et le numéro de mort ; conserve le profil dans leur rendu cosmétique.
+- Mémorise la disposition au décès et ouvre les nouveaux inventaires de mort à gauche de l’inventaire actuel, avec retrait uniquement, en conservant le rendu normal de l’inventaire actuel et toutes les textures existantes. Les anciennes têtes gardent leur lecture historique.
+- Build, 68 tests serveur et parcours client natif réussis ; 173 textures conservées.
+- [Contrats de données et validation](docs/food-panorama-beta039.md) · [Disposition des tombes](docs/grave-inventory-beta039.md) · [Hello World](docs/hello-starter-beta039.md).
+
+## beta.038 — informations alimentaires, têtes-tombes et factions
+
+- Intègre les indications d’AppleSkin à la faim évolutive : icônes de nourriture et de saturation en infobulle, surplus non absorbé grisé, saturation, épuisement et aperçu de la nourriture tenue, avec données serveur privées.
+- Ajoute quatre préférences locales dans Options et un guide de remplacement des textures, sans modifier la respiration vanilla.
+- Réunit le butin de mort dans la tête du défunt : inventaire étendu, équipement, accessoires et objets manipulés ; pillage libre, récupération sur deux pages et tête transportable.
+- Conserve la règle d’XP, `keepInventory` et la malédiction de disparition ; cherche un sol proche sûr et protège la tête lâchée de la lave, des dégâts, du vide et du despawn.
+- Grise toutes les cases de tombe, refuse les dépôts et conserve sur la tête le nom, la date, la cause native et les coordonnées originales du décès.
+- Autorise la fondation sans prestige pour 10 niveaux : une seule place, détenue par le fondateur, puis des places financées par les charges de prestige. Reprise des anciennes factions et débit d’XP récupérable sans double paiement.
+- Build, 55 tests serveur, 14 784 comparaisons alimentaires et parcours client natif réussis ; deux MRpacks locaux vérifiés.
+- [Contrat de sauvegarde et validation](docs/graves-food-beta038.md).
+
+## beta.037 — temps réel et suivi commun
+
+- Pilote le ciel depuis l’heure réelle : fuseau automatique en solo, fuseau/référence configurables sur serveur, saisons et heure d’été sans géolocalisation.
+- Garde les ticks de simulation normaux, empêche le saut de nuit et les apparitions naturelles de phantoms liées à l’insomnie en temps réel.
+- Ajoute les commandes de consultation, retour vanilla, réactivation et rechargement ; conserve la vitesse native dans les sauvegardes pour le retour sans mod.
+- Centralise instantanés, notifications, expiration et épingles des objectifs ; les advancements utilisent ce moteur, prêt pour les futures quêtes.
+- Corrige le rendu sans F3, qui pouvait sortir avant l’affichage des notifications.
+- Build, 29 tests serveur et deux parcours client natifs réussis ; 7 765 assertions solaires/suivi et deux MRpacks vérifiés.
+- [Contrat de migration, réglages et validation](docs/realtime-beta037.md).
+
+## beta.036 — notifications, objectifs épinglés et recettes consultables
+
+- Place les notifications en haut à droite par défaut ; ajoute neuf positions et l’évitement des colonnes F3, toasts, boss et hotbar.
+- Fait expirer les progrès automatiques, notamment A Balanced Diet ; nettoie le fil temporaire à la mort et au respawn.
+- Un clic sur un advancement visible épingle son suivi, un autre le retire ; trois choix maximum, conservés par compte et monde/serveur, retirés à la réussite.
+- Fait planer avec un animal volant porté, efface la chute accumulée pendant le plané et réduit la visibilité native des joueurs allongés au sol.
+- Rend recherchables dans JEI les sorties des recettes connues, sans ajouter de possession ni révéler toutes les matières premières.
+- Garde quêtes et temps réel pour leurs prochains tickets.
+- Build, 36 tests serveur et deux parcours client natifs réussis ; deux MRpacks vérifiés et anciennes livraisons conservées.
+- [Contrat, compatibilité et validation](docs/corrections-beta036.md).
+
+## beta.035 — collections et recettes Minecraft
+
+- Intègre les 2 042 recettes classées dans les 67 collections documentées, avec les 126 advancements et leurs amorces de découverte.
+- Enseigne les familles complètes sans demander leurs ingrédients ; conserve le catalogue à 4 niveaux et la fabrication manuelle libre.
+- Préserve les collections bois/alchimie, leurs accès historiques et les connaissances des habitants sans modifier le schéma de sauvegarde.
+- Indexe les déclencheurs au chargement ; enseigne les recettes d’une collection une seule fois par carnet et révision de données.
+- Affiche les collections connues en FR/EN et annonce leurs déblocages dans le fil de découvertes. Garde l’ouverture du catalogue en haut de sa page.
+- Relie chaque fiche documentaire à son JSON serveur ; ajoute le contrôle de concordance du catalogue au build.
+- Build, 28 tests serveur (dont les 126 advancements), parcours client natif et deux MRpacks vérifiés ; collections conservées après prestige, mort et redémarrage.
+- [Contrat, reprise et validation](docs/collections-beta035.md).
+
+## beta.034 — découvertes et lisibilité de l’inventaire
+
+- Ajoute un fil de premières découvertes en style F3, sans ouvrir le débogage : blocs vus ou possédés, objets et recettes apprises.
+- Borne la file, regroupe les recettes, efface les messages et affiche les barres natives de collections ; ne rejoue pas les anciennes acquisitions.
+- Options locales persistantes pour le fil, les barres, la durée et le suivi des deux advancements visibles avancés le plus récemment.
+- Inclut les objets au sol visibles à huit blocs, sans voir à travers les murs ni simuler de possession.
+- Retire Factions et History du menu pause ; accès conservés par Progression → Prestige.
+- Restaure la hotbar personnelle en 182×22, cases espacées de 20 pixels et objets de 18×18, avec des zones de clic cohérentes.
+- Build, 22 tests serveur, parcours client natif et exports normal/test rapide vérifiés.
+- [Contrat et validation](docs/notifications-beta034.md).
+
+## beta.033 — navigation directe et test rapide
+
+- Ajoute Discovery, Progression, World, Inhabitant, Factions et History dans le menu pause ; retire les boutons de signalement, commentaires et amis.
+- Libère les en-têtes des pages, ouvre World sur la carte et renomme l’accès au cycle en Prestige.
+- Discovery démarre sur Owned ; Catalogue à quatre niveaux révèle All Blocks parmi les découvertes réelles. Le serveur filtre aussi les entrées transmises.
+- Retire les outils opérateur de Discovery, masque la configuration JEI et simplifie les libellés favoris/historique FR/EN.
+- Corrige la hauteur de la flamme du gâteau porté.
+- Ajoute un module facultatif Sanctuary Test : nouveau preset plat, admission réelle et cinématique automatiquement passée. Pack normal sans ce module.
+- Build, 34 tests serveur, parcours client natifs et deux MRpacks vérifiés ; History final testé en jeu.
+- [Menus et droits](docs/menus-discovery-beta033.md) · [profil de test](docs/test-rapide-beta033.md).
+
+## beta.032 — refonte des pouvoirs des 88 familiers
+
+- Chiffre et implémente les 88 passifs/actifs, recharges réduites et descriptions FR/EN.
+- Ajoute les zones partagées, brumes poussées par le vent, interpositions et déplacements consentis avec Maj.
+- Conserve les doses natives des potions, les vrais outils, graines, munitions et stocks ; hotbar protégée lors du rangement.
+- Budgets persistants par bénéficiaire : soin, air, garde et enveloppe de déplacement ; contrôle dur limité et résistant aux répétitions.
+- Aperçus de visée avec coûts/refus pour les joueurs ayant étudié les familiers et les opérateurs ; G agit une fois par pression.
+- Migration additive documentée, anciens achats, noms, tailles et configurations personnalisées conservés ; client et serveur beta.032 requis ensemble.
+- Build, 85 tests serveur, parcours client natif et export local vérifiés. Équilibrage en partie, rangs et Anomaly restent à poursuivre.
+- [Catalogue des valeurs](docs/spawn-eggs-v2-catalogue-beta032.md) et [contrat de livraison](docs/spawn-eggs-v2-beta032.md).
+
+
+## beta.031 — apparition galactique
+
+- Cinématique de 29,5 s entre validation de la fiche et admission : UUID serveur
+ en 32 glyphes SGA successifs, profondeur, rémanences, spirale vers le centre et flash blanc sur le spawn.
+- Première arrivée gardée en configuration jusqu'à la fin ; reprise après une
+ interruption, sans répéter l'introduction des habitants déjà arrivés.
+- Échap pour passer, `/sanctuary intro` pour revoir, libellés FR/EN ; mixage
+ Ambiance et nettoyage des sons. Aucun changement de sauvegarde ou de terrain.
+- Vérifications et export : [INTRO-01](docs/introduction-beta031.md).
+
+
+## beta.030 — éclairage dynamique
+
+- Mains principale/secondaire, cosmétiques de tête, autres joueurs et objets jetés éclairent le décor.
+- Intensités natives des blocs et états allumés/éteints, dont les bougies sur gâteau ; lumière du ciel préservée.
+- Une source par cellule de 4 blocs pour les objets jetés, sans multiplication par quantité.
+- Instantanés de rendu, index spatial, sources proches plafonnées et sections mises à jour par budget.
+- Réglages client et raccourci configurable FR/EN ; aucun bloc artificiel ni changement de sauvegarde.
+- Build, 21 tests serveur, parcours client natif et MRpack local vérifiés.
+- [Contrat et vérifications](docs/dynamic-lights-beta030.md).
+
+## beta.029 — cri et recul des familiers
+
+- Ajoute le cri natif de l’espèce, la voix bébé et les variantes terrestres/aquatiques.
+- Ajoute un recul physique de 0,4 et une courte pause des déplacements autonomes.
+- Conserve le délai entre réactions, les collisions, le suivi, l’invulnérabilité et les protections du portage.
+- Build, 38 tests serveur et MRpack local vérifiés ; écoute et essai visuel à confirmer.
+- [Contrat et vérifications](docs/familiar-hit-feedback-beta029.md).
+
+## beta.028 — plongeon jusqu’à collision
+
+- Retire la fin automatique du plongeon après 30 ticks.
+- Termine la posture sur le sol, un mur, un plafond ou l’entrée dans un fluide.
+- Ignore les nouvelles demandes de posture pendant le plongeon, côté client et serveur.
+- Conserve une impulsion par saut, les dégâts de chute et les interruptions de sécurité.
+- Build, 32 tests serveur et MRpack local vérifiés ; essai visuel à confirmer.
+- [Contrat et vérifications](docs/dive-until-collision-beta028.md).
+
+## beta.027 — piles de joueurs
+
+- Permet de monter sur un joueur déjà porté et de déplacer une pile sur un nouveau porteur.
+- Compte tous les passagers au-dessus pour le ralentissement : facteur 0,75 par passager, plancher 0,25.
+- Refuse les coups entre tous les membres, les cycles et les plafonds trop bas pour le groupe.
+- Dépose du groupe supérieur et nettoyage des liens à la mort ou au déchargement.
+- Build, 27 tests serveur et MRpack local vérifiés ; essai visuel en attente.
+- [Contrat et vérifications](docs/player-stacks-beta027.md).
+
+## beta.026 — cache d’affichage Demeure
+
+- Remplace les rectangles par bloc et par image par une couche de texture alignée sur le terrain.
+- Repeint seulement les empreintes ou pixels connus modifiés ; réutilise les lots réseau identiques.
+- Préserve les contours, couleurs, pixels inconnus et transparence du vide ; retire les empreintes absentes du lot suivant.
+- Reprojection lors du changement de fenêtre, nettoyage des données à la révocation et libération de la texture à la fermeture.
+- Demeure activé par défaut ; règles serveur, génération et sauvegardes inchangées.
+- Build et 9 tests serveur réussis ; test graphique compilé, exécution bloquée par la session Mac verrouillée.
+- [Diagnostic et vérifications](docs/demeure-map-performance-beta026.md).
+
+## beta.025 — barres d’XP des compétences
+
+- Remplace les jauges ASCII et la colonne Max/prix par les sprites de la barre d’expérience native.
+- Prix du prochain palier centré, nombre vert et contour noir ; remplissage initial visible et barre complète au plafond.
+- Largeur adaptée au GUI, icônes et boutons conservés, explications au survol FR/EN.
+- Prix, remboursements opérateur et six rangées d’inventaire inchangés.
+- 24 tests serveur, parcours client FR/EN et export MRpack local vérifiés.
+- [Contrat et vérifications](docs/skill-xp-bars-beta025.md).
+
+## beta.024 — JEI, carnet et collections de recettes
+
+- Port source MIT de JEI 26.2 vers Minecraft 26.3-pre-2 ; fork et dépendances embarqués avec leurs notices.
+- Catalogue à 4 niveaux, découvertes conservées avant achat, filtrage personnel des objets et recettes.
+- Récompenses natives d’advancements et collections du bois/de l’alchimie configurables par datapack.
+- Fabrication manuelle libre ; plans consultables avant la découverte de leur poste de travail.
+- Vue opérateur explicite, révocation des droits et retrait des recettes cachées jusque dans les favoris.
+- Transfert JEI depuis les six rangées débloquées ; cases de récupération exclues.
+- Carnet indépendant conservé au prestige, à la mort et au redémarrage, FR/EN.
+- Conserve le retrait du bouton Cosmetic et l’optimisation du terrain, vérifiée bit à bit.
+- 123 tests serveur et parcours client natif réussis ; export MRpack local vérifié.
+- [Contrat et limites](docs/recipes-jei-beta024.md).
+
+## beta.023 — interactions sur la case cosmétique
+
+- Intègre le correctif de chargement beta.022, sans modifier les valeurs du terrain.
+- Retrait du grand bouton Cosmetic ; clic droit sur la case avec l’objet au curseur, clic gauche et Maj-clic conservés.
+- Bougie/gâteau/statue manipulables depuis l’inventaire ; coûts et restitution au curseur contrôlés côté serveur.
+- Infobulles FR/EN pour les huit cases d’équipement, vides ou occupées.
+- Panneaux écrits par autrui, motifs de bannières, lits/portes complets, minecarts et blocs enveloppant la tête.
+- TNT amorcée par les dégâts, mèche native et explosion réelle qui continue après retrait du cosmétique.
+- [Contrat, stockage et vérifications](docs/head-cosmetics-beta021.md).
+- 117 tests serveur, parcours client et 588 288 comparaisons de densité réussis ; MRpack local vérifié.
+
+## beta.022 — création de monde plus rapide
+
+- Évite les bruits dont l’influence est exactement nulle et les calculs du champ
+ de base au-dessus de son extinction certaine, sans retirer les corps flottants.
+- Même génération, mêmes identifiants et formats de sauvegarde. Comparaisons
+ bit à bit et mesures décrites dans [STARTUP-01](docs/initial-loading.md).
+- Correctif local sur `codex/initial-loading`, non publié et non installé dans Prism.
+
+## beta.020 — 2026-09-13, familiers singuliers et interactions
+
+- Portage natif par Maj + clic droit : animaux sur la tête, joueur sur joueur, ralentissement du porteur et protection mutuelle contre les coups.
+- Dépose avec la main vide, synchronisation du porteur, fin du portage à la déconnexion/mort/dimension et sauvegarde indépendante des animaux.
+- Gestes de portage séparés du raccourci double-Maj pour s’asseoir.
+- Familiers bébés lorsque possible, caresses avec cœurs seulement, coup visuel sans dégâts, nom conservé dans l’œuf via enclume/étiquette.
+- Taille aléatoire enregistrée dans chaque œuf au premier équipement, de ×0,12 à ×6 ; tailles extrêmes rares, données conservées aux transferts et sauvegardes, rendu et noms adaptés.
+- Cinq couleurs de rareté et noms cryptiques du passif/actif ; Étude des familiers à quatre niveaux pour révéler les détails dans les infobulles FR/EN.
+- Réglages et carnet déplacés dans Habitant, aides alliées acceptées par défaut, choix historiques conservés ; utilisation du pouvoir par G configurable.
+- Plongeon gratuit après un saut en sprint, avec la touche S’allonger, une impulsion par saut et rendu horizontal immédiat.
+- Détections derrière les parois et téléportations à travers les murs vers un emplacement libre, dans leur portée.
+- Freinage de chute : distance accumulée remise à zéro pendant le ralentissement, dégâts natifs pour une nouvelle chute non protégée.
+- Contrôles purs, 104 tests serveur, parcours clavier/souris et rendu des 88 modèles à trois tailles vérifiés ; export beta.020 local immuable, PNG et beta.019 conservés.
+
+
+## beta.019 — 2026-09-13, pouvoirs et locomotion des familiers
+
+- 88 passifs et 88 actifs avec cinq raretés, Lien actif à quatre niveaux et touche G configurable.
+- État indépendant persistant, recharge commune, récupération des bénéficiaires, consentement révocable et panneau FR/EN.
+- Dégâts plafonnés, ressources réelles, échanges natifs, cycles de production attribués et actions interrompues au changement de lien.
+- Vol orbital du Dragon, vol des Withers/Ghasts, nage, déplacements terrestres et sauts adaptés ; invincibilité et absence de collision d’entités conservées.
+- Éclairage des modèles à la position réelle du familier ; animations natives pilotées sans IA hostile.
+- Fondu de proximité des familiers volants entre 0,6 et 3,5 blocs, corps et yeux compris ; monstres du monde inchangés.
+- Rangs I–III, Anomaly et obtention des œufs reportés ; équilibrage à poursuivre.
+- Contrôles purs, 91 tests serveur, parcours client et export local vérifiés. [Détails](docs/companion-powers-beta019.md).
+
+## beta.018 — 2026-09-13, équipement cosmétique et familiers
+
+- Trois cases d’équipement personnel, icônes 16 × 16 du créateur dans le mod et le resource pack.
+- Titre Inventaire sans suffixe de rangées ; Maj-clic pour équiper/retirer œufs et capes.
+- Ordre visuel des emplacements : cosmétique, cape, œuf, main secondaire ; cosmétique équipé manuellement.
+- Maj + clic gauche maintenus transfèrent successivement les cases parcourues, y compris lors d’un mouvement rapide, selon les règles du conteneur.
+- Tab change la rangée de hotbar en jeu comme dans l’inventaire ; Maj inverse le sens. Touche configurable, cases et colonne sélectionnée conservées.
+- Minage et construction groupés verrouillés au premier clic : tourner la caméra ne change plus la sélection, ne réinitialise plus la charge et n’interrompt plus les poses.
+- Taille et orientation des poses figées pendant le maintien ; nouveau groupe après relâchement. Portée, droits, outils et matériaux toujours contrôlés côté serveur.
+- Familier temporaire avec navigation, promenade, retour sûr, invincibilité et absence de collisions d’entités.
+- Caresse par clic droit du propriétaire : particules de son œuf et cœurs, sans pouvoirs passifs/actifs.
+- Cape Zéro de test avec rendu et physique de cape natifs ; visuel provisoire attribué dans les notices.
+- Tout objet/bloc accepté sur la tête à titre cosmétique ; états de blocs et piston réagissant aux dégâts en rendu uniquement.
+- Stockage joueur additif `sanctuary:accessories` schéma 1, contrat de migration documenté ; mort native, conservation au prestige.
+- S’allonger gratuit avec W, retiré des aptitudes achetables ; Se reposer reste achetable et utilise la pose de sommeil des lits.
+- Preuves d’achat historiques de S’allonger conservées ; récupération vie/faim et temps inchangés.
+- Client et serveur beta.018 ensemble ; nouveau contrat réseau pour les cases d’équipement.
+- `check build assemblePack`, contrôles purs, 67 tests serveur et parcours client natif validés ; MRpack local vérifié, exports antérieurs inchangés.
+
+## beta.017 — 2026-09-13, aptitudes de mouvement et retrait du livre natif
+
+- Aptitudes Nage, S’allonger et Se reposer : 4 niveaux chacune, prix configurables.
+- Nage rapide verrouillée avant achat ; déplacement lent et remontée conservés.
+- W bascule la posture basse, Maj + W le repos ; touche configurable, V laissé libre.
+- Repos immobile : un demi-cœur toutes les 10 secondes pour une demi-barre de faim ;
+ mouvement et dégâts interrompent la séance, sans changer l’heure.
+- Progression 8 → 9 et configuration 4 → 5 selon le [contrat](docs/swimming-recipebook-beta017.md) ;
+ achats antérieurs conservés et nouvelles aptitudes persistantes après prestige.
+- Bouton, panneau, fantômes et invitation à consulter le livre de recettes natif retirés ;
+ fabrication, recettes serveur, statistiques et advancements conservés.
+- Prix du prochain rang à côté de chaque jauge de compétence, à la place du ratio ;
+ `Max.` lorsque le dernier rang est atteint.
+- Son vanilla de ramassage d'XP pour chaque récompense des Premiers pas, une seule
+ fois par étape ; le défaut W tient compte de la disposition AZERTY/QWERTY.
+- JEI lié aux advancements reste à intégrer. XP du tutoriel et textures beta.016 incluses.
+- `check build assemblePack`, 62 tests serveur puis 15 tests finaux ciblés et parcours
+ client natif validés ; MRpack beta.017 local vérifié, exports antérieurs inchangés.
+
+## beta.016 — 2026-09-13, XP des premiers pas
+
+- Cinq actions du tutoriel natif donnent chacune 8 points d’XP par défaut :
+ déplacement/vue, arbre, première bûche minée, inventaire et planches fabriquées.
+- Récompenses personnelles uniques, message FR/EN et montant configurable
+ dans `config/sanctuary/tutorial.json` ; créatif et spectateur exclus.
+- Attachement `sanctuary:tutorial` conservé avec les XP dans les données joueur,
+ après la mort et le New Game+. Les anciens habitants peuvent réaliser les
+ gestes une fois ; aucun paiement des statistiques historiques.
+- [Contrat de migration additive](docs/tutorial-xp-beta016.md) et capacité
+ réseau beta.016 commune au client et au serveur. Icônes beta.015 conservées.
+- 57 tests serveur, contrôles purs et parcours client natif validés ; MRpack local vérifié.
+
+## beta.015 — 2026-09-13, nouvelles textures de progression
+
+- Remplacement des six icônes de compétences par les nouveaux PNG 9 × 9 du
+ créateur, sans retouche ni changement des chemins utilisés par le Blocodex.
+- Copies identiques préparées dans les sources du resource pack Sanctuary.
+ Correspondances `food → hunger` et `breath → breathing` documentées dans
+ [le ticket](docs/progression-textures-beta015.md).
+- Build, six tests serveur de progression et export local validés. Bytecode
+ identique à beta.014 ; sprites du HUD de respiration et de faim vanilla conservés.
+
+## beta.014 — 2026-09-13, rangement, hotbar mobile et noms de faction
+
+- Aptitude Rangement dans le Blocodex, 4 niveaux par défaut et prix
+ configurable.
+- Clic molette configurable : piles regroupées et stockage débloqué trié ; la
+ hotbar ne participe que lorsqu'elle est visée. Les composants d'objets restent
+ distincts, les cases de récupération et le conteneur ouvert sont préservés.
+- Tri validé et appliqué côté serveur, sans objets fournis par le client.
+- Tab/Maj+Tab et la molette déplacent le cadre de la hotbar parmi les rangées,
+ en gardant chaque objet dans sa case visible. Le tri exclut la rangée active
+ sauf si elle est visée. La sélection est sauvegardée avec les objets ; revenir
+ à une rangée après New Game+ conserve les autres objets en récupération.
+- Nom de faction en préfixe du pseudo, coloré, avec prestige romain conservé :
+ fiche Habitant, noms affichés, chat et liste Tab. Mise à jour après adhésion,
+ départ ou dissolution, y compris quand la fiche est déjà ouverte.
+- Descriptions FR/EN des aptitudes sans rappel individuel de leur conservation
+ après New Game+ ; la règle générale reste dans le bilan du cycle.
+- Progression 7 → 8 et configuration 3 → 4 selon le
+ [contrat préalable](docs/inventory-sorting-beta014.md) ; nouvelle capacité
+ réseau de rangement, mise à jour du client et du serveur ensemble.
+- `check build assemblePack`, 56 tests natifs serveur et parcours client FR/EN
+ validés. Export local beta.014 vérifié ; les MRpacks beta.003–013 sont inchangés.
+
+## beta.013 — 2026-09-13, Progression, prestige et repères visuels
+
+- Icône, nom, jauge ASCII, valeur/plafond et boutons réunis sur une ligne par
+ compétence, avec colonnes adaptées au GUI et noms défilants si nécessaire.
+- Courbe par défaut 1, 2, 4, 8, 16, 32, 48 niveaux. Configuration au schéma 3 :
+ reprise de l'ancienne courbe par défaut, conservation des prix personnalisés.
+ Les anciens achats gardent leur montant de remboursement exact.
+- La page Factions explique les charges gagnées au New Game+ et le recrutement
+ sans prestige ; un bouton mène au bilan des six compétences dans Cycle.
+- Prestige en chiffres romains après le nom sur la fiche Habitant, sans renommer
+ l'identité. Les 45 cases de récupération restent grisées après un passage 6 → 1,
+ y compris les cases vides des rangées supplémentaires et les vues de coffre.
+- La carte actualise les relevés sans effacer son image à chaque flux réseau ;
+ les déplacements réutilisent les zones reçues, dans un cache temporaire borné.
+- Sprite de locator natif sur la carte ; couleurs d'habitants attribuées aux
+ waypoints côté serveur. Les marqueurs se superposent à la barre d’XP,
+ dont le fond et le remplissage restent visibles. Les règles de visibilité restent natives.
+- Molette selon la zone survolée : case de hotbar, rangée d’inventaire ou
+ échanges de villageois ; rotations confirmées par le serveur.
+- `check build assemblePack`, 50 tests natifs serveur et parcours graphique
+ FR/EN validés. MRpack local vérifié, avec ses ressources et versions internes.
+- [Contrat et vérifications](docs/progression-inline-beta013.md).
+
+## beta.012 — 2026-09-13, Inventaire et contrôles de progression
+
+- Fond des coffres, machines et zones de clic alignés avec les cases à chaque GUI.
+- Stockage dans l'ordre naturel ; barre rapide en bas et rotation Tab / Maj+Tab,
+ configurable, validée par le serveur et limitée aux rangées débloquées.
+- Maj-clic fusionne les piles de toutes les rangées avant d'occuper une case vide.
+ Les rangées supplémentaires suivent les règles natives de chaque machine ;
+ un coffre plein ne provoque plus de déplacement interne au joueur.
+- Six jauges de compétence avec valeur/plafond, + selon l'XP disponible et −
+ opérateur avec remboursement du coût réellement payé pour le rang retiré.
+- Progression au schéma 7 : audit des retraits et révision monotone, reprise des
+ schémas précédents, objets conservés en récupération après réduction de capacité.
+ Contrat préalable : `docs/inventory-flow-beta012.md`.
+- Nouvelle capacité réseau ; client et serveur beta.012 doivent être associés.
+ `check build assemblePack` et 49 tests natifs passent ; parcours graphique
+ vérifié avec coffres remplis, touches Tab/E et remboursement opérateur.
+ MRpack local et reçus de vérification consignés dans le ticket.
+
+## beta.011 — 2026-09-13, Factions et New Game+
+
+- Bilan des six compétences et confirmation du New Game+ dans le Blocodex.
+- Un prestige par cycle terminé ; charges personnelles et prix de fondation /
+ agrandissement configurables, enregistrés une seule fois côté serveur.
+- Factions persistantes, invitations de sept jours, capacité, responsable et
+ membres, transmission, exclusion, départ et dissolution sans remboursement.
+- Migration additive des achats vers le schéma 6 ; aptitudes, objets et données
+ communautaires conservés, six rangs remis à zéro, XP configurable.
+- Journal atomique commun aux cycles et aux dépenses de faction, historique
+ daté, archivage des achats et reprise du passage après interruption.
+- Interfaces natives FR/EN défilantes ; aucun claim ni droit opérateur accordé.
+ Voir [contrat et vérifications](docs/cycle-factions-beta011.md).
+
+## beta.010 — Inventaire extensible
+
+- Compétence Inventaire : cinq achats, de neuf à 54 cases, barre rapide incluse.
+- Migration progression 4 → 5 avec rang Inventaire nul et historique préservé.
+- Conservation des anciennes cases remplies en retrait uniquement ; les 18
+ nouvelles cases sont enregistrées avec le joueur et suivent `keepInventory`.
+- Portage des six fonds personnels et des fonds de conteneurs 26.2, adaptation
+ aux menus natifs, défilement des rangées sur les petits écrans et accès à
+ l'inventaire personnel depuis le catalogue créatif.
+- Collecte, déplacements rapides, glisser-déposer et livre de recettes suivent
+ les capacités serveur. Les indices des machines et de l'équipement restent
+ inchangés. Voir `docs/inventory-beta010.md` pour les preuves et limites.
+
+
+## beta.009 — 2026-09-13, Construction et vein building
+
+- Construction devient une compétence achetable : sept rangs, surfaces de
+ 3×3 à 15×15, coûts identiques aux autres compétences et rangs conservés à la mort.
+- Aperçu vert, R + clic droit maintenus, R + molette pour le diamètre. La touche
+ configurable est partagée avec Minage ; une surface reste figée pendant le geste.
+- Placement Minecraft côté serveur, stock réellement consommé, surfaces orientées,
+ dalles et escaliers natifs, refus de collision/protection et interruptions.
+ Les contenants de support ne sont pas ouverts. Les objets à pose multiple
+ (portes, lits, plantes doubles), échafaudages et nénuphars restent en pose individuelle.
+- Migration stricte de progression vers le schéma 4 avec `building: 0`, sans
+ modifier les achats existants. [Contrat et vérifications](docs/building-beta009.md).
+
+## beta.008 — 2026-09-13, Minage et vein mining
+
+- Compétence Minage achetable, sept rangs et vitesse native de 100 à 200 %.
+ Plafonds de veine : 1 / 4 / 8 / 16 / 32 / 64 / 128 / 256 blocs.
+- R configurable pour l'aperçu, R + clic gauche maintenus pour miner,
+ R + molette pour limiter la sélection. Contour des blocs et compteur en jeu.
+- Durée calculée sur le serveur, interruption et protections par bloc,
+ destruction native pour outils, loot, XP, faim, enchantements et statistiques.
+ Un seul effet sonore de casse par groupe ; la rupture d'outil arrête le reste.
+- Progression schéma 3 : ajout de Minage à zéro lors de la lecture des anciens
+ profils, achats et dates conservés, rangs persistants après mort et reprise.
+ Configuration existante conservée. Voir `docs/mining-beta008.md`.
+
+## beta.007 — 2026-09-13, carte immersive
+
+- Carte plein écran sous les commandes, carré central clair et abords sombres.
+ Le vide observé et les zones inconnues sont transparents ; leurs états restent
+ distincts dans les relevés personnels existants.
+- Boutons natifs superposés, coordonnées et zoom en bas à gauche. Grille,
+ empreintes Demeure et marqueur du joueur suivent le terrain sur tout l'écran.
+- Glisser-déplacer et molette dans les marges ; priorité des boutons, y compris
+ désactivés. Zoom minimal adapté à la taille du GUI. Libellés FR/EN du zoom.
+- Aucun changement de protocole, de format de sauvegarde ou de génération.
+ Voir `docs/atlas-immersive-beta007.md` pour la validation et l'artefact.
+
+## beta.006 — 2026-09-13, livraison locale d’essai
+
+- Corrige le bouton gauche (SDL / API Minecraft 26.3), les glissements successifs,
+ le relâchement hors carte et la navigation pendant les rafraîchissements.
+- Brouillard cartographique circulaire, bordure tramée et fond parchemin ;
+ seuls les pixels observés sont transmis. Les anciens chunks restent connus.
+- Grille de chunks et surcouche Demeure activables ; survol des zones connues
+ avec état, influence principale et couleur de l’habitant.
+- Premier port autonome de Demeure : activité, placements réussis, destruction,
+ récoltes, sommeil, mort et présence du vivant. Jours UTC réels, budgets
+ persistants et décroissance sans résurrection des anciens scores.
+- Données Demeure séparées ; Atlas schéma 1 lu puis sauvegardé en schéma 2.
+ Pas d’import historique, de modification du terrain, de claim ou de protection.
+ Voir `docs/demeure-beta006.md` et `mods/demeure/PROVENANCE.md`.
+
+
+## beta.005 — 2026-09-13, livraison locale d’essai
+
+- Carte native de surface dans Blocodex → Monde : glisser, zoomer, recentrer,
+ orientation et couleur du joueur. Aucun code ou asset Xaero repris.
+- Aptitude Grande carte, coût serveur configurable (4 niveaux par défaut) ;
+ exploration personnelle enregistrée avant l’achat et conservée après la mort.
+- Inspection opérateur explicite des chunks chargés, séparée des découvertes
+ de l’habitant. Pas de génération déclenchée par la consultation.
+- Noms de couleurs FR/EN associés aux teintes, à la place du SGA ; boutons
+ Minecraft au texte coloré, sans changer les couleurs déjà attribuées.
+- Retrait des boutons S’asseoir des aptitudes et de la fiche ; geste inné conservé.
+- Lecture des progressions/configurations schéma 1, ajout de l’aptitude au
+ schéma 2 et fichiers de relevés séparés. Contrat : `docs/atlas-beta005.md`.
+
+## beta.004 — 2026-09-13, livraison locale d'essai
+
+- Cadre Blocodex natif : titre et onglets Discovery, Progression, Story,
+ Inhabitant en haut ; Done / Terminé centré dans le pied de page.
+- Contenu adapté à la taille du GUI, cadre partagé avec le catalogue de
+ découvertes, biographie et histoire défilantes avec texte complet.
+- Six icônes 9×9 fournies par le créateur, conservées sans transformation
+ et affichées à gauche de chaque compétence.
+- Rétablit le seuil vanilla de sprint : plus de 6 points de faim. Le premier
+ rang de Faim permet de dépasser ce seuil après avoir mangé. Contrôle côté
+ serveur, arrêt à 6 points, exceptions créative et montures conservées.
+- Compatible avec les profils beta.003, sans migration des sauvegardes ni
+ changement de terrain. Voir `docs/blocodex-beta004.md`.
+
+## beta.003 — 2026-09-13, livraison locale d'essai
+
+- HELLO_WORLD avant le spawn : skin, pseudo, bio, palette RGB déterministe
+ extensible et accueil `helloworld {pseudo}` unique.
+- Blocodex commun depuis B et Échap : fiche, progression, histoire, blocs.
+- Compétences à gauche (six entrées, dont Minage, Construction et Inventaire
+ réservées au prochain ticket), aptitudes en liste défilante à droite.
+- Couleurs dans des boutons Minecraft classiques : glyphes SGA teintés,
+ hexadécimal standard dans l'infobulle.
+- Départ 3/3/3, capacités achetables avec les niveaux et conservées à la mort.
+- Première aptitude Noms : familles de 26.2, configuration serveur par type
+ de mob et rechargement opérateur, priorité aux étiquettes des joueurs.
+- Position assise disponible dès le début, sans XP ; sièges non sauvegardés.
+- 70 % des points d'XP disponibles laissés en orbes à la mort, configurable ;
+ respect de `keepInventory`.
+- Données additives ; adoption explicite des anciens mondes, aucun changement
+ du terrain ou des expansions. Notes : `docs/progression-beta003-release.md`.
+
+## beta.002 — 2026-09-12, préparation locale
+
+- Nord boréal/Peaks, avec les trois biomes de glace de l’ancien profil glacial
+ ajoutés à la palette boréale de cette révision.
+- Bassin ouest en `warm_ocean` pour la décoration native de corail.
+- Réserve une pyramide du désert au sud et un temple de la jungle à l’est,
+ avec des emplacements déterministes variables et des pièces vanilla.
+- Isole cette génération dans les nouveaux mondes de révision 2 ; les mondes
+ beta.001 et alpha ne sont ni convertis ni régénérés.
+- Version du mod et du pack `beta.002`, MRpack `Sanctuary-beta.002.mrpack`.
+ Vérifications : `docs/expeditions-beta002.md`.
+
+## beta.001 — 2026-09-12, préparation locale
+
+- Adopte `beta.xxx`, à partir de `001`, pour le mod, le pack et les
+ futurs tags ; une livraison consomme un numéro, y compris pour un correctif.
+- Vérifie le format et l’alignement mod/pack lors du contrôle packwiz.
+- Clôt le chantier worldgen sur le socle alpha.30.7 à la demande du créateur ;
+ ouvre le chantier des anciennes expéditions avant la progression en jeu.
+- Les nouveaux mondes ouvrent quatre îles de 512 blocs : nord glacial/Peaks,
+ est tropical/Volcan, sud aride/Canyon et ouest humide/Océan, avec noms FR/EN.
+- Cherche la première position admissible sur chaque axe, sans l’écart et le
+ décalage aléatoire alpha ; conserve les emprises écrites et les bateaux.
+- Enregistre les cinq régions dans une transaction initiale, journal bêta
+ distinct, sans migration automatique des anciennes sauvegardes.
+- Le relief initial et les codecs alpha restent conservés. Aucune publication
+ ni synchronisation Prism effectuée pour cette préparation.
+
+Voir [les expéditions et leurs vérifications](docs/expeditions-beta001.md) et
+[la convention de versionnement](docs/versioning.md).
+
## 0.1.0-alpha.30.4
- Plateaux bas conservés, abords des massifs élargis et hauts reliefs en 3D ; aucun seuil de découpe latérale, microbruit ou gradin ajouté.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b29765d..afdcf43 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -27,6 +27,11 @@ Les nombres, ressources et interfaces encore incertains peuvent rester des hypot
Les commandes exactes de développement vivent dans le [README](README.md), afin de ne pas maintenir deux listes divergentes.
+Les livraisons du mod et du pack partagent le compteur `beta.xxx`,
+à partir de `beta.001`. Incrémenter les deux propriétés et le manifeste
+packwiz ensemble ; le tag reprend cette version exacte, sans `v`. Une modification
+documentaire seule ne consomme pas de numéro. Voir [Versionnement](docs/versioning.md).
+
## Vérifier la génération du monde
Utiliser une sauvegarde de développement dédiée et conserver la seed des observations. Les vérifications pertinentes comprennent le spawn, les limites de l'île, le vide, les jointures de chunks, le comportement de l'eau, le redémarrage et l'arrivée de plusieurs joueurs.
diff --git a/README.md b/README.md
index 219cc81..7d05ca2 100644
--- a/README.md
+++ b/README.md
@@ -12,14 +12,932 @@ infrastructures qui permettront progressivement d'ouvrir de nouveaux continents.
> Rather than a linear campaign, the server itself becomes a world that grows
> through the action of its players.
-Ce dépôt démarre la nouvelle base Fabric. La [vision complète](docs/vision.md)
-conserve les intentions ; le [backlog](docs/backlog.md) suit les
-tickets. Les systèmes d’économie, de progression collective et les dimensions
-décrits dans la vision ne sont pas encore implémentés. **L’alpha.27 repart de
-l’alpha.24**, après le rejet du rendu de l’alpha.25. Cette première passe conserve
-le relief et les souterrains de la 24, retire la montgolfière et travaille les
-marais souterrains et la restauration de vrais modèles d’épaves vanilla. Voir le
-[chantier et ses limites](docs/generation-alpha27.md).
+Le chantier worldgen est terminé sur le socle **alpha.30.7**, selon la décision
+du créateur du 12 septembre 2026. La suite commence par une passe courte sur
+les **expansions**, puis se concentre sur la **progression en jeu**.
+La [vision complète](docs/vision.md) conserve les intentions ; le
+[backlog](docs/backlog.md) suit les tickets et leurs résultats vérifiés.
+L’économie, les claims et les dimensions décrits dans la vision restent
+à implémenter.
+
+Les sources Git sont synchronisées jusqu’à **beta.060**. Le récapitulatif de
+[synchronisation](docs/git-sync-beta060.md) précise les vérifications et les
+limites. Les archives bêta mentionnées dans ce document sont des fichiers
+locaux ; la dernière release du canal packwiz reste **alpha.30.7**.
+
+Les [archives pour le site](archives/web/README.md) conservent la maquette
+Blocodex autonome, les documents et les images retrouvées jusqu'à alpha.30.7.
+La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
+l’étude de Xaero et des alternatives libres.
+
+
+## beta.060 — objets et blocs sur les mobs
+
+Maj + clic droit avec un objet équipe la tête d'un mob. Clic droit utilise
+le bloc ; Maj + clic droit à main vide récupère l'objet. Une poule portant une
+torche de redstone alimente les circuits voisins en se déplaçant. Coffres,
+cuissons, leviers, boutons et autres blocs reprennent les interactions de tête.
+[Contrat et vérifications](docs/mob-head-blocks-beta060.md).
+Build, parcours natif et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.060.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.060.mrpack).
+
+## beta.059 — bannières et cartes au trésor
+
+Un clic droit enregistre une bannière avec sa couleur et son nom ; utiliser
+une carte au trésor importe ses repères dans la World Map. Les opérateurs
+peuvent inspecter les trésors des îlots sans les découvrir pour leur personnage.
+Les cartes s'obtiennent dans le butin des navires et des îlots miniers.
+[Contrat](docs/atlas-markers-beta059.md). Build, parcours natif et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.059.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.059.mrpack).
+
+## beta.058 — altitude de la carte
+
+Les boutons ↑ et ↓ sous le zoom changent la coupe de la carte :
+0, 140, 280, 420, 560, 640. La position et le zoom restent en place ;
+les relevés personnels des couches inférieures se constituent lors des visites.
+Chunks et Demeure sont désactivés par défaut. Le cadre assombri est retiré.
+[Contrat](docs/atlas-altitude-beta058.md). Build et parcours natif vérifiés.
+[Pack normal](build/Sanctuary-beta.058.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.058.mrpack).
+
+## beta.057 — météo quotidienne et nouvelles ambiances
+
+`/sanctuary weather realtime` choisit une ambiance pour chaque journée réelle :
+beau temps, ciel gris, brouillard, averses, pluie continue ou orage.
+Les averses alternent avec des périodes sèches ; les autres profils gardent
+leur état pour la journée. Le serveur suit son fuseau Real Time et conserve
+la prévision lors des reconnexions. `/sanctuary weather vanilla` reprend le
+cycle Minecraft. [Contrat](docs/daily-weather-beta057.md).
+Build, essais natifs et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.057.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.057.mrpack).
+
+## beta.056 — étoiles dispersées et saturation
+
+Les étoiles d'un même accomplissement se répartissent sur toute la sphère
+céleste. Les nouveaux souvenirs augmentent la densité sans déplacer ceux déjà
+affichés. Les anciennes constellations conservent leurs liens et suivent cette
+nouvelle disposition. Vanilla Light démarre à 142 % de saturation, en respectant
+les préférences déjà enregistrées. [Contrat](docs/sky-spacing-beta056.md).
+Build, test client natif et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.056.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.056.mrpack).
+
+## beta.055 — Force et portage de joueurs
+
+Force réduit le ralentissement lié aux joueurs portés, y compris dans une pile.
+Avec un joueur sur la tête : 75 % de vitesse sans Force, environ 87 % avec
+Force I et 91 % avec Force II. Le calcul suit l'effet du porteur et se rétablit
+à son expiration. [Contrat et vérifications](docs/strength-carry-beta055.md).
+Build et test natif client/serveur réussis.
+[Pack normal](build/Sanctuary-beta.055.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.055.mrpack).
+
+## beta.054 — duels, mises et niveaux des familiers
+
+H → Duel permet de défier un joueur proche. Chacun choisit éventuellement une
+mise dans son inventaire, puis les deux valident. Le gagnant reçoit les deux
+mises ; une interruption les restitue. G déclenche la technique du familier.
+Les gains restent disponibles si l'inventaire est plein. H → Niveaux du familier
+permet de stocker et récupérer son XP. Chaque niveau renforce la vie et les
+dégâts sans plafond ; la rareté de l'œuf augmente également. Transferts hors
+combat, exacts en points d'XP, conservés avec l'individu lors des échanges.
+[Contrat et vérifications](docs/familiar-combat-beta054.md).
+Les 88 profils sont présents ; certaines variantes et leur équilibrage restent
+à finaliser. Build, parcours natifs FR/EN et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.054.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.054.mrpack).
+
+## beta.053 — brume progressive et saturation
+
+Vanilla Light fait commencer la brume dès le point de vue. Options → Shaders
+propose une saturation de 0 à 200 %, appliquée immédiatement et sauvegardée ;
+100 % conserve le rendu habituel. Les œufs de Hello World sont alignés à gauche.
+[Contrat et vérifications](docs/progressive-fog-beta053.md).
+Build et parcours client natif FR/EN réussis ; archives locales vérifiées.
+[Pack normal](build/Sanctuary-beta.053.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.053.mrpack).
+
+## beta.052 — nom du familier de départ
+
+Hello World propose un champ « Nom du familier » sous les trois œufs. Le nom
+choisi suit le compagnon et sa fiche d'habitant ; vide, il garde le nom de son
+espèce. [Contrat et migration](docs/starter-name-beta052.md).
+Build, **8 tests serveur**, parcours client FR/EN et reconnexion vérifiés.
+[Pack normal](build/Sanctuary-beta.052.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.052.mrpack).
+
+## beta.051 — distributeur porté orienté par le regard
+
+Les projectiles partent dans la direction regardée par le porteur, y compris
+vers le haut et vers le bas. Le tir différé utilise le regard au moment du
+lancement et suit la position précise de la tête.
+[Contrat et vérifications](docs/head-dispenser-beta051.md).
+Build, **34 tests serveur** et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.051.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.051.mrpack).
+
+## beta.050 — bateaux compacts et redstone portée
+
+Bateaux **1 × 2 et 2 × 1**, quatre places dans les onze bois. Bibliothèques
+sculptées compatibles avec les livres enchantés, objets visibles dans les
+étagères et livre visible sur le pupitre. Lits complets, uniquement décoratifs ;
+pots de fleurs utilisables. Boutons et leviers reposent sur la tête et, avec
+la torche de redstone, alimentent les circuits proches.
+[Contrat et vérifications](docs/head-blocks-beta050.md).
+Build, **40 tests serveur**, parcours client natif FR/EN et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.050.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.050.mrpack).
+
+## beta.049 — bateaux rectangulaires et chapeaux actifs
+
+Bateaux 3 × 1, 1 × 3, 2 × 3 et 3 × 2 dans les onze bois, avec recettes
+identiques à leur disposition. Chaque module reçoit deux passagers, en remplissant
+d’abord les places principales, puis les secondaires. Les blocs portés montrent leurs aliments et
+leurs ouvertures ; leurs sons incluent celui qui les utilise. Têtes et fours
+regardent vers l'avant, les bateaux ont leur modèle de chapeau et les coups
+produisent un signal redstone local.
+[Contrat et vérifications](docs/boats-head-beta049.md).
+Build, **35 tests serveur**, parcours client natif FR/EN et archives vérifiés.
+[Pack normal](build/Sanctuary-beta.049.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.049.mrpack).
+
+## beta.048 — bateaux collectifs et moteurs animaux
+
+Bateaux 2 × 2 et 3 × 3 dans les onze bois natifs, avec fonds répétés, rebords
+extérieurs et deux rames. Quatre ou neuf bateaux du même bois forment la recette.
+Poule, chauve-souris, perroquet, abeille, Blaze, Allay et Breeze fournissent des
+propulsions différentes ; les pilotes partagent les commandes, les piles ajoutent
+leur poids. Les petites piles se raccrochent si la place est libre ; à partir de
+quatre personnes, le porteur touché descend avec son groupe supérieur.
+Le Happy Ghast se déplace deux fois plus vite.
+[Contrat, gestes et vérifications](docs/poultry-aeronautics-beta048.md).
+Build, **33 tests serveur**, parcours client natif FR/EN et exports vérifiés.
+[Pack normal](build/Sanctuary-beta.048.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.048.mrpack).
+
+## beta.047 — étoiles à la longue-vue et boutons
+
+Observer une étoile à la longue-vue affiche son nom, l'événement qui l'a créée,
+sa date et ses constellations. La molette parcourt les appartenances multiples.
+Le clic droit observe ; **Maj + clic droit** retire désormais le dernier trait.
+Les textes des boutons actifs deviennent jaune vif au survol.
+[Contrat et vérifications](docs/spyglass-stars-beta047.md).
+Build, **12 tests serveur**, parcours client FR/EN et exports vérifiés.
+[Pack normal](build/Sanctuary-beta.047.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.047.mrpack).
+
+## beta.046 — constellations, vol et zoom
+
+Les nouvelles factions proposent deux places dès leur création.
+Longue-vue en main, le clic gauche relie les étoiles et le clic droit retire le
+dernier trait. Une poule dans le bateau permet de monter avec Espace et de
+redescendre en le relâchant. Le zoom devient une aptitude, avec C et la molette.
+La fenêtre utilise le titre Sanctuary - beta.046 et l’icône officielle.
+Les dix aptitudes affichent les icônes fournies, également copiées dans le resource pack.
+[Gestes, règles et vérifications](docs/sky-flight-zoom-beta046.md) ·
+[Migration des factions](docs/faction-two-slots-beta046.md).
+[Pack normal](build/Sanctuary-beta.046.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.046.mrpack).
+Build, **20 tests serveur**, parcours client FR/EN et intégrité des exports vérifiés.
+
+## beta.045 — menu principal
+
+La version de Sanctuary apparaît en haut à gauche. Friends / Amis est un bouton
+normal sous Multiplayer, à la place de Realms. Les trois icônes compactes
+Friends, Language et Accessibility sont retirées du menu principal.
+[Contrat et vérifications](docs/title-menu-beta045.md).
+[Pack normal](build/Sanctuary-beta.045.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.045.mrpack).
+Build, quatre tests serveur ciblés et parcours client FR/EN réussis.
+
+## beta.044 — pouvoirs natifs des familiers
+
+Les attaques de Creeper, Blaze, Ghast, Wither, Dragon et Breeze utilisent leurs
+effets natifs. Le Slime crée un vrai bloc sous les pieds pendant cinq secondes.
+Les 88 œufs ont un contexte positif d'activation ; les stocks, impacts et effets
+principaux sont contrôlés par les tests.
+[Audit et contrat](docs/spawn-eggs-audit-beta044.md) ·
+[Catalogue des 88 œufs](docs/spawn-eggs-catalogue-beta044.md).
+
+La commande /sanctuary time vanilla fonctionne après activation tardive des
+commandes en LAN : le replay d'introduction ne capture plus les commandes serveur.
+L'icône officielle utilise la dernière variante fournie, avec le bleu en haut.
+[Pack normal](build/Sanctuary-beta.044.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.044.mrpack).
+Build, **69 tests serveur** et deux parcours client réussis.
+
+## beta.043 — blocs portés et icône officielle
+
+Les blocs portés ouvrent leurs menus natifs et gardent leur fonctionnement de
+stockage et de traitement. Au retrait, le contenu est rendu ; les Shulkers le
+conservent. L’icône officielle est incluse dans le mod et les packs.
+[Contrat, couverture et limites](docs/functional-head-blocks-beta043.md).
+Build et **41 tests serveur** réussis. Rendu client supplémentaire non validé.
+[Pack normal](build/Sanctuary-beta.043.mrpack) ·
+[Monde plat rapide](build/Sanctuary-Test-beta.043.mrpack).
+
+## beta.042 — joueurs assis sur la tête
+
+Le bassin du passager repose sur la tête du porteur, avec la pose assise native.
+Le placement tient compte des piles, de l'accroupissement et des différences
+de taille. [Ticket et diagnostic LAN](docs/lan-carry-beta042.md).
+
+Build, **39 tests serveur** et rendu natif réussis. Exports vérifiés :
+[pack normal](build/Sanctuary-beta.042.mrpack) ·
+[monde plat rapide](build/Sanctuary-Test-beta.042.mrpack).
+
+## beta.041 — portage des bébés
+
+Porter un bébé mob ne ralentit plus le joueur. La même règle s'applique aux
+familiers dont le modèle est un bébé, y compris dans une pile de joueurs.
+Les adultes gardent leur poids ; le ralentissement se recalcule si un animal
+grandit pendant le portage. [Contrat et vérifications](docs/carry-babies-beta041.md).
+
+Build et **43 tests serveur** réussis. Exports vérifiés :
+[pack normal](build/Sanctuary-beta.041.mrpack) ·
+[monde plat rapide](build/Sanctuary-Test-beta.041.mrpack).
+
+## beta.040 — mode opérateur, fiche Habitant et ciel partagé
+
+Le crash du Hello World en multijoueur à froid est corrigé : les trois œufs
+s'affichent avant la réception des composants d'items. Le tirage et l'admission
+restent gérés par le serveur. [Correctif](docs/multiplayer-hello-beta040.md).
+
+**World Options → Allow Commands** pilote les options opérateur Sanctuary.
+Sans commandes, elles sont masquées et refusées par le serveur ; avec commandes,
+les inspections de la carte et des catalogues sont disponibles. Les contrôles
+se mettent à jour dès l'application du réglage.
+
+**Options → Shaders → Sanctuary · Vanilla Light** ajoute un traitement léger des
+couleurs, désactivable. Les étoiles empruntent la passe native du ciel ; la
+compatibilité avec Iris devra être vérifiée lorsque sa version pour notre
+Minecraft sera disponible. [Rendu et limites](docs/shaders-beta040.md).
+
+La fiche Habitant présente le personnage et son familier en 3D, avec un cadrage
+adapté à la largeur, le nom, la bio et le nom personnalisé de l'œuf équipé.
+Le ciel se remplit à partir des premières arrivées, advancements, prestiges
+et premières factions de deux membres. Les étoiles restent après la mort,
+les reconnexions et la dissolution d'une faction. Un même duo ne peut pas
+multiplier les étoiles en recréant sa faction.
+[Contrat et limites](docs/operator-inhabitant-stars-beta040.md).
+
+Build, **50 tests serveur** et parcours client natif réussis, y compris
+l’accueil à froid et le catalogue complet après réouverture. Connexion réseau
+dédiée non testée. Exports : [pack normal](build/Sanctuary-beta.040.mrpack) ·
+[monde plat de test rapide](build/Sanctuary-Test-beta.040.mrpack).
+
+## beta.039 — Hello World, savoir alimentaire et inventaires de mort
+
+Le **Hello World** affiche trois cases d’œufs sous les couleurs, avec une courte
+description au survol. Trois familiers différents sont tirés une seule fois parmi
+les communs, avec **10 % de chance** qu’un peu commun remplace l’un d’eux. La
+reconnexion reprend la création au même stade et garde les mêmes choix. Le choix
+confirmé est définitif ; l’œuf est équipé à l’arrivée, une seule fois.
+[Contrat du familier de départ](docs/hello-starter-beta039.md).
+
+L’aptitude **Connaissance alimentaire** coûte **4 niveaux**. Sans elle, il faut
+avoir goûté un aliment pour voir ses apports dans les infobulles et l’aperçu du
+HUD. Les dégustations attestées par Une alimentation équilibrée sont reprises.
+Le savoir reste après la mort et le prestige. L’indication d’épuisement passe
+sous les sprites de nourriture.
+
+**Options → Captures d’écran → Capture panoramique** transforme la touche de
+screenshot en capture des six faces, avec profondeur et masque de brume
+reconstitué. Résultats dans `screenshots/panoramas/`, sans écraser les captures
+précédentes. Désactiver l’option rétablit les screenshots habituels.
+
+Les nouvelles têtes s’appellent **Pseudo 1**, **Pseudo 2**, etc., selon le compteur
+de morts Minecraft. Le cosmétique de tête utilise le profil du défunt. Leur menu
+affiche l’inventaire au décès à gauche et celui du joueur qui récupère à droite :
+les cases restent à leur place, grisées et accessibles en retrait uniquement.
+L’inventaire actuel conserve son rendu normal ; les deux côtés utilisent les
+textures existantes.
+Les anciennes têtes gardent leur coffre de récupération, faute de disposition
+sauvegardée. [Contrat des inventaires](docs/grave-inventory-beta039.md) ·
+[Capture et savoir](docs/food-panorama-beta039.md).
+
+Build, **68 tests serveur** et parcours client natif réussis.
+Exports : [pack normal](build/Sanctuary-beta.039.mrpack) ·
+[monde plat de test rapide](build/Sanctuary-Test-beta.039.mrpack).
+
+## beta.038 — nourriture, têtes-tombes et factions
+
+Les informations alimentaires inspirées d’AppleSkin sont intégrées au HUD :
+saturation, épuisement, aperçu de la nourriture tenue et icônes en infobulle,
+adaptés aux trois à dix icônes de Hunger. Options → Informations alimentaires
+permet de choisir les indications visibles. Le [guide des textures](ressources-pack/sanctuary/FOOD.md)
+prépare leur remplacement dans le resource pack.
+
+À la mort, les objets sont réunis dans la tête du joueur, pillable par tout le
+monde. Clic droit sur la tête : coffre de récupération à deux pages. La casser
+permet de transporter le contenu ; clic droit dans le vide pour l’ouvrir en main,
+Maj + clic droit sur un bloc pour la reposer. Les cases grisées autorisent
+uniquement le retrait. La tête conserve nom, date, cause et coordonnées de la
+mort. L’XP garde sa règle actuelle.
+
+Fonder sa faction coûte **10 niveaux**, dès le début et sans prestige. Le
+fondateur possède l’unique place de départ ; les charges gagnées par prestige
+financent les places suivantes. Les anciennes factions gardent leurs capacités.
+[Contrat et vérifications](docs/graves-food-beta038.md).
+Build, 55 tests serveur et parcours client natif réussis. Exports vérifiés :
+[pack normal](build/Sanctuary-beta.038.mrpack) ·
+[monde plat de test rapide](build/Sanctuary-Test-beta.038.mrpack).
+
+## beta.037 — temps réel et suivi commun
+
+Le ciel suit l’heure réelle et les saisons. En solo, Sanctuary prend le fuseau
+horaire de la machine et sa ville de référence, sans géolocalisation. Le serveur
+dédié choisit son fuseau dans `config/sanctuary/realtime.json`. Les ticks de jeu
+restent normaux ; dormir en lit ne saute plus la nuit.
+
+Le suivi utilise un seul moteur pour les advancements et les futurs objectifs
+de quête, avec les notifications au même endroit. Le rendu fonctionne aussi
+lorsque F3 est fermé. Les quêtes elles-mêmes restent à développer.
+
+`/sanctuary time` consulte l’heure ; les opérateurs peuvent choisir
+`/sanctuary time vanilla` ou `realtime`, et recharger la configuration avec
+`/sanctuary time reload`. Voir le [contrat et les réglages](docs/realtime-beta037.md).
+
+Exports vérifiés (29 tests serveur et deux parcours client) : [pack normal](build/Sanctuary-beta.037.mrpack) et
+[profil plat rapide](build/Sanctuary-Test-beta.037.mrpack).
+
+## beta.036 — notifications et suivi choisi
+
+Les notifications s’affichent **en haut à droite** par défaut, avec neuf positions
+au choix et un placement qui évite F3, les annonces natives et les barres du HUD.
+Un clic sur un advancement l’épingle ; un second le retire. Les objectifs choisis
+restent suivis après la mort, les découvertes et progrès non épinglés s’effacent.
+
+Porter un animal volant permet de planer ; s’allonger au sol réduit la détection
+visuelle des mobs. Les sorties des recettes apprises par collection deviennent
+recherchables dans JEI sans inventer leur possession dans Discovery.
+[Contrat et vérifications](docs/corrections-beta036.md).
+Build, **36 tests serveur** et deux parcours client natifs réussis.
+Exports : [pack normal](build/Sanctuary-beta.036.mrpack) ·
+[monde plat de test rapide](build/Sanctuary-Test-beta.036.mrpack).
+
+## beta.035 — collections Minecraft
+
+Les **2 042 recettes** de la version sont réparties en **67 collections** :
+66 familles de survie et une référence technique. Les **126 advancements**
+et la découverte de leurs matériaux enseignent les familles correspondantes,
+même avant l’achat du catalogue. Les recettes connues restent conservées.
+Discovery → Recettes affiche les collections en FR/EN ; le fil de découvertes
+annonce les nouveaux chapitres. [Règles et vérifications](docs/collections-beta035.md).
+Le [document de conception](docs/collections-minecraft.md) comporte désormais
+un lien vers chaque définition serveur, et un générateur vérifie sa concordance
+avec l’[inventaire exhaustif](docs/collections-minecraft-inventaire.md).
+Build, **28 tests serveur** et parcours client natif réussis, dont la conservation
+des collections après prestige, mort et redémarrage.
+Exports : [pack normal](build/Sanctuary-beta.035.mrpack) ·
+[pack de test rapide](build/Sanctuary-Test-beta.035.mrpack).
+
+## beta.034 — fil de découvertes et hotbar agrandie
+
+Un fil discret en bas à droite signale les premières découvertes de blocs,
+d’objets et de recettes. Les lots de recettes se regroupent, les messages
+s’effacent et les collections utilisent les barres Minecraft. **Options →
+Notifications** règle leur visibilité, leur durée et le suivi des advancements.
+Les quêtes, bounties et panneaux restent pour un prochain ticket.
+
+Factions et History sont désormais accessibles par **Progression → Prestige**,
+sans leurs boutons supplémentaires dans le menu pause. La hotbar de l’inventaire
+personnel reprend les proportions du HUD et des objets légèrement plus grands.
+[Contrat et vérifications](docs/notifications-beta034.md).
+Exports : [pack normal](build/Sanctuary-beta.034.mrpack) ·
+[pack de test rapide sur monde plat](build/Sanctuary-Test-beta.034.mrpack).
+Build, 22 tests serveur, parcours client natif et intégrité des deux packs vérifiés.
+
+## beta.033 — menus directs et profil de test rapide
+
+Le menu pause ouvre directement Discovery, Progression, World, Inhabitant, Factions et
+History. Discovery commence sur **Owned** ; l’aptitude **Catalogue**, à quatre
+niveaux, ajoute **All Blocks** parmi les blocs déjà découverts. Les accès de
+maintenance et le bouton de configuration JEI sont retirés de ces interfaces.
+La flamme du gâteau porté rejoint la pointe de sa bougie.
+[Contrat des menus](docs/menus-discovery-beta033.md).
+
+Le module facultatif **Sanctuary Test** propose un monde plat natif et passe
+automatiquement la cinématique, tout en gardant la fiche d’habitant et les vrais
+systèmes du jeu. [Utilisation du profil rapide](docs/test-rapide-beta033.md).
+Exports : [pack de test rapide](build/Sanctuary-Test-beta.033.mrpack) ·
+[pack normal](build/Sanctuary-beta.033.mrpack).
+Build, 34 tests serveur et parcours client natifs réussis.
+
+
+## beta.032 — refonte des 88 familiers
+
+Les 88 passifs et actifs disposent de nouvelles valeurs et recharges, avec
+zones de laine, brumes déplaçables, passages volontaires et gestes de travail
+utilisant les vrais outils et stocks. Les limites de soin, d’air, de garde et
+de déplacement se partagent entre les œufs de tous les aidants.
+[Catalogue complet](docs/spawn-eggs-v2-catalogue-beta032.md) ·
+[Migration et vérifications](docs/spawn-eggs-v2-beta032.md).
+Build, 85 tests serveur et parcours client natif validés ; équilibre en partie
+à essayer. Export : [Sanctuary-beta.032.mrpack](build/Sanctuary-beta.032.mrpack).
+
+## beta.031 — apparition galactique
+
+Après **Entrer dans Sanctuary**, les 32 glyphes de l'UUID surgissent un à un
+du noir, avec des mouvements de profondeur, des rémanences et un crescendo
+sonore. Une spirale les entraîne des bords vers un portail qui grandit jusqu’à
+remplir l’écran. Le flash blanc s’efface ensuite sur le spawn.
+Cette apparition atmosphérique dure **29,5 secondes** ; le spawn et `helloworld`
+suivent sa fin. **Échap** permet de la passer. **`/sanctuary intro`** la rejoue
+avec son UUID, sans recréer de personnage.
+[Proposition, admission et vérifications](docs/introduction-beta031.md).
+Export : [Sanctuary-beta.031.mrpack](build/Sanctuary-beta.031.mrpack).
+Build, 9 tests serveur et parcours client natif validés.
+
+## beta.030 — lumières portées et objets jetés
+
+Les mains, les cosmétiques de tête et les objets jetés éclairent le décor,
+y compris chez les autres joueurs. Une bougie sur un gâteau éclaire une fois
+allumée. Les objets proches au sol partagent une source, avec un budget de
+mise à jour et aucun recalcul répété pour une source immobile.
+[Utilisation, réglages et vérifications](docs/dynamic-lights-beta030.md).
+Export d’essai : [Sanctuary-beta.030.mrpack](build/Sanctuary-beta.030.mrpack).
+Build, 21 tests serveur et parcours client natif réussis.
+
+## beta.029 — cri et recul des familiers
+
+Frapper un familier déclenche le cri de son espèce et le repousse avec la physique
+native. Son suivi marque une courte pause pour laisser voir le recul, puis reprend.
+Le familier reste invulnérable ; l’arme ne s’use pas. La caresse garde ses cœurs.
+[Contrat et vérifications](docs/familiar-hit-feedback-beta029.md).
+Export d’essai : [Sanctuary-beta.029.mrpack](build/Sanctuary-beta.029.mrpack).
+Build et 38 tests serveur réussis ; écoute et essai visuel encore à confirmer.
+
+## beta.028 — plongeon jusqu’à collision
+
+Le plongeon conserve sa posture jusqu’au contact du terrain ou de l’eau,
+sans limite de 1,5 seconde. Réappuyer sur la touche ne coupe plus le geste
+en plein vol. Une seule impulsion par saut ; gravité et dégâts de chute conservés.
+[Contrat et vérifications](docs/dive-until-collision-beta028.md).
+Export d’essai : [Sanctuary-beta.028.mrpack](build/Sanctuary-beta.028.mrpack).
+Build et 32 tests serveur réussis ; essai visuel encore à confirmer.
+
+## beta.027 — piles de joueurs
+
+**Maj + clic droit** permet de monter sur un joueur déjà porté. On peut aussi
+grimper avec les personnes que l’on porte. Toute la pile ralentit le joueur
+du dessous : 75 %, 56 %, 42 %… jusqu’à un minimum de 25 % de sa vitesse habituelle.
+[Gestes, règles et vérifications](docs/player-stacks-beta027.md).
+Export d’essai : [Sanctuary-beta.027.mrpack](build/Sanctuary-beta.027.mrpack).
+Build et 27 tests serveur réussis ; essai visuel encore à confirmer.
+
+## beta.026 — cache d’affichage Demeure sur la carte
+
+Les empreintes Demeure utilisent une couche de texture mise en cache. La carte
+ne dessine plus un rectangle par bloc à chaque image ; les lots réseau identiques
+ne reconstruisent plus la couche. Demeure reste activé par défaut, avec le même
+brouillard, les mêmes couleurs et les mêmes règles serveur.
+[Diagnostic et vérifications](docs/demeure-map-performance-beta026.md).
+Export d’essai : [Sanctuary-beta.026.mrpack](build/Sanctuary-beta.026.mrpack).
+Build et 9 tests serveur réussis ; mesure graphique encore à valider sur une
+session Mac déverrouillée.
+
+## beta.025 — barres d’XP des compétences
+
+**B → Progression** : les jauges ASCII deviennent des barres d’expérience Minecraft.
+Le prix du prochain palier est centré sur chaque barre. Elles commencent légèrement
+remplies et deviennent pleines au dernier rang, sans texte « Max ».
+Les six rangées d’inventaire restent le plafond actuel.
+[Affichage et vérifications](docs/skill-xp-bars-beta025.md).
+Export local : [Sanctuary-beta.025.mrpack](build/Sanctuary-beta.025.mrpack).
+24 tests serveur et parcours graphique FR/EN réussis.
+
+## beta.024 — recettes progressives dans le Blocodex
+
+**B → Découverte → Recettes** ouvre le carnet. L’aptitude **Catalogue de recettes**
+coûte **4 niveaux** : les découvertes et les advancements Minecraft enseignent
+les plans avant même cet achat. Une planche révèle les bases du bois ; le premier
+progrès d’alchimie ouvre sa collection. La fabrication manuelle reste libre.
+
+JEI est porté depuis ses sources 26.2 vers la version exacte du pack. Son catalogue
+masque les objets inconnus ; la vue opérateur s’active explicitement. **R / U**
+consultent recettes et utilisations dans l’inventaire. Le bouton **+** de JEI tient
+compte des six rangées et exclut les cases de récupération verrouillées.
+Le catalogue et les recettes sont conservés après prestige, mort et redémarrage.
+[Contrat, collections et limites](docs/recipes-jei-beta024.md).
+
+Le [catalogue proposé des collections Minecraft](docs/collections-minecraft.md)
+relie les 126 advancements à 67 collections et classe les 2 042 recettes de la
+version du pack. Son inventaire couvre aussi les blocs et objets ; cette proposition
+documentaire n’active pas de nouvelles collections en jeu.
+
+Le correctif de chargement beta.022 et les interactions cosmétiques beta.023 sont
+inclus. Export : [Sanctuary-beta.024.mrpack](build/Sanctuary-beta.024.mrpack).
+Client et serveur beta.024 ensemble. 123 tests serveur, 588 288 comparaisons de
+terrain et parcours client natif validés. Livraison locale, non installée dans Prism.
+
+## beta.023 — cosmétiques directement sur leur case
+
+Le grand bouton Cosmetic est retiré. **Clic droit sur la case cosmétique** :
+utiliser une bougie ou un briquet au curseur ; curseur vide pour éteindre,
+récupérer la bougie d’un gâteau ou changer la pose du golem. **Clic gauche**
+retire l’objet. Les huit cases d’équipement ont leur infobulle de rôle.
+Cette livraison intègre aussi l’optimisation du chargement de beta.022.
+
+Les panneaux sont modifiables par les autres joueurs, les bannières gardent
+leurs motifs, les lits et portes sont complets, et les minecarts utilisent leur
+modèle en volume. Toile, feuilles et verre enveloppent la tête. Une TNT portée
+s’amorce sur un coup et explose réellement après quatre secondes.
+[Contrat et vérifications](docs/head-cosmetics-beta021.md).
+
+Export : [Sanctuary-beta.023.mrpack](build/Sanctuary-beta.023.mrpack).
+117 tests serveur et parcours client natif réussis.
+Client et serveur beta.023 ensemble.
+
+## beta.022 — création de monde plus rapide
+
+La génération évite les calculs de bruit dont l’influence est nulle, en conservant
+le même terrain. La comparaison CPU sur les mêmes colonnes mesure **19,66 % de
+calcul en moins** ; la création serveur témoin atteint **89 s** sur Mac M1.
+Cette durée exclut le lancement et le rendu du client. Le détail des mesures,
+de leur portée et des vérifications est dans [STARTUP-01](docs/initial-loading.md).
+Livraison locale, non publiée et non installée dans Prism.
+
+## beta.020 — œufs singuliers, portage et plongeon
+
+Les œufs portent désormais les couleurs de leur rareté et deux noms d’effets
+cryptiques. **Étude des familiers**, dans les aptitudes, coûte **4 niveaux**
+et révèle leurs explications dans l’infobulle native. **Habitant** rassemble
+les réglages d’aide alliée, acceptée par défaut, et le carnet. **G** active
+le pouvoir ; les boutons d’activation dans les menus sont retirés.
+
+Les familiers utilisent de préférence leur modèle bébé. Clic droit : cœurs ;
+clic gauche : réaction de coup, sans dégâts. Nommer l’œuf à l’enclume ou utiliser
+une étiquette sur son compagnon conserve son nom avec l’œuf.
+Chaque œuf reçoit aussi une **taille aléatoire persistante** au premier équipement :
+90 % de tailles ordinaires, 5 % de minuscules et 5 % de géants. La taille reste
+visuelle et ne change pas les pouvoirs.
+
+**Maj + clic droit** porte un animal ou fait monter sur la tête d’un joueur.
+Refaire le geste dépose le passager ; le joueur porté relâche Maj puis rappuie
+pour descendre. Le porteur est ralenti de 25 %, et les coups entre les deux sont
+bloqués. **Saut en sprint puis W**, ou la touche S’allonger configurée, déclenche
+un plongeon horizontal.
+
+Les détections révèlent leurs repères derrière les murs ; les téléportations
+Endermite/Enderman peuvent les traverser vers une arrivée libre. Le freinage
+de chute de la poule efface la distance dangereuse accumulée auparavant.
+[Contrat, tailles et vérifications](docs/familiar-interactions-beta020.md).
+
+Export : [Sanctuary-beta.020.mrpack](build/Sanctuary-beta.020.mrpack).
+Client et serveur beta.020 ensemble. Contrôles purs, 104 tests serveur,
+parcours client et rendu des 88 modèles aux tailles extrêmes validés.
+L’équilibrage en partie réelle reste à poursuivre.
+
+## beta.019 — pouvoirs et déplacements des familiers
+
+Les **88 familiers** ont leur passif et leur actif, répartis entre commun,
+peu commun, rare, extraordinaire et légendaire. Équiper un œuf et attendre
+10 secondes active son passif. **B → Progression → Aptitudes → Lien actif**
+coûte **4 niveaux** ; **G**, configurable, utilise le pouvoir. Le panneau
+Familier affiche les effets, la recharge et le consentement aux aides.
+
+Les volants utilisent une navigation aérienne, le Dragon décrit une orbite,
+les aquatiques nagent et se débattent au sol. Le rendu noirci est corrigé.
+Les familiers volants deviennent progressivement transparents à proximité
+du joueur ou de la caméra, corps et yeux compris.
+
+La recharge commune et les récupérations soin/air survivent à la mort et au
+prestige. Ressources, cibles et transactions restent contrôlées par le serveur.
+Les rangs I–III, œufs Anomaly et leur obtention en survie restent à concevoir.
+L’équilibrage en partie réelle reste à éprouver : [contrat et couverture](docs/companion-powers-beta019.md).
+
+Export : [Sanctuary-beta.019.mrpack](build/Sanctuary-beta.019.mrpack).
+Contrôles purs, 91 tests serveur et parcours client natif validés.
+
+## beta.018 — familiers, cape et cosmétique de tête
+
+L’inventaire personnel reçoit trois cases : **œuf de familier**, **cape** et
+**cosmétique de tête**, avec les trois icônes fournies par le créateur. Son titre
+ne comporte plus de compteur de rangées. Maj-clic équipe un œuf ou une cape ;
+un objet de tête se place volontairement et n’ajoute aucun effet d’équipement.
+Un piston porté s’étend brièvement quand le joueur subit des dégâts.
+
+Le familier suit réellement le joueur, contourne les obstacles et revient par
+téléportation vers une place libre quand nécessaire. Il est invincible, sans
+collision avec les entités. Clic droit sur son propre compagnon pour le caresser :
+particules de son œuf et petits cœurs. Les pouvoirs feront l’objet d’un autre ticket.
+**Cape Zéro** est disponible en créatif ou avec `/give @s sanctuary:zero_cape` ;
+son visuel provisoire est la Vanilla Cape, avec [provenance](THIRD_PARTY_NOTICES.md).
+
+**W : s’allonger**, gratuit dès le départ. **Maj + W : se reposer**, aptitude
+à 4 niveaux par défaut, utilise la pose de sommeil des lits. Le repos conserve
+son échange vie/faim et ne fait pas passer la nuit. Les touches se configurent.
+Les emplacements sont, de haut en bas : cosmétique, cape, œuf, main secondaire.
+**Maj + clic gauche maintenus** transfèrent les cases parcourues vers le
+conteneur ouvert, avec les règles natives de chaque machine.
+**Tab / Maj + Tab** changent aussi la rangée de hotbar pendant le jeu, sans
+ouvrir l’inventaire. Les objets restent dans leurs cases et la touche se configure.
+Le minage et la construction groupés verrouillent la sélection au premier clic :
+le regard reste libre pendant le maintien. Relâcher permet de choisir un nouveau
+groupe ; le plan et l’orientation de construction conservent leur état initial.
+[Contrat et vérifications beta.018](docs/companions-cosmetics-beta018.md).
+Export local vérifié : [Sanctuary-beta.018.mrpack](build/Sanctuary-beta.018.mrpack).
+Contrôles purs, 67 tests serveur et parcours client natif validés.
+
+## beta.017 — Nage, S’allonger, Se reposer et fabrication manuelle
+
+Trois aptitudes rejoignent la liste du Blocodex, **4 niveaux chacune** par défaut :
+**Nage** autorise le sprint aquatique ; **S’allonger** utilise **W** pour basculer
+la posture basse ; **Se reposer** utilise **Maj + W** pour récupérer un demi-cœur
+toutes les 10 secondes immobiles contre une demi-barre de faim. Bouger ou subir
+des dégâts arrête le repos. La touche se configure dans Contrôles ; V reste libre
+pour le vocal. Les trois aptitudes sont conservées au prestige.
+Les compétences affichent le prix du prochain rang à côté de leur jauge ASCII ;
+le ratio redondant est remplacé par `4 niv.` ou `Max.`.
+Chaque récompense des Premiers pas s'accompagne du son vanilla de gain d'XP.
+
+Sans Nage, on peut avancer lentement dans l'eau et remonter. La nage rapide suit
+la faim nécessaire au sprint. Les prix se règlent par `swimmingCost`, `lyingCost`
+et `restingCost` dans `config/sanctuary/progression.json`.
+
+Le livre natif est retiré des inventaires, établis et fours ; la fabrication
+manuelle reste disponible. L'intégration de JEI révélant les recettes selon les
+advancements Minecraft est un chantier suivant. Les récompenses du tutoriel et
+les textures beta.016 restent incluses.
+[Contrat de migration et vérifications](docs/swimming-recipebook-beta017.md).
+Export local vérifié : [Sanctuary-beta.017.mrpack](build/Sanctuary-beta.017.mrpack).
+Contrôles purs, 62 tests serveur puis 15 tests ciblés finaux et parcours client
+natif validés ; sons d'XP, clavier AZERTY et fabrication manuelle vérifiés.
+
+## beta.016 — les premiers gestes rapportent de l’XP
+
+Se déplacer et regarder, trouver un arbre, miner une bûche, ouvrir son inventaire
+et fabriquer des planches donnent **8 points d’XP chacun**, une seule fois par
+habitant dans le monde. Le premier geste suffit à acheter un premier rang.
+Les étapes restent terminées après la mort et le New Game+ ; les points non
+dépensés suivent les règles de mort existantes. Message personnel FR/EN à chaque gain.
+
+`config/sanctuary/tutorial.json` permet de régler `enabled` et `xpPerStep`
+au redémarrage du serveur. [Contrat et vérifications](docs/tutorial-xp-beta016.md).
+Les six nouvelles icônes beta.015 sont incluses ; la respiration du HUD reste vanilla.
+Export local : [Sanctuary-beta.016.mrpack](build/Sanctuary-beta.016.mrpack).
+57 tests serveur, contrôles purs et parcours client natif validés.
+
+## beta.015 — icônes de progression communes
+
+Les six icônes du Blocodex utilisent les nouveaux PNG 9 × 9 du créateur.
+Les mêmes textures sont présentes dans les sources du resource pack Sanctuary,
+avec les mêmes identifiants. [Correspondance et vérifications](docs/progression-textures-beta015.md).
+Export local : [Sanctuary-beta.015.mrpack](build/Sanctuary-beta.015.mrpack).
+
+## beta.014 — Rangement, hotbar mobile et préfixe de faction
+
+**B → Progression → Aptitudes → Rangement** débloque le tri pour **4 niveaux**
+par défaut. Le clic molette sur une case de stockage regroupe les piles et range
+les rangées débloquées, jusqu'aux six rangées de l'inventaire. La hotbar garde
+son organisation ; la viser permet de ranger uniquement ses neuf cases.
+La touche se configure dans **Contrôles → Sanctuary → Ranger la zone d'inventaire visée**.
+
+Les cases de récupération, l'équipement, la fabrication et le contenu des
+coffres restent à leur place.
+Le prix se règle avec `sortingCost` dans `config/sanctuary/progression.json`.
+[Contrat de migration et vérifications](docs/inventory-sorting-beta014.md).
+
+**Tab** fait monter le cadre de la hotbar, **Maj+Tab** le fait descendre.
+Les objets restent dans leurs cases et la rangée active devient celle utilisée
+en jeu. La molette suit la zone survolée. La sélection est conservée à la
+reconnexion ; le rangement tient compte de cette rangée active.
+
+La faction précède le pseudo : **Les Arpenteurs · Poupoutain I**, dans la fiche
+Habitant et les noms affichés. Son préfixe suit l'appartenance et sa couleur ;
+le prestige conserve ses chiffres romains. Les descriptions d'aptitudes ne
+répètent plus la règle globale de conservation au New Game+.
+
+Export local vérifié : [Sanctuary-beta.014.mrpack](build/Sanctuary-beta.014.mrpack).
+
+
+## beta.013 — Progression, prestige et repères visuels
+
+Chaque compétence rassemble sur une ligne son icône, son nom, la jauge ASCII,
+la valeur/plafond et les boutons d'achat/retrait. Les aptitudes gardent leur
+colonne défilante. La courbe par défaut devient **1, 2, 4, 8, 16, 32, 48 niveaux** ;
+les prix personnalisés et les remboursements des anciens achats sont conservés.
+
+Pour passer en prestige : **B → Progression → Cycle**, après avoir maximisé
+les six compétences, puis confirmer le New Game+. Le premier passage donne
+par défaut **1 prestige et 1 charge**. **B → Monde → Factions** permet alors
+de fonder une faction de deux places ; les invités n'ont pas besoin de prestige.
+Cette page explique désormais le parcours et propose un accès direct à Cycle.
+
+La fiche Habitant affiche le prestige en chiffres romains après le nom. Après
+le New Game+, les cinq rangées de récupération sont entièrement grisées quand
+elles restent visibles, même dans les coffres et même pour les cases vides.
+
+La carte conserve les relevés reçus pendant les déplacements et actualise son
+image sans effacement cyclique. Le point personnel reprend le sprite natif du
+locator. Les marqueurs des joueurs, teintés avec les couleurs des habitants,
+se superposent à la barre d’XP, dont le fond et le remplissage restent visibles.
+
+La molette suit le survol : case sélectionnée sur la hotbar, rotation de rangée
+sur le stockage débloqué, échanges sur les offres du villageois. Tab/Maj+Tab
+restent disponibles.
+
+Contrat et vérifications : [beta.013](docs/progression-inline-beta013.md).
+Export local vérifié : [Sanctuary-beta.013.mrpack](build/Sanctuary-beta.013.mrpack).
+
+## beta.012 — Inventaire et progression lisible
+
+Les coffres et les machines alignent leur fond, leurs commandes et leurs cases.
+Le stockage suit l'ordre des rangées, avec la barre rapide en bas. **Tab** fait
+circuler les rangées débloquées et **Maj+Tab** inverse le sens ; la touche se règle
+dans les contrôles. Si E est choisie, **Échap** ferme l'inventaire.
+
+Maj-clic regroupe les piles sur toutes les rangées avant de remplir une case vide.
+Les rangées supplémentaires suivent les priorités natives des machines ; un coffre
+plein laisse les objets à leur emplacement. Les six compétences affichent une
+jauge et leur plafond. **+** apparaît quand l'achat est possible ; **−**, réservé
+aux opérateurs, rembourse le prix réellement payé pour le dernier rang retiré.
+
+Le [contrat beta.012](docs/inventory-flow-beta012.md) décrit les données conservées
+et la migration des achats. Export local vérifié :
+[Sanctuary-beta.012.mrpack](build/Sanctuary-beta.012.mrpack).
+
+## beta.011 — Factions, prestige et New Game+
+
+**B → Progression → Cycle** montre les six plafonds, les compétences restantes
+et le bilan avant confirmation. Toutes les compétences au maximum permettent
+un New Game+ ; les aptitudes ne sont pas un prérequis. Le passage remet les six
+rangs et, par défaut, l'XP à zéro. Identité, aptitudes, objets, découvertes,
+Atlas, Demeure, statistiques, exploits et faction restent acquis. Les anciennes
+rangées pleines restent accessibles en récupération.
+
+Chaque passage donne **1 prestige et 1 charge personnelle**. Dans **B → Monde
+→ Factions**, une charge fonde une faction de deux places, puis chaque membre
+peut financer une place supplémentaire pour une charge. Invitations, départs,
+exclusions, transmission et dissolution sont enregistrés côté serveur. La
+couleur de faction est celle du fondateur au moment de sa création ; les
+couleurs personnelles ne changent pas. Les factions ne protègent pas les chunks.
+
+Le [contrat commun et la migration](docs/cycle-factions-beta011.md) précisent
+les données conservées, les prix configurables et la reprise après interruption.
+Les prochaines étapes sont les claims et leur contrat de protection.
+Export local : [Sanctuary-beta.011.mrpack](build/Sanctuary-beta.011.mrpack).
+
+## beta.010 — Inventaire jusqu'à six rangées
+
+La sixième compétence devient achetable : **une à six rangées de neuf cases,
+barre rapide comprise**. Cinq achats ajoutent chacun une rangée, pour un
+plafond de 54 cases hors équipement. Les fonds d'inventaire et de conteneurs
+proviennent de la 26.2 et suivent la capacité du joueur.
+
+Les anciennes cases occupées restent visibles et permettent de retirer leurs
+objets, sans y déposer à nouveau tant que leur rangée n'est pas débloquée.
+Un astérisque dans le titre signale ces cases de récupération. Les grands
+conteneurs font défiler leurs rangées à la molette lorsque l'écran manque de
+hauteur ; la barre rapide reste visible. Le catalogue créatif propose un
+bouton **Inventaire** pour ouvrir les rangées personnelles.
+
+Les achats, sauvegardes, transferts, fabrication et règles de mort sont décrits
+avec les vérifications dans [le contrat beta.010](docs/inventory-beta010.md).
+Export local : [Sanctuary-beta.010.mrpack](build/Sanctuary-beta.010.mrpack).
+
+## beta.009 — Construction et vein building
+
+**Construction** est achetable dans les Compétences du Blocodex. Sept rangs
+agrandissent les surfaces de **3×3 à 15×15**, avec un aperçu vert. La face visée
+détermine le sol, le mur ou le plafond ; le serveur pose du centre vers les bords.
+
+Avec un bloc en main, **R** affiche l'aperçu, **R + clic droit maintenus** pose
+la surface et **R + molette** règle sa taille. **R + clic gauche** reste le
+minage ; la touche commune se configure dans les contrôles. Relâcher arrête
+le geste en conservant ce qui est déjà construit.
+
+En survie, chaque pose consomme un bloc de la pile tenue, tous les deux ticks.
+Les collisions, orientations, permissions, statistiques et contributions Demeure
+suivent les placements natifs. Portes, lits, plantes doubles, échafaudages et nénuphars gardent la pose
+individuelle. Les anciennes progressions conservent leurs achats, puis commencent
+Construction au rang zéro : [contrat et vérifications](docs/building-beta009.md).
+Export local : [Sanctuary-beta.009.mrpack](build/Sanctuary-beta.009.mrpack).
+
+## beta.008 — Minage et vein mining
+
+**Minage** est achetable dans la colonne Compétences du Blocodex. Ses sept
+rangs augmentent la vitesse de 100 à 200 % et la taille des veines jusqu'à
+256 blocs. Maintenir **R** affiche les blocs identiques reliés par leurs faces ;
+maintenir **R + clic gauche** les mine. **R + molette** choisit une taille plus
+petite parmi les rangs acquis. La touche se règle dans les contrôles.
+
+La durée dépend des blocs et de l'outil ; les objets, l'XP, l'usure, la faim
+et les enchantements suivent Minecraft. Relâcher, changer d'outil ou ouvrir
+le Blocodex interrompt le geste. Les rangs survivent à la mort et à la reprise.
+Les progressions beta.003–007 sont reprises selon le
+[contrat de migration et les vérifications](docs/mining-beta008.md).
+Export local : [Sanctuary-beta.008.mrpack](build/Sanctuary-beta.008.mrpack).
+
+## beta.007 — carte immersive
+
+La carte s'étend à tout l'écran, derrière les commandes du Blocodex. Un carré
+central reste clair, avec des abords sombres et le fond du jeu visible là où
+aucune surface n'est dessinée. Coordonnées et zoom se superposent en bas à
+gauche ; les boutons gardent la priorité sur le déplacement de la carte.
+La grille et les empreintes Demeure accompagnent le terrain jusqu'aux bords.
+
+Les relevés existants sont conservés, sans changement des sauvegardes ou du
+terrain. Voir [le ticket et les vérifications](docs/atlas-immersive-beta007.md).
+Export local : [Sanctuary-beta.007.mrpack](build/Sanctuary-beta.007.mrpack).
+
+## beta.006 — brouillard, grille et Demeure
+
+Dans **B → Monde → Grande carte**, le glisser-déplacer utilise les boutons
+souris natifs de 26.3 et reste disponible pendant les rafraîchissements.
+Le terrain apparaît progressivement sur un parchemin ; les découvertes
+beta.005 restent acquises. Les boutons **Chunks** et **Demeure** activent
+la grille et les empreintes d'habitation. Survoler une zone connue affiche
+son état et son influence principale, avec la couleur de l'habitant.
+
+Demeure est un mod autonome embarqué. Les traces utilisent des jours réels
+et persistent après redémarrage. **Aucun claim ni protection de chunk** dans
+ce premier lot. Contrat et validation : [Demeure beta.006](docs/demeure-beta006.md).
+Export local : [Sanctuary-beta.006.mrpack](build/Sanctuary-beta.006.mrpack).
+
+## beta.005 — carte native et noms de couleurs
+
+**B → Monde → Grande carte** ouvre une carte de surface avec déplacement,
+zoom et recentrage. L'aptitude coûte 4 niveaux par défaut ; les relevés
+personnels sont conservés avant son achat. La vue opérateur, explicitement
+sélectionnée et autorisée par le serveur, inspecte le terrain déjà chargé.
+Mini-carte, marqueurs et partage entre habitants restent à développer.
+
+Les couleurs de création portent des noms associés aux teintes (Azur,
+Pamplemousse, Sauge…), dans des boutons Minecraft au texte coloré.
+S'asseoir disparaît des aptitudes et du passeport et reste disponible par le geste.
+
+Les achats beta.003/004 sont repris selon [le contrat de migration](docs/atlas-beta005.md).
+Export local : [Sanctuary-beta.005.mrpack](build/Sanctuary-beta.005.mrpack).
+
+## beta.004 — cadre Blocodex et sprint
+
+Le titre et les onglets **Discovery, Progression, Story, Inhabitant** restent
+en haut ; **Done / Terminé** reste centré en bas. Les découvertes partagent
+ce cadre, et les textes de la fiche et de l'histoire peuvent défiler.
+Les six compétences affichent chacune l'icône 9×9 fournie par le créateur.
+Le sprint exige à nouveau **plus de trois icônes de nourriture**, comme en
+vanilla : acheter Faim puis manger permet de franchir ce seuil.
+
+Les profils et mondes beta.003 se conservent. Voir
+[le ticket et ses vérifications](docs/blocodex-beta004.md).
+Export local : [Sanctuary-beta.004.mrpack](build/Sanctuary-beta.004.mrpack).
+
+## beta.003 — créer son habitant et commencer la progression
+
+Première fiche avant l'arrivée : skin, pseudo, bio et couleur personnelle.
+Le Blocodex (B ou Échap) réunit la fiche, les améliorations, l'histoire et les
+connaissances de blocs. Départ à **3 cœurs, 3 icônes de nourriture, 3 bulles** ;
+les achats restent après la mort. **Noms des mobs** est la première aptitude,
+avec les familles de 26.2 et des noms personnalisables par type côté serveur.
+S'asseoir est disponible dès le début.
+
+Tester dans un **nouveau monde Sanctuary**. L'adoption d'une partie existante
+est explicite et ne touche pas au terrain. Voir les
+[notes beta.003 et la configuration](docs/progression-beta003-release.md).
+Export local d'essai : [`Sanctuary-beta.003.mrpack`](build/Sanctuary-beta.003.mrpack).
+Le parcours de progression passe les essais natifs serveur et client ; la
+suite générale conserve les deux échecs worldgen détaillés dans les notes.
+
+## beta.002 — biomes boréaux, corail et temples
+
+Les quatre îles de 512 blocs conservent leurs noms et positions. Au nord,
+**la Veille de Noor** devient boréale : taïga, neige et biomes de glace partagent
+les Peaks. À l’ouest, le bassin des **Eaux d’Ari** utilise l’océan chaud et sa
+décoration de corail. Une pyramide du désert est réservée dans **la Faille de
+Kai et Efe**, et un temple de la jungle dans **les Braises de Makena** ; leurs
+emplacements dépendent de la graine et des terrains capables de les accueillir.
+
+**Nouveau monde Sanctuary requis pour cette disposition.** Les sauvegardes
+beta.001 restent sur leur génération. Voir [le contrat et les vérifications](docs/expeditions-beta002.md).
+Export local d’essai : `build/Sanctuary-beta.002.mrpack`.
+
+## beta.001 — les quatre anciennes expéditions
+
+Le mod et le pack adoptent la même version : **`beta.001`**.
+Chaque livraison suivante incrémente le compteur : `beta.002`,
+`beta.003`, etc. Les tags reprennent la version exacte, sans `v`.
+Voir [la convention et les vérifications](docs/versioning.md).
+
+Un **nouveau monde Sanctuary** ouvre quatre îles de 512 blocs, témoins des
+anciennes expéditions : **la Veille de Noor** au nord (glacial/Peaks), **les
+Braises de Makena** à l’est (tropical/Volcan), **la Faille de Kai et Efe** au sud
+(aride/Canyon) et **les Eaux d’Ari** à l’ouest (humide/Océan).
+
+Les régions sont enregistrées ensemble dès la création ; leurs chunks se
+génèrent à l’exploration. Le placement cherche la première position libre sur
+l’axe demandé, avec les emprises de décoration et les bateaux protégés.
+Le relief de l’île initiale reste celui de l’alpha.30.7. Les anciennes
+sauvegardes gardent leur contrat et ne reçoivent pas ces îles automatiquement.
+Voir [le contrat et les vérifications](docs/expeditions-beta001.md).
+
+Les sources bêta sont disponibles dans Git jusqu’à beta.060 ; la dernière
+release du canal packwiz reste l’alpha.30.7 ci-dessous. Les sections alpha
+constituent l’historique.
## Alpha.30.7 — trésors aériens et vestiges asséchés
@@ -668,7 +1586,7 @@ sera une mise à jour explicite, avec vérification des API et des sauvegardes.
| Fabric API | 0.160.0+26.3 |
| Fabric Loom | 1.17.20 |
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
-| Sanctuary / pack | 0.1.0-alpha.30.4 |
+| Sanctuary / pack | beta.060 |
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
@@ -697,7 +1615,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
-- `mods/sanctuary/build/libs/sanctuary-0.1.0-alpha.30.4.jar` : mod à installer avec
+- `mods/sanctuary/build/libs/sanctuary-beta.060.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).
@@ -737,6 +1655,7 @@ sauvegarde 26.2 : aucun outil de migration de ses chunks n'est livré ici.
| Chemin | Responsabilité |
| --- | --- |
| `mods/sanctuary/` | Code, ressources et tests du mod Sanctuary |
+| `mods/sanctuary-test/` | Module facultatif pour tester sur monde plat sans cinématique |
| `packwiz/` | Manifeste source, dépendances distantes et hashes |
| `ressources-pack/` | Sources graphiques existantes et futurs resource packs |
| `shaders-pack/` | Sources et réglages de shaders à venir |
@@ -745,8 +1664,10 @@ sauvegarde 26.2 : aucun outil de migration de ses chunks n'est livré ici.
| `.gitea/workflows/` | Vérification sur un runner Gitea compatible |
It's Alive !, Only Fun et Master Key restent des modules autonomes prévus par la
-vision. Leurs sources 26.2 ne sont pas copiées dans ce socle. Fabric API est la
-seule dépendance de gameplay actuellement distribuée avec le mod.
+vision. Leurs sources 26.2 ne sont pas copiées dans ce socle. Fabric API est
+requise ; Demeure et le port expérimental de JEI pour 26.3-pre-2 sont imbriqués
+dans le JAR Sanctuary. Leurs sources ou leur procédure de reconstruction et
+leurs attributions figurent dans `mods/demeure/` et `mods/jei/`.
## Travailler par tickets
@@ -755,10 +1676,9 @@ un petit incrément, on le vérifie en jeu si nécessaire, puis on ajuste avec
le ticket suivant. Pour le terrain, joindre la seed et les coordonnées aide
à retrouver exactement le problème. Voir [Contribuer](CONTRIBUTING.md).
-Le [backlog initial](docs/backlog.md) contient des propositions locales, pas des
-issues déjà publiées. Le prochain travail après ce socle est le réglage de
-l'île, puis le placement de continents et leur ouverture sans écraser les
-constructions des joueurs.
+Le [backlog](docs/backlog.md) contient des tickets locaux, pas des issues déjà
+publiées. La prochaine étape est une passe courte de vérification et de finition
+des expansions, puis la progression en jeu, par petits incréments jouables.
## Licence et crédits
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index f80fcde..90abc95 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -4,6 +4,18 @@ Le code Sanctuary est distribué sous GPL-3.0-or-later, comme la version 26.2 do
la génération est reprise. Voir `LICENSE` et `docs/migration-26.2.md` pour la
provenance détaillée et le périmètre du portage.
+Le minage groupé beta.008 reprend les paliers, la sélection par faces et le
+contour de sélection de Sanctuary 26.2 (GPL-3.0-or-later, même dépôt historique
+en lecture seule). Le service de progression, le contrôle des gestes et le
+calcul de durée sont adaptés à la bêta et aux API Minecraft 26.3-pre-2.
+Voir `docs/mining-beta008.md` ; aucun mod externe de minage n'est embarqué.
+
+Construction beta.009 adapte le plan orienté `SanctuaryBuildPlane` et les
+principes de sélection/session de `gameplay/building` dans le même Sanctuary
+26.2, GPL-3.0-or-later. Les contextes de placement, permissions, gestes et
+migrations sont adaptés à la bêta et testés sur Minecraft 26.3-pre-2.
+Voir `docs/building-beta009.md`.
+
Minecraft appartient à Mojang Studios / Microsoft. Sanctuary est un projet
communautaire indépendant. Le dépôt ne redistribue pas le jeu.
@@ -21,6 +33,16 @@ Fabric Loader : Apache-2.0. Fabric API : Apache-2.0. Fabric Loom et le wrapper
Gradle : Apache-2.0. Ces projets conservent leurs auteurs, notices et licences.
Les dépendances sont résolues depuis leurs distributions officielles.
+Les six icônes de compétences dans
+`assets/sanctuary/textures/gui/progression/` ont été fournies par le créateur
+le 13 septembre 2026. Depuis beta.015, elles utilisent ses nouveaux fichiers
+`progression_heart.png`, `progression_food.png`, `progression_mining.png`,
+`progression_building.png`, `progression_breath.png` et `progression_inventory.png`.
+Les PNG 9×9 sont conservés octet pour octet, y compris leur transparence.
+Les noms de ressources restent stables : `food` correspond à `hunger`,
+`breath` à `breathing`, et le préfixe `progression_` est retiré.
+Les mêmes PNG sont préparés dans `ressources-pack/sanctuary/` pour le resource pack.
+
Le fichier préexistant `ressources-pack/helloworld/assets/minecraft_title.png`
est conservé comme source graphique fournie par le propriétaire du dépôt.
Il n'est pas encore installé dans le pack généré. Sa provenance et son adaptation
@@ -29,3 +51,69 @@ Il n'est pas encore installé dans le pack généré. Sa provenance et son adapt
Les resource packs, shaders et mods communautaires envisagés dans la vision
ne sont pas inclus automatiquement. Chaque ajout aura une version, une source,
un hash et les crédits de sa distribution.
+
+Demeure est embarqué comme mod autonome, sous GPL-3.0-or-later. Son modèle
+provient des sources de KOKA99CAB dans `Structures/demeure`, référencées par
+l’inventaire 26.2, et a été adapté à Minecraft 26.3-pre-2. Voir
+`mods/demeure/PROVENANCE.md` pour les empreintes et les différences du port.
+Le JAR Demeure contient sa licence et ce document de provenance.
+
+## Inventaire beta.010
+
+Les interfaces, algorithmes de transfert et PNG d'inventaire proviennent de
+`../26.2/sanctuary`, projet Sanctuary de Koka sous GPL-3.0-or-later. Le portage
+conserve les six variantes personnelles, la barre rapide et sa sélection,
+les panneaux des conteneurs natifs et leurs fonds supérieurs. Les PNG sont
+copiés sans retouche dans le namespace `sanctuary` ; le serveur 26.2 et ses
+sauvegardes ne sont pas modifiés. Cape, œuf de compagnon et moteur Aircraft
+ne font pas partie de ce lot.
+
+## Équipement beta.018
+
+Les repères de cases vides `egg.png`, `cape.png` et `cosmetics.png` (16 × 16)
+proviennent des fichiers fournis par le créateur le 13 septembre 2026 dans
+`../textures/`. Ils sont conservés octet pour octet dans le mod et le resource pack.
+
+Cape Zéro utilise temporairement le visuel **Vanilla Cape** de Mojang/Microsoft,
+téléchargé depuis le serveur officiel de textures Minecraft :
+https://textures.minecraft.net/texture/f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4
+SHA-256 : `f9a76537647989f9a0b6d001e320dac591c359e9e61a31f4ce11c88f207f0ad4`.
+Présentation originale : https://www.minecraft.net/en-us/article/introducing-vanilla-cape
+L’icône d’objet correspondante provient de `../26.2/sanctuary`
+(`textures/item/cape/vanilla_cape.png`). Ces images sont un placeholder de test,
+restent la propriété de leurs auteurs et ne sont pas placées sous la licence
+du code Sanctuary. Il ne s’agit pas d’une cape officielle accordée au compte.
+
+Les familiers utilisent les modèles et textures résolus par Minecraft installé ;
+aucune copie des textures de mobs ni aucun ancien pouvoir n’est embarqué.
+
+## JEI beta.024
+
+Just Enough Items, mezz, MIT : fork source de la branche 26.2, commit
+`aae2dfcfb82e6b5bce787ba72bb2eda7339b274f`, adapté à Minecraft 26.3-pre-2.
+Le JAR contient la licence d’origine et [la provenance du portage](mods/jei/PROVENANCE.md).
+Les patches et la reconstruction sont fournis ; les téléchargements restent ignorés.
+Baked Substring Index 0.1.0 (MIT, James Mitchell) et Suffix Tree 1.1.0
+(Apache-2.0, Alessandro Bahgat Shehata) sont inclus avec leurs licences.
+
+## Références solaires beta.037
+
+Les villes de référence et leurs coordonnées publiques proviennent de `zone.tab`
+et les alias de `backward`, dans IANA tzdata **2026d** (domaine public).
+Archive : https://data.iana.org/time-zones/releases/tzdata2026d.tar.gz
+SHA-256 : `0cb2aa8e333c3dc049badc42a0c61f21987b8cd44e107fa900bad764aacc7767`.
+Licence : https://data.iana.org/time-zones/tzdb/LICENSE
+Le script `scripts/compile_solar_references.py` produit les 549 références/alias
+embarqués. Le jeu ne télécharge ni ces données ni une position utilisateur.
+Les équations saisonnières approximatives suivent la fiche publique NOAA :
+https://gml.noaa.gov/grad/solcalc/solareqns.PDF
+
+## Informations alimentaires beta.038 — AppleSkin
+
+L’asset `assets/sanctuary/textures/gui/food_details.png` est une copie intacte
+de `resources/assets/appleskin/textures/icons.png` par squeek502 et contributeurs,
+[AppleSkin, branche 26.2-fabric](https://github.com/squeek502/AppleSkin/tree/62513191f6a3497447595c2215d466ad1d2bdb92),
+commit `62513191f6a3497447595c2215d466ad1d2bdb92`, sous Unlicense.
+Le comportement des contours et leurs coordonnées d’atlas suivent cette source ;
+l’intégration, la synchronisation et le calcul de capacité sont propres à Sanctuary.
+Texte de licence : [Unlicense amont](https://github.com/squeek502/AppleSkin/blob/62513191f6a3497447595c2215d466ad1d2bdb92/LICENSE).
diff --git a/archives/web/README.md b/archives/web/README.md
new file mode 100644
index 0000000..ae8c73a
--- /dev/null
+++ b/archives/web/README.md
@@ -0,0 +1,50 @@
+# Archives pour le site Sanctuary
+
+Ce dossier conserve les références réutilisables pour le site et le Galactium.
+Il est indépendant de `build` : nettoyer les builds ne supprime pas les
+collections placées ici. Aucun contenu n'est publié automatiquement.
+
+- [Maquette Blocodex autonome](blocodex-navigation-v1/index.html) : ouvrir
+ ce fichier dans un navigateur, même hors ligne. Copier son dossier suffit.
+- [Source de la maquette](blocodex-navigation-v1/fragment.html) et
+ [guide de réutilisation](blocodex-navigation-v1/README.md).
+- [ZIP léger de la maquette](snapshots/Sanctuary-Blocodex-2026-09-13-v1.zip)
+ (10 188 octets), avec HTML autonome et source éditable.
+- [Collection du 13 septembre 2026](snapshots/2026-09-13-web-v1/index.html) :
+ catalogue des images, PDF, documents, atlas et maquette.
+- [Archive ZIP complète](snapshots/Sanctuary-Web-Archive-2026-09-13-v1.zip).
+- [Inventaire et empreintes](catalog-2026-09-13-web-v1.json).
+
+Collection initiale : **782 fichiers**, ZIP de **196 242 020 octets**.
+Copie vérifiée par SHA-256, intégrité ZIP et empreintes de chaque membre
+contrôlées ; les trois manifestes originaux des atlas passent également.
+Reçu : [receipt-2026-09-13-web-v1.json](receipt-2026-09-13-web-v1.json).
+
+Les collections volumineuses et ZIP sont conservés localement dans
+`snapshots/`, ignoré par Git. Les sources de la maquette, les notices, le
+script et le catalogue peuvent être versionnés. Une copie locale durable
+ne remplace pas une sauvegarde sur un autre support.
+
+Le fonds alpha réunit les images et PDF retrouvés dans les sorties du dépôt
+jusqu'à alpha.30.7 et les trois atlas complets alpha.30.5, 30.6 et 30.7.
+Leur provenance distingue artefacts livrés, essais intermédiaires et documents
+de conception. Une image de recherche n'est pas une preuve de fonctionnalité
+livrée. Les sources originales restent intactes et les doublons conservent
+leurs chemins pour ne pas perdre leur contexte.
+
+La collection inclut aussi les captures beta.003/004, les six icônes de
+compétences et la maquette validée du Blocodex, dans des catégories séparées.
+Les documents sont une photographie au jour de l'archivage ; les archives
+atlas originales conservent en plus leur documentation historique exacte.
+
+Une version de collection est immuable : le script refuse de l'écraser.
+Pour une nouvelle collecte, choisir un autre identifiant :
+
+```sh
+python3 scripts/archive_web_assets.py --id AAAA-MM-JJ-web-v2
+```
+
+Le catalogue JSON contient les chemins relatifs, sources, catégories, versions
+déduites des noms, tailles et SHA-256, pour une future ingestion par le site.
+La présence d'un fichier dans l'archive ne change pas sa licence : conserver
+les notices et les crédits, notamment pour les contenus issus de Minecraft.
diff --git a/archives/web/blocodex-navigation-v1/README.md b/archives/web/blocodex-navigation-v1/README.md
new file mode 100644
index 0000000..a1abc09
--- /dev/null
+++ b/archives/web/blocodex-navigation-v1/README.md
@@ -0,0 +1,30 @@
+# Blocodex — référence interactive v1
+
+Conception et navigation approuvées par le créateur le 13 septembre 2026,
+après beta.004. La maquette contient des données fictives.
+
+Ouvrir `index.html` dans un navigateur. Elle fonctionne sans Codex, sans
+serveur, sans compte, sans connexion et sans dépendance téléchargée.
+Les interactions sont en mémoire : recharger revient à l'état initial.
+Le délai de 24 heures est illustré, pas appliqué à un véritable habitant.
+La vue opérateur est une simulation d'interface, pas une permission réelle.
+
+`fragment.html` conserve exactement la source affichée dans la conversation.
+`index.html` l'encapsule dans une page française autonome, avec ses styles
+et son JavaScript. Le sélecteur de variante Tweak est facultatif et reste
+inactif hors Codex ; aucune fonction de la navigation n'en dépend.
+
+Pour le site, copier le dossier ou intégrer le fragment dans un composant
+isolé. Conserver l'identifiant racine unique et exécuter le script après le
+montage ; prévoir une instance par page. Les données et actions réelles
+seront raccordées ultérieurement aux services Sanctuary.
+
+L'export standard Visualize a été produit avant l'encapsulation hors ligne.
+L'archive réutilisable garde uniquement la maquette et son code, sans le
+runtime d'aperçu, ses bibliothèques CDN ou un appel aux API de l'application.
+
+Origine : discussion Sanctuary / Blocodex du 13 septembre 2026.
+La mise en page a été créée pour ce projet ; aucun sprite Minecraft ou police
+propriétaire n'est embarqué dans cette maquette. Les polices sont système.
+Le cadrage est conservé dans `references/docs/blocodex-navigation.md` au sein
+de la collection complète. Le code du jeu reste en beta.004 à cet archivage.
diff --git a/archives/web/blocodex-navigation-v1/fragment.html b/archives/web/blocodex-navigation-v1/fragment.html
new file mode 100644
index 0000000..b3f9a14
--- /dev/null
+++ b/archives/web/blocodex-navigation-v1/fragment.html
@@ -0,0 +1,124 @@
+
+
+
+
diff --git a/archives/web/catalog-2026-09-13-web-v1.json b/archives/web/catalog-2026-09-13-web-v1.json
new file mode 100644
index 0000000..9e231a2
--- /dev/null
+++ b/archives/web/catalog-2026-09-13-web-v1.json
@@ -0,0 +1,7052 @@
+{
+ "id": "2026-09-13-web-v1",
+ "schema": 1,
+ "scope": "local-alpha-media-through-30.7-plus-blocodex",
+ "source_head": "1490fb2a80130df2e4a485a7b706d514e1a39cc2",
+ "source_worktree_dirty": true,
+ "counts": {
+ "alpha-figures": 13,
+ "alpha-research": 406,
+ "alpha-atlas": 248,
+ "beta-previews": 17,
+ "creator-icons": 6,
+ "title-reference": 1,
+ "alpha-release-atlas": 3,
+ "blocodex-concept": 3,
+ "documentation": 74,
+ "integration-intake": 2,
+ "reproduction-script": 6,
+ "archive-index": 2
+ },
+ "files": [
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.5-Grand-cartes.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.5-Grand-cartes.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.5",
+ "bytes": 341624,
+ "sha256": "ef8e815877a16cd398aa11303575e1f10138032685850a5187e69ab2dcfcea9a"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.5-Grand-coupes.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.5-Grand-coupes.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.5",
+ "bytes": 1418614,
+ "sha256": "2076cb21a2d0784bc6a12b509c8ef28449892a3895aae18a3430bc4bcbcc2d8b"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.6-Grand-cartes.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.6-Grand-cartes.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.6",
+ "bytes": 334280,
+ "sha256": "430dc14e20f63cda25aafed2ac1b02183b4e0e995bcfa37718d4f114d6e8686b"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.6-Grand-coupes.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.6-Grand-coupes.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.6",
+ "bytes": 1408971,
+ "sha256": "8c0381bda87ee1b44e10f27535186f551dc5a08ce183f2663fa4b362e64aa855"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.6-Moyen-terrains-aeriens.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.6-Moyen-terrains-aeriens.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.6",
+ "bytes": 64900,
+ "sha256": "029d0db5752ef782b54b4158eff4847f657f65369ffcb8daa91ea8a5380a69e6"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.6-Moyen-village.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.6-Moyen-village.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.6",
+ "bytes": 127063,
+ "sha256": "006e9108d4936fa9536d8fc8e2207bee36eb6bc137bad546cf3b8f8c8c1544b5"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.7-Grand-cartes.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.7-Grand-cartes.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.7",
+ "bytes": 333310,
+ "sha256": "ab0796c9dba59e99e460837040ea58a3491ff1f4e59b6a162acee2c8e04c018e"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.7-Grand-coupes.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.7-Grand-coupes.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.7",
+ "bytes": 1407846,
+ "sha256": "e61283c2e4c1b0e4900d0c1319b305cff7ff584e28fd3cb5e8a932a923be467e"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.7-Grand-installations.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.7-Grand-installations.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.7",
+ "bytes": 119369,
+ "sha256": "2ffd3731b978aef468558e0d952a52c539959e5be0543610f4835ebce4ba3535"
+ },
+ {
+ "path": "assets/Sanctuary-0.1.0-alpha.30.7-Moyen-installations.png",
+ "source": "build/Sanctuary-0.1.0-alpha.30.7-Moyen-installations.png",
+ "category": "alpha-figures",
+ "status": "release-named",
+ "version_hint": "alpha.30.7",
+ "bytes": 115971,
+ "sha256": "812220efa373c325ae2c6e7bdc8e0b41624eb64fb44d542ad5a6fd5ccd6c0bcf"
+ },
+ {
+ "path": "assets/alpha10-capacity-density.png",
+ "source": "build/alpha10-capacity-density.png",
+ "category": "alpha-figures",
+ "status": "development-output",
+ "version_hint": "alpha.10",
+ "bytes": 366375,
+ "sha256": "ada2a0da972319f3735c68a2e7a6ad635ca310ade0814f3ac8c04055c7881c8d"
+ },
+ {
+ "path": "assets/alpha19-evidence/heritage19-review.svg",
+ "source": "build/alpha19-evidence/heritage19-review.svg",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.19",
+ "bytes": 19256,
+ "sha256": "eaec32fdf0bd49ef644009ce8fb176ee7f96f540d5ab8d17d9761966bd074d64"
+ },
+ {
+ "path": "assets/alpha19-evidence/heritage19-review.svg.png",
+ "source": "build/alpha19-evidence/heritage19-review.svg.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.19",
+ "bytes": 180663,
+ "sha256": "fa7c9e0f4b85e6cd9d9826d68e7543f67e206e86a82fbb6e4dae28984832f601"
+ },
+ {
+ "path": "assets/alpha19-evidence/heritage19-seed0.png",
+ "source": "build/alpha19-evidence/heritage19-seed0.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.19",
+ "bytes": 163135,
+ "sha256": "64cfcb2690c84c6006f4d440147324284b15b445e3a8f30d9015ab1f4715d926"
+ },
+ {
+ "path": "assets/alpha19-evidence/heritage19-seed0.svg.png",
+ "source": "build/alpha19-evidence/heritage19-seed0.svg.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.19",
+ "bytes": 248912,
+ "sha256": "31c9e97ee2d9357213760a8bbc22c7cc1dff7a72a11b7c19ffdabc833bd5d673"
+ },
+ {
+ "path": "assets/alpha20-preview/images/apartment.png",
+ "source": "build/alpha20-preview/images/apartment.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 356238,
+ "sha256": "2c82e6f4701155233e876d7f6d472c9a1cb4fd8f09356a28d02fe9c65ae3e77c"
+ },
+ {
+ "path": "assets/alpha20-preview/images/barracks.png",
+ "source": "build/alpha20-preview/images/barracks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 270232,
+ "sha256": "bf62e5bb1a7f53efdeca313c0dc544f0d8b885ae01797642ff04e9f09c6f1d07"
+ },
+ {
+ "path": "assets/alpha20-preview/images/communal_depot.png",
+ "source": "build/alpha20-preview/images/communal_depot.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 278042,
+ "sha256": "d416218ec2353b342f649566997ffbec9b792ce101868afbcf74582657c456d4"
+ },
+ {
+ "path": "assets/alpha20-preview/images/construction_refuge.png",
+ "source": "build/alpha20-preview/images/construction_refuge.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 242664,
+ "sha256": "bb0f49abffb35c9a3d24b4e695649e089e927d68c08d9a4425ff62a46b10384b"
+ },
+ {
+ "path": "assets/alpha20-preview/images/excavation.png",
+ "source": "build/alpha20-preview/images/excavation.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 156328,
+ "sha256": "a403823f5a3dc51c01c8e388b7d7d0abb979dc862095c3dc236131621c294070"
+ },
+ {
+ "path": "assets/alpha20-preview/images/expedition_office.png",
+ "source": "build/alpha20-preview/images/expedition_office.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 200686,
+ "sha256": "42533cfcdf7db69b926cec6b3c7eff07abae92ad4e276929f0f4c45d12097470"
+ },
+ {
+ "path": "assets/alpha20-preview/images/freight_station.png",
+ "source": "build/alpha20-preview/images/freight_station.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 207334,
+ "sha256": "976761166d96caa49cbf14e5bed8cfa508a1c1285bd51b3b69020068a08223cd"
+ },
+ {
+ "path": "assets/alpha20-preview/images/greenhouse.png",
+ "source": "build/alpha20-preview/images/greenhouse.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 276271,
+ "sha256": "2d2f03140ab49c3addd2a14bc1c5e3fe2a622675f04ee007ffc4b47eb0659e86"
+ },
+ {
+ "path": "assets/alpha20-preview/images/hall.png",
+ "source": "build/alpha20-preview/images/hall.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 334955,
+ "sha256": "2c84c640144004c2011992f85771fa54abb04380d516b9a94ded77e4f44e9ee5"
+ },
+ {
+ "path": "assets/alpha20-preview/images/hotel.png",
+ "source": "build/alpha20-preview/images/hotel.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 326119,
+ "sha256": "2323e6b7220b1f537357137b8921496d81b52868e3297fe22ec60b888d44ae75"
+ },
+ {
+ "path": "assets/alpha20-preview/images/impossible_service.png",
+ "source": "build/alpha20-preview/images/impossible_service.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 221861,
+ "sha256": "58c29a93ae04ece3e7759e0d45ee475ad2ed6853f06e0183bd24cd3b24f62204"
+ },
+ {
+ "path": "assets/alpha20-preview/images/library.png",
+ "source": "build/alpha20-preview/images/library.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 342031,
+ "sha256": "d6b60141a7554664d2604227f951afa6bafb6cbadeea34cc781679962c304be7"
+ },
+ {
+ "path": "assets/alpha20-preview/images/market.png",
+ "source": "build/alpha20-preview/images/market.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 184797,
+ "sha256": "1e295195a6068718511ca69dfd0bb17db08c547ad915a495c1ad8276e09c3c76"
+ },
+ {
+ "path": "assets/alpha20-preview/images/observatory.png",
+ "source": "build/alpha20-preview/images/observatory.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 253766,
+ "sha256": "8200e2b53c3ad78e861b53d987857f2e876385e113b035d5fbbec23cd2361a01"
+ },
+ {
+ "path": "assets/alpha20-preview/images/overview.png",
+ "source": "build/alpha20-preview/images/overview.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 2795611,
+ "sha256": "79c76e1d3a33b52d117bcc23bb9b3f2213cd7f1246c7f955c64bd201852b6b53"
+ },
+ {
+ "path": "assets/alpha20-preview/images/park.png",
+ "source": "build/alpha20-preview/images/park.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 231816,
+ "sha256": "4f4b002a6fe3bf971f1e5839d9f00b8ae1ca2f1caafecdfad1ea35545ece144d"
+ },
+ {
+ "path": "assets/alpha20-preview/images/park_garden.png",
+ "source": "build/alpha20-preview/images/park_garden.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 241027,
+ "sha256": "c92f7bea7a2161d1fd93936b839f5dc9684d05683be905088c99fd95aa493a8c"
+ },
+ {
+ "path": "assets/alpha20-preview/images/park_memorial.png",
+ "source": "build/alpha20-preview/images/park_memorial.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 138675,
+ "sha256": "0b4ebc2926837ab1721647eb5cac8de7974afa7b9fba3eaf5f6473824b891550"
+ },
+ {
+ "path": "assets/alpha20-preview/images/park_playground.png",
+ "source": "build/alpha20-preview/images/park_playground.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 191724,
+ "sha256": "a731ae4678a4532a92516b90093e9ab3163c4e431b4c765ef1c59ba892a5692c"
+ },
+ {
+ "path": "assets/alpha20-preview/images/parking.png",
+ "source": "build/alpha20-preview/images/parking.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 97097,
+ "sha256": "b99f772b27bfc2b3a9e1fa10df30b39f10feebbf53f02b6ba4d8ce58f9dec2c2"
+ },
+ {
+ "path": "assets/alpha20-preview/images/photo_club.png",
+ "source": "build/alpha20-preview/images/photo_club.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 200830,
+ "sha256": "790e404542aa76394c261000e64f15b708dd1f08eb0cd8aa58028127208031cb"
+ },
+ {
+ "path": "assets/alpha20-preview/images/plaza.png",
+ "source": "build/alpha20-preview/images/plaza.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 146583,
+ "sha256": "5cefc170077bbba9d0c41bc6122d2d9b3e2bb532345b3a6785018e497b7f07cd"
+ },
+ {
+ "path": "assets/alpha20-preview/images/pool.png",
+ "source": "build/alpha20-preview/images/pool.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 152183,
+ "sha256": "f14ad47fde06bb10c8f61b51afd5b792d931191cbdcd64eb97400d4ad854aca6"
+ },
+ {
+ "path": "assets/alpha20-preview/images/post_office.png",
+ "source": "build/alpha20-preview/images/post_office.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 210173,
+ "sha256": "fed474440089540fda1d81d1ebc459c13ec4d9c17506a3d4278797735174c5b4"
+ },
+ {
+ "path": "assets/alpha20-preview/images/pump_station.png",
+ "source": "build/alpha20-preview/images/pump_station.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 230806,
+ "sha256": "721a850999b3dce58e4639102cba26f01599289625f99db547895c63360c1ed8"
+ },
+ {
+ "path": "assets/alpha20-preview/images/radio_relay.png",
+ "source": "build/alpha20-preview/images/radio_relay.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 201055,
+ "sha256": "f67751518b199211b9816661706121d27d82aebaa1e7dcd65e903139d7ae1f2d"
+ },
+ {
+ "path": "assets/alpha20-preview/images/rail_workshop.png",
+ "source": "build/alpha20-preview/images/rail_workshop.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 230782,
+ "sha256": "7341df62f34746fe037c64a15f177b84df14cfcebdf4f45d061a6561a9e43e45"
+ },
+ {
+ "path": "assets/alpha20-preview/images/restaurant.png",
+ "source": "build/alpha20-preview/images/restaurant.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 362010,
+ "sha256": "a1d58861ae7e9010a713237632e880cdb328af2bdf839d926325249f60a0ea05"
+ },
+ {
+ "path": "assets/alpha20-preview/images/substation.png",
+ "source": "build/alpha20-preview/images/substation.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 203742,
+ "sha256": "6a66d2dcdba28647f28163480183e5c84b89ac7a25c043759cda1ba65020f00b"
+ },
+ {
+ "path": "assets/alpha20-preview/images/void_dock.png",
+ "source": "build/alpha20-preview/images/void_dock.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.20",
+ "bytes": 206212,
+ "sha256": "33cb62c1775c09d80267895d728cb1ad7c3e8541753816c67a72f48a7211cc52"
+ },
+ {
+ "path": "assets/alpha21-preview/images/apartment.png",
+ "source": "build/alpha21-preview/images/apartment.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 319018,
+ "sha256": "ce39bcfbc14f57765860703b8e1a6bb519636328d6da872f2c9a24fceada55b5"
+ },
+ {
+ "path": "assets/alpha21-preview/images/barracks.png",
+ "source": "build/alpha21-preview/images/barracks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 266722,
+ "sha256": "8b55bc1fa2fe5b88cab462733ffa408e48cd394f335b6613ca376791437960a9"
+ },
+ {
+ "path": "assets/alpha21-preview/images/communal_depot.png",
+ "source": "build/alpha21-preview/images/communal_depot.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 293480,
+ "sha256": "78dc2e057be04d308d6c95f787ef9748e7f23ef20c92c471aab6a432e35beeb0"
+ },
+ {
+ "path": "assets/alpha21-preview/images/construction_refuge.png",
+ "source": "build/alpha21-preview/images/construction_refuge.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 223231,
+ "sha256": "f58f53dec0b7b70dc56defcfbcb9ffd62f9922f0e3071c8e8fa21512dad81c35"
+ },
+ {
+ "path": "assets/alpha21-preview/images/freight_station.png",
+ "source": "build/alpha21-preview/images/freight_station.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 199044,
+ "sha256": "0ca72f19aa9783f89cc4c9ac190a5aa14562db1f71b0a4e4669fe3a74cd5d71c"
+ },
+ {
+ "path": "assets/alpha21-preview/images/greenhouse.png",
+ "source": "build/alpha21-preview/images/greenhouse.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 270440,
+ "sha256": "e4c708844ddce4dd2190ff03109a21856b2495b4c1d7e72826f8f3196db9fe55"
+ },
+ {
+ "path": "assets/alpha21-preview/images/hall.png",
+ "source": "build/alpha21-preview/images/hall.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 304320,
+ "sha256": "5cfd5b4fc1293c0f08131a0089bd52ac8dfd6af3afc59bfb637edc0d22363bcb"
+ },
+ {
+ "path": "assets/alpha21-preview/images/hotel.png",
+ "source": "build/alpha21-preview/images/hotel.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 271217,
+ "sha256": "3e69647fbf3ec98fd6658dcabac1b8a71e5f48039f2c7202f4969d75d8446275"
+ },
+ {
+ "path": "assets/alpha21-preview/images/library.png",
+ "source": "build/alpha21-preview/images/library.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 305966,
+ "sha256": "14cad7394f32108aa64bff17228228ebe076cf455d73d15898a6a72ad3916c73"
+ },
+ {
+ "path": "assets/alpha21-preview/images/observatory.png",
+ "source": "build/alpha21-preview/images/observatory.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 237085,
+ "sha256": "15f4f4831dce9251a7b31a56523f49c0836d00cff1fa1a3bc377decf8f981d00"
+ },
+ {
+ "path": "assets/alpha21-preview/images/overview.png",
+ "source": "build/alpha21-preview/images/overview.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 2522128,
+ "sha256": "3d1ebb51a457a90d0c7f33c475a5b4f2b9c7216df1935b1c7dd2b228ebac61db"
+ },
+ {
+ "path": "assets/alpha21-preview/images/post_office.png",
+ "source": "build/alpha21-preview/images/post_office.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 200351,
+ "sha256": "a09ca463db086f873f52ff2deb2c91987fb83853e28b64b38cd0ac20a279c785"
+ },
+ {
+ "path": "assets/alpha21-preview/images/radio_relay.png",
+ "source": "build/alpha21-preview/images/radio_relay.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 203218,
+ "sha256": "fd37898065a33485006c4e3afb5b48d44fd0e8da1bb7775ed57b68a9514dbae7"
+ },
+ {
+ "path": "assets/alpha21-preview/images/rail_workshop.png",
+ "source": "build/alpha21-preview/images/rail_workshop.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 264204,
+ "sha256": "0c062da88f2240c69e73b6a3927edc7e43cec1db5f8a0dcb7482368dffcf145a"
+ },
+ {
+ "path": "assets/alpha21-preview/images/restaurant.png",
+ "source": "build/alpha21-preview/images/restaurant.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 324556,
+ "sha256": "fcf3c27d15df730a70ab895293c7181327faf12603e8713c356a935a31ebbbc7"
+ },
+ {
+ "path": "assets/alpha21-preview/roof-review/construction_refuge-underside.png",
+ "source": "build/alpha21-preview/roof-review/construction_refuge-underside.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 116997,
+ "sha256": "578f434d26bf27b163262d49a11614e9eb2a2587331ac3431f25df0258bd3bfe"
+ },
+ {
+ "path": "assets/alpha21-preview/roof-review/overview-underside.png",
+ "source": "build/alpha21-preview/roof-review/overview-underside.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 313776,
+ "sha256": "8d7ffc0c17b8f7be614697314ea346d088c86d610590c220ffdb97457818cce6"
+ },
+ {
+ "path": "assets/alpha21-preview/roof-review/radio_relay-underside.png",
+ "source": "build/alpha21-preview/roof-review/radio_relay-underside.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 111165,
+ "sha256": "9ef9a9af788a3b5e267a8a62af0efeaf8419c3efdafbc22547942180fcc743f9"
+ },
+ {
+ "path": "assets/alpha21-preview/roof-review/rail_workshop-underside.png",
+ "source": "build/alpha21-preview/roof-review/rail_workshop-underside.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.21",
+ "bytes": 151618,
+ "sha256": "3c3c4197166ee7da08627a6a6ba5ef6b29b9c61f7540434bf5419ff56edb8982"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-shore-fix/balloon.png",
+ "source": "build/alpha24-evidence-before-shore-fix/balloon.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 26190,
+ "sha256": "a728378ba014fe7582bc82a39d6b069181b56ef185717ba789615ad5f758f16d"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-shore-fix/merchant_provisions.png",
+ "source": "build/alpha24-evidence-before-shore-fix/merchant_provisions.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 22055,
+ "sha256": "95bb0f960b94465d96938c45c8df274046154f22a57d79595947a74081a1a63b"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-shore-fix/merchant_tools.png",
+ "source": "build/alpha24-evidence-before-shore-fix/merchant_tools.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 21437,
+ "sha256": "45c2af8aff3cd5059b553160e9220e1d45d7f023305b0d5b5df87babdfee7552"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-shore-fix/mineral_islet_dry.png",
+ "source": "build/alpha24-evidence-before-shore-fix/mineral_islet_dry.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 42432,
+ "sha256": "134c5d3afd4b8b63f71f166d4c929efb048b5f05e55825351d3d9a9ca6be0ef9"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-shore-fix/mineral_islet_water.png",
+ "source": "build/alpha24-evidence-before-shore-fix/mineral_islet_water.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 39551,
+ "sha256": "9b0f86ec800589e05aa49076a289940107dc1c719837fd6f2d70ca24846e160b"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-vegetation/balloon.png",
+ "source": "build/alpha24-evidence-before-vegetation/balloon.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 26174,
+ "sha256": "ffbaa698cdea9c1a72e1edab717e6287578b1a5560ea11f4caa7c603e5536231"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-vegetation/merchant_provisions.png",
+ "source": "build/alpha24-evidence-before-vegetation/merchant_provisions.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 22060,
+ "sha256": "0ae1010266064867c7c38b3e1ebffe42233b84cd5989ab83741959e73cc6e828"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-vegetation/merchant_tools.png",
+ "source": "build/alpha24-evidence-before-vegetation/merchant_tools.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 21448,
+ "sha256": "40a3b71a2c9acbdbf8a22bdaaf2b9858dd2b427785b12ef892c369a7c701fc5f"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-vegetation/mineral_islet_dry.png",
+ "source": "build/alpha24-evidence-before-vegetation/mineral_islet_dry.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 30349,
+ "sha256": "14c2e167018315af143676c0eb453e2fdf35573d2fdd3caf65283cda6fb63a4a"
+ },
+ {
+ "path": "assets/alpha24-evidence-before-vegetation/mineral_islet_water.png",
+ "source": "build/alpha24-evidence-before-vegetation/mineral_islet_water.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 33444,
+ "sha256": "a705827fec4a775168aebdd6be27385f6b4acf59f56781db558b4d34047811f5"
+ },
+ {
+ "path": "assets/alpha24-evidence/balloon.png",
+ "source": "build/alpha24-evidence/balloon.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 26196,
+ "sha256": "d3372728d69e7df66893a39ac34cfa99efb4d8bfdac29469051724153603334d"
+ },
+ {
+ "path": "assets/alpha24-evidence/merchant_provisions.png",
+ "source": "build/alpha24-evidence/merchant_provisions.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 22087,
+ "sha256": "20028003186168f172656fd22c5a444012e0875efb9821c6f433fa4e88817cf1"
+ },
+ {
+ "path": "assets/alpha24-evidence/merchant_tools.png",
+ "source": "build/alpha24-evidence/merchant_tools.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 21443,
+ "sha256": "0acebb149bf2da6e4357c84a021e263058c212fcf3f3be0c1c5440d5424d4dbd"
+ },
+ {
+ "path": "assets/alpha24-evidence/mineral_islet_dry.png",
+ "source": "build/alpha24-evidence/mineral_islet_dry.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 42294,
+ "sha256": "d3ce3ab74e6a38d7513dd448e257829519ed8cd1ecc7ebb3d48f24e9f6f11f06"
+ },
+ {
+ "path": "assets/alpha24-evidence/mineral_islet_water.png",
+ "source": "build/alpha24-evidence/mineral_islet_water.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.24",
+ "bytes": 39586,
+ "sha256": "c1d6104ea113a1c2e7f3759367fb511397f23584638e99346067bc3eeca0171b"
+ },
+ {
+ "path": "assets/alpha27-preview/build/alpha24-evidence/merchant_provisions.png",
+ "source": "build/alpha27-preview/build/alpha24-evidence/merchant_provisions.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.27",
+ "bytes": 21916,
+ "sha256": "38e1ee7f714708093812fcbf609bc85bd2de6e8da1c623bd34a116c3ae2fab08"
+ },
+ {
+ "path": "assets/alpha27-preview/build/alpha24-evidence/merchant_tools.png",
+ "source": "build/alpha27-preview/build/alpha24-evidence/merchant_tools.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.27",
+ "bytes": 22753,
+ "sha256": "12a3b2acb23ba9631a4f253c6709b7d61ee07285016a4f64d136614cb7e9c154"
+ },
+ {
+ "path": "assets/alpha27-preview/native-merchant_provisions.png",
+ "source": "build/alpha27-preview/native-merchant_provisions.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.27",
+ "bytes": 22484,
+ "sha256": "4214e1de9f2b617cd9b5b13baeb8b511214e3215d7518bd221892896ee31747c"
+ },
+ {
+ "path": "assets/alpha27-preview/native-merchant_tools.png",
+ "source": "build/alpha27-preview/native-merchant_tools.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.27",
+ "bytes": 22503,
+ "sha256": "73c53bc6c1c67cd002bc05dcb61f610f608410745c309d1b4179110fa8d8f5bf"
+ },
+ {
+ "path": "assets/alpha27-preview/superseded-custom-coque/merchant_provisions.png",
+ "source": "build/alpha27-preview/superseded-custom-coque/merchant_provisions.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.27",
+ "bytes": 21916,
+ "sha256": "38e1ee7f714708093812fcbf609bc85bd2de6e8da1c623bd34a116c3ae2fab08"
+ },
+ {
+ "path": "assets/alpha27-preview/superseded-custom-coque/merchant_tools.png",
+ "source": "build/alpha27-preview/superseded-custom-coque/merchant_tools.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.27",
+ "bytes": 22753,
+ "sha256": "12a3b2acb23ba9631a4f253c6709b7d61ee07285016a4f64d136614cb7e9c154"
+ },
+ {
+ "path": "assets/alpha28-preview/geology-final-comparison.png",
+ "source": "build/alpha28-preview/geology-final-comparison.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.28",
+ "bytes": 94337,
+ "sha256": "b94323d3c0a35885611fc8119f5fd92e537a841688f945d7fa0568eee04ab1fd"
+ },
+ {
+ "path": "assets/alpha28-preview/geology-first-comparison.png",
+ "source": "build/alpha28-preview/geology-first-comparison.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.28",
+ "bytes": 91219,
+ "sha256": "e2f989c2c5179ee0fd299691b1540eae9029f77ee1d67bd000de4e30bc0d5c5e"
+ },
+ {
+ "path": "assets/alpha29-preview/terraces-1024--7228211907433324401.png",
+ "source": "build/alpha29-preview/terraces-1024--7228211907433324401.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.29",
+ "bytes": 105532,
+ "sha256": "d7a88e0f42dd681cb6537028c51d5f0e0a1978b6b26c5f5b9576ffae57dab439"
+ },
+ {
+ "path": "assets/alpha30-preview/relief-1024--7228211907433324401.png",
+ "source": "build/alpha30-preview/relief-1024--7228211907433324401.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30",
+ "bytes": 106311,
+ "sha256": "8a63f9221f4c87fcdcc255b2947c19d77b736fd3aa72421e82c9f6e35a2b3c71"
+ },
+ {
+ "path": "assets/alpha30-preview/relief-512-2026.png",
+ "source": "build/alpha30-preview/relief-512-2026.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30",
+ "bytes": 100605,
+ "sha256": "35c14b55948b87efbb931aeaa33dcf7f8594a040d0dc594fbfb5dc3686474f4f"
+ },
+ {
+ "path": "assets/alpha301-river-plan.png",
+ "source": "build/alpha301-river-plan.png",
+ "category": "alpha-figures",
+ "status": "development-output",
+ "version_hint": "alpha.30.1",
+ "bytes": 24413,
+ "sha256": "63b620e5212ac5b6d76d0d8a030f80aa9f91535ae4706406e9d6fe6afaec886e"
+ },
+ {
+ "path": "assets/alpha302-evidence/medium-0/native-before-after.png",
+ "source": "build/alpha302-evidence/medium-0/native-before-after.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.2",
+ "bytes": 104858,
+ "sha256": "a57bd0af013df6536c1cb6322a10472bd6f23c70ac32a4f3f4089f9b5df92b3e"
+ },
+ {
+ "path": "assets/alpha302-preview-first-pass/native-before-after.png",
+ "source": "build/alpha302-preview-first-pass/native-before-after.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.2",
+ "bytes": 100119,
+ "sha256": "93cb49acaafde95e6b8a5bc51b31c255ae2efd5313505767891ed49fa3fd8e19"
+ },
+ {
+ "path": "assets/alpha302-preview-second-pass/native-before-after.png",
+ "source": "build/alpha302-preview-second-pass/native-before-after.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.2",
+ "bytes": 93240,
+ "sha256": "4683644aa490fa85b8db6436d204c01a42bf4f4b621dc2e1c358ed81d0191323"
+ },
+ {
+ "path": "assets/alpha302-preview/native-before-after.png",
+ "source": "build/alpha302-preview/native-before-after.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.2",
+ "bytes": 104858,
+ "sha256": "a57bd0af013df6536c1cb6322a10472bd6f23c70ac32a4f3f4089f9b5df92b3e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 67505,
+ "sha256": "349d25183947d7985c843cd66a7d6e6477423e22bf50450e1c6bbe3af096fd8e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14221,
+ "sha256": "8fda5b0a536de9bd64e77bef3f91358f2dbb2b340476afe35aaf5d58d47ff2d6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14694,
+ "sha256": "e4c31922bbc7525c1770c52662ea8f1af159ef6d87fa4643f236389a2d968e29"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 41277,
+ "sha256": "7d7579a2426fe8bab4efc3705b6a87118be94fbf93e864ae994962aa54328b76"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 28323,
+ "sha256": "0d9397c69a46a80d016d660c1523a5b68a6c7539e1c0462c2383593fa94bf0df"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 28431,
+ "sha256": "cac883360e62c84a3339bf3fb3e2e7a4ac7663c33a4d87b4589713c9f501531d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27086,
+ "sha256": "4291acef7fe4f379b48fb571c56bce7c4022777d70e137c0bd20e2ccc181944a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17588,
+ "sha256": "d1f00486be98d045cf834afb87d6c3581e5c804d8db67be3d2b2587fe4469a7b"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 23294,
+ "sha256": "2ae2dba486c1f0457db73dff3de218dcce1fa1b11d57fe9e2dd39ee994cf4459"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21389,
+ "sha256": "e5ad3796cc17087b3b2aed25ecb872717720c7be061a6d5809b0b8966382b30f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 81320,
+ "sha256": "1bff0ab7bf6a09cbc8091e14635c5cb92ab314e1e01fd028ae07e899ff6ce64e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 67032,
+ "sha256": "e0d42fa2041d0cf6cb037c9784c310a78a434332cf11371d6fe9de0b19e2ba31"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-lava--3864087617497570649.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 13828,
+ "sha256": "016a29795197a8e5bcd74b4686c9a9c7f665fb2f271e37eb5751128570e0bc36"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-lava--480127526815419003.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14295,
+ "sha256": "fe5d4f6f0128d4259ea405081fd66b94c39a1b9ab82eb640a49a1e8edc7e469e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 40845,
+ "sha256": "f62ee362ffcd58da76dc8077e71068bba019436cd43435625172d85d5c169e1c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-rift--4486800185823584124.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27919,
+ "sha256": "449a50a42ee88c57745166bfcaba5d221726682b5d9bd198a7e540f529bf7c8d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-rift-5929486622880958566.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 28037,
+ "sha256": "857445e0e479619cb19e3172250fcf68ec310165643c014c778441b4aaa34307"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-rift-7388736356304645874.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 26701,
+ "sha256": "66bf0ec959131a8e7cb63dbde88b0cc65d1010de209fed118976cfb8473d0833"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17039,
+ "sha256": "9b2c48e370ff9b4a9c5978d02d51a21ba706adb46268fc874020d78987112cad"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22893,
+ "sha256": "40e28ec05398f1beeba079ea0ae6a89d10aca5ef7302c8d4c759eb0d81ce2df2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated-spring-2318528324537894782.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20897,
+ "sha256": "fd8fa68ed2f02b7878e916a62c2825c60f36fc1b680c5968f359370dc93ef9d7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-0-generated.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 80807,
+ "sha256": "57d66d3304b0c65cc099edfb00272086aa9e9dff91e243ca66ac7452b4c505a2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 68878,
+ "sha256": "ead168b9b127746a8b0308d3e4693bac4235e92c21a1642152482623c914c636"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-lava--4133217051590653271.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-lava--4133217051590653271.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14947,
+ "sha256": "39c756130ebbe7d211ac093f50ed5a9e0f21c466e36b9d19fdc9f03a03cddaf2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-lava--8699916113382030230.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-lava--8699916113382030230.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14402,
+ "sha256": "3b3870da367a54512d3b7e6741ffedc0a2c312c67c787f2e312dc8c8c6c87455"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 38929,
+ "sha256": "510d39d961b88890252e03cf139db4b362857de226d7b602a17e91e544acc4d7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-rift-171155121851024969.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-rift-171155121851024969.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 25409,
+ "sha256": "09c02948f8890ff0f56b34bd7695b422facdc9fc4559a5086575b71bab150473"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-rift-2302936489416991036.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-rift-2302936489416991036.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 25926,
+ "sha256": "6cd1ae73977590972d98e2a45ae1ae676efa6ea30a9621f3a9c4e0c2fb91ec05"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21430,
+ "sha256": "a1f557380af4e53170331dd296e828864dc9bde9ce230dc9d18a7b69f2a414f0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-spring--470657339720053241.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-spring--470657339720053241.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22206,
+ "sha256": "c198388a2cfb29814926f507143646c50a221565bf7a2a6b20db8fbe65b03ef7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16959,
+ "sha256": "2c64538035d0cd55c8cb4c3886948351c5ae3117c547387d76a37521c9c2913a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 81002,
+ "sha256": "46bc235bb7508f9667204fbbf92f06e87421593f3e9791e2287fac67da79198c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 68220,
+ "sha256": "bdb42accc7e4de941cfd13fd9112c488305f9819e47e4a256a2a82a9ff74ed91"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-lava--4133217051590653271.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-lava--4133217051590653271.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14404,
+ "sha256": "be1e5e409616501436d83ff76ff6c65787acf00c8bc5b9f8e8f3abd7d9981c49"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-lava--8699916113382030230.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-lava--8699916113382030230.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 13850,
+ "sha256": "fe4ad2f3b3e40911e2961106ab9bffe28394537df19f1c4d5b3f3c11aa06013d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 38367,
+ "sha256": "6e9ebf26defa4f7d4ac306511967857d54d4d33f14ea7b81a0ade40ba03508f6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-rift-171155121851024969.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-rift-171155121851024969.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 24895,
+ "sha256": "c897026c8ba07c20855baff204e19093a261c4f9a0a14e4f768edff6c2512e34"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-rift-2302936489416991036.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-rift-2302936489416991036.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 25402,
+ "sha256": "5728627f8c42faa4b43dae64dfa6c4bfd644f299973c987b503446c7411571df"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20770,
+ "sha256": "fe4e0b8c679967a07a77c7dde51cc0c5324738f7325cc1abdd2de14aec1d1d3d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-spring--470657339720053241.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-spring--470657339720053241.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21567,
+ "sha256": "546d7c1b066fc47559addb664a1a7d00e346f5fff928dbba7ad8b4d4e5df577d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16249,
+ "sha256": "63a5717f215b2a6ef6dfd2fbfd54817d8b9358584d9e83c1a302415476e02ab3"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-42-generated.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 80245,
+ "sha256": "96e59373ab3d21630102d378fb1d5adf8be1376cfbe573ff2043ee7c1413d2d9"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 66877,
+ "sha256": "a21eb75777a9825791029a62c59776a03f822ddfd7fd208a2f5a777d452b825e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-lava--5861423135258671607.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-lava--5861423135258671607.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 15062,
+ "sha256": "1a024dee1f6925481e3e93abe25fbaf2c8ed3f603f106be49dd538153eb50721"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-lava-3423285028654801951.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-lava-3423285028654801951.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 15037,
+ "sha256": "0ca08dd2242f37a6bfc4a4454c3c4c6d7318ed4e3424735f53e02915d7e7ff12"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 39256,
+ "sha256": "86ce3635acbfe460d2861a92b9aaec063faad3431ac378ee65d0dc47d87daf86"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-rift--6844189678019355418.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-rift--6844189678019355418.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27469,
+ "sha256": "a8963d1535c6b53382af7f4d9c3aeaa1bfea5e085c2d9e53f58ea64cd7980f42"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-rift-2611836651394526228.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-rift-2611836651394526228.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 29703,
+ "sha256": "6e34fd4640937be3d65cfab3768c0124fc042af999c3f1e366dd95b9023cb3d1"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-rift-4050165116292789323.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-rift-4050165116292789323.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27752,
+ "sha256": "84c1c602c98cd798d512c77963cbfc14b460ff3d3acaaa3740d6ac839465471e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 23345,
+ "sha256": "007f9cd1eec9acc5652da8feb5f4de6133b68b86c80d245be3b657602ff665cd"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-spring-1281588063776317571.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-spring-1281588063776317571.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22178,
+ "sha256": "115b44967b9b2c57ed31d85f22e710182a394e62c50ecaabbdb62fce653b2d34"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18156,
+ "sha256": "035f3507bb09acf5d7bebca6000d4d19439f3d87ae87e0b4fbb2fbcebd97fea3"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 81439,
+ "sha256": "5f0361bea24d97861ce9516224714217857028780ae7aab2cefd5c3abbfb6c4e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 66591,
+ "sha256": "13e1fcbf7d097b0a5e1ae926bdf39a61f68bf091343a9d8ad6f0a114718c666d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-lava--5861423135258671607.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-lava--5861423135258671607.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14887,
+ "sha256": "23705259b0aa1e3fd8cc53d82d5a679ec5792edbefb50b88a50a7e7b5a7296b1"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-lava-3423285028654801951.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-lava-3423285028654801951.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14867,
+ "sha256": "88e586b9f5f622ccd0f9e0fba71e16697a3848d456d81b3cf92d3c53a01566f0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 39059,
+ "sha256": "5d1b722e909df29b5733788bc2defa9d0a1ecbf23fa368494189470a8c27c760"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-rift--6844189678019355418.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-rift--6844189678019355418.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27333,
+ "sha256": "535524675a78a47f71d7c574ec3044037d70a72b8176765dcd40108d2a5429a1"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-rift-2611836651394526228.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-rift-2611836651394526228.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 29565,
+ "sha256": "714a69ef210533cf8d7680a33e4611fdb8d98aad50519c7c5a50b7a7132bd0c5"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-rift-4050165116292789323.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-rift-4050165116292789323.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27616,
+ "sha256": "3a9e91a98c00cb85bc4b701d52f91c87269442a6633e9e9bebfb0a8e39b97419"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 23208,
+ "sha256": "47e4524dc77900e9f50ef61f085aabc29557771c0a6588452f261525eedc1ff5"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-spring-1281588063776317571.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-spring-1281588063776317571.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21887,
+ "sha256": "a03e5616f10ca6e6d718a462cc6e2c786feec6715b59110e0272e40c5e84133d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17836,
+ "sha256": "52ea12f3bc310d6a4a01cc61d702c8be7a1ee4a72214b5a4d33fb86df8f9f0fb"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/cavern-seed-8675309-generated.png",
+ "source": "build/alpha303-evidence/medium-0/cavern-seed-8675309-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 81150,
+ "sha256": "de7d386c569a2bbfb6615ab4ee6a1bbe120df3f0e6aa62167faf28eea74b006e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/island-density-seed-0.png",
+ "source": "build/alpha303-evidence/medium-0/island-density-seed-0.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 70369,
+ "sha256": "452003e68182b7c1813544b3525d11701af97d818cef6f321d29509f48428251"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/island-density-seed-42.png",
+ "source": "build/alpha303-evidence/medium-0/island-density-seed-42.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 66166,
+ "sha256": "0e67ddeb653819aedcd68219c5d823c71823cffc4daefe45f633fe3076acbdc7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/island-density-seed-8675309.png",
+ "source": "build/alpha303-evidence/medium-0/island-density-seed-8675309.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 70193,
+ "sha256": "061c6d80dbf93773ba8e794a74742d2199c6d36441436eb0d7e061438ee9fb2c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 42791,
+ "sha256": "a9dbf012d0d4a1e00f0c8859cecda1225a0d15ba7e7c911d08df90946b8c4157"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 19388,
+ "sha256": "8b5771e6207d55ff1b1d85a96ab179391375da099edd7c4516b812dde397ecf5"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22829,
+ "sha256": "fca7e0a60ead98e23e4e3705840c540551ded47ac189c7cd69565a9ff4d107ba"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-spring-9096382656761284700.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks-spring-9096382656761284700.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16756,
+ "sha256": "64fc8be38e0690c2adbc60ff43de07e3682f4c374726e5f5044400b85f8626c2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 69504,
+ "sha256": "d08ba77c4b5cf8c09484c6cdc75a63949210680bdf4098c9ae05882103f25b46"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 42367,
+ "sha256": "48e1f5b966b12a916d588c0dd63f26608ba9cc9135c790830dcfa31f97fef065"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18851,
+ "sha256": "8b238aba8fc0cb1cd7d09b3a5ecc9b49f3bc5a739490012ad9fa4f9a2535b9c8"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22362,
+ "sha256": "2b8d1ef1ef1b7630621ca4189004e1df65389bd959e450752304bb7a8b4bc126"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-spring-9096382656761284700.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated-spring-9096382656761284700.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16203,
+ "sha256": "3ff6bd7713838389102436a3a75a4ccb2a6ab74686d8db12e88cde6bcf716fce"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 69046,
+ "sha256": "405040518b2ae48ba17a73f7fa0301ca3de36c76b4ba89741e81e3097596b913"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 42677,
+ "sha256": "fa2247ca79983abdd26a87f9d684107fbcf0a6dd4792f08db2475b357952360b"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21447,
+ "sha256": "eb545a85091c9142d1317733ef9e4bab1386ff65b19f58519739fb06288a15e0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-spring-36992381830938400.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-spring-36992381830938400.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17231,
+ "sha256": "bde52804cd2a5621e9e53e3c2d0c97827d25bae7d15b63aee8fab73859bb7757"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18913,
+ "sha256": "61484bc6a2d1d9bd4cc1e21df1da359358852f922508f754e8d78b260d9f8d7a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 68262,
+ "sha256": "a97e604ec9f9c447c31aab94804f051205c010e4d88a9faddfd419d76725612f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 42125,
+ "sha256": "fa95fbb501c46db8b7be46abe69359d70f57962bc6c150c82f2a6128d561c0a1"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20762,
+ "sha256": "bee9e06bd549af1ab1ef6e4b719a789895dd207667580fd509b339b76bbfa1c0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-spring-36992381830938400.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-spring-36992381830938400.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16514,
+ "sha256": "bbdc443c1a05730e2af225e56da15f77c59b7953fc5cbdf05562f3d28bf37670"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18176,
+ "sha256": "7a27f5fd5dc52e2524fec4e9d24062a0c2fa9bb3ae56232972e8181e524d5539"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 67640,
+ "sha256": "9270232f2d8fd78e98e4ded51b4b1e39cfafcf8f7236a3abc24f5e663dea1e75"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 43957,
+ "sha256": "c3fbe4b3fe9fd3558b71e438bd6a1f030cb2d01749bb0c97e7799803da77d97c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22303,
+ "sha256": "f4f9a178d86caa21c5305ff4a7f73da30c1778a233fe452a770b0e02a93dcd1f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-spring--4800433529807892464.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-spring--4800433529807892464.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17874,
+ "sha256": "438aa19ec2fdd8b54b2173a34a6e4980576618a276240b955d55ba2cd82f4c01"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17937,
+ "sha256": "dfbdcf0679545911ee92f7044b37a2fcd3e54fb23a22eeae7baf30a580cdacd8"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 70736,
+ "sha256": "93f7f26da69367534570c978a117691304d4cafed31c279d0102e8df3d1001b9"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 43775,
+ "sha256": "e6ddd157934a3c1d2ba6e6ad7c537a2f16a7a252439d621cd2b4325ab7cfa6ca"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21984,
+ "sha256": "51d08f343fb1a7092fef155407018e7d13063d5c6ddcda07433e86c121c80ad2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-spring--4800433529807892464.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-spring--4800433529807892464.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17536,
+ "sha256": "ff76752fa76a03c673643f27c06a22815a55ebcc9ddad63a6f575ab05a29b4a3"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17605,
+ "sha256": "b905a4146540360f749bce183b009169bf4286a78ef509c7bf6fa0d3e1410dc9"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated.png",
+ "source": "build/alpha303-evidence/medium-0/layered-hydrology-seed-8675309-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 70490,
+ "sha256": "b986359b58d51e3d37dec86a1b997d033d06a7743e945844a60f52f8ef709928"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20459,
+ "sha256": "40d48d1eac9059d112263c449ec35577c9682383d3325985d2d1405eae25c749"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20829,
+ "sha256": "7fd391d092e03f15df5e7dbee9d7a5ce8eda1f0dc99a56f7a532822033cf78b4"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks-spring-9096382656761284700.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks-spring-9096382656761284700.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22828,
+ "sha256": "08cfad6f7a1e267caab3bafad5187178d58f2cda0b3cfab39105297e697cc96e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 54753,
+ "sha256": "0ba2548789882b83bfb6bc28b0457e51c15316e51fb3657186a241e70f5200ad"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 19899,
+ "sha256": "5f3186d2cbcd80599658e28dd124fc98af1ad8879093b3d5d0190955e0605c6f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20297,
+ "sha256": "67edf64346c1661aaa5b196ae46add6686a1d2946ff4d168f5f8c6efb3c74332"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated-spring-9096382656761284700.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated-spring-9096382656761284700.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22359,
+ "sha256": "2b2dc7c572d2592c91044cf49542b7f143420a175403dc4280afa77e51009502"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 54283,
+ "sha256": "5edf4db2b764a99cd0371b8fc49d87a0c757d31a10d11f8b5ec3c3390a24a3d1"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 19530,
+ "sha256": "9d45344408c40a4061aabf07b9ba3848e9f867dfdf04173dac24e686c7a4c542"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks-spring-36992381830938400.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks-spring-36992381830938400.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21556,
+ "sha256": "0c626ef95305ddcdfa37aea142b07e5af7ae98aad8faa99cd7a46f24372a0159"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18377,
+ "sha256": "ed7b1a6432e479bf4bd82b17f2e850d4b8d1d28fb728bf6fd1ad74c764353f96"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 52916,
+ "sha256": "781d927f776904894923cb25a9f3933c4b0728984b0e83ed64d19356c638aa0f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18827,
+ "sha256": "639d236408ff2549ac13bfd531ed577af34ac9f1a4491d91bb2f33c14bc5cda2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated-spring-36992381830938400.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated-spring-36992381830938400.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20887,
+ "sha256": "a8798567692efdeace050094117f150661d3e0174d83c20520a34e8fa83ec41d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17688,
+ "sha256": "3cf3968c50d8aa85508d29bcc2aeb16509ca0528f23adfb7ff3555ae7c8e4c38"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 52297,
+ "sha256": "4c8b552f056d9eebc929c74bf830256b81eae1c2ac0f848bcb15a3fd5617be78"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20951,
+ "sha256": "9fc4da6ff38d11e2edf5cfb7797240bcc3ace643c8d83047c3c7eb95f6b132d9"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks-spring--4800433529807892464.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks-spring--4800433529807892464.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22368,
+ "sha256": "cd25fd2914aea4b09eba5717f986cd654290cd06c11d52e61c39d374f43227e6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21276,
+ "sha256": "58827c08e5e54d21cfcf0e5f5e5c4280dc2ee29b3a8ea254dbbc1616140db0ec"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 57973,
+ "sha256": "6442395acc749af9f0a85767acb048677f891554a0c489608826cd473555585a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20603,
+ "sha256": "96e21e3ccc74e33ba3f1b7922820b52c3729980487a1796371b4dbe6dbbb49f7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated-spring--4800433529807892464.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated-spring--4800433529807892464.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22043,
+ "sha256": "459c5409efdf070ef27b2f148c35ccbc0b8958676d8b54c95b9c6f525763e30e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20984,
+ "sha256": "cc610aa802cd1317c39149d8cce14e24973ec57f04e3f3f66529f3f9cde01816"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated.png",
+ "source": "build/alpha303-evidence/medium-0/natural-hydrology-seed-8675309-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 57729,
+ "sha256": "18b186600075cd8ee3ac9bcdd3292bb4084e345c5f24409c79f07ad519d85553"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 67360,
+ "sha256": "1c2ff1a96e58d8e2a7955c05379f96d42f052ac5e37f99220275aa4a3ac811d7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14221,
+ "sha256": "8fda5b0a536de9bd64e77bef3f91358f2dbb2b340476afe35aaf5d58d47ff2d6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14694,
+ "sha256": "e4c31922bbc7525c1770c52662ea8f1af159ef6d87fa4643f236389a2d968e29"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 34622,
+ "sha256": "982b161950957cddcb169dc00d69addce4733f1d5ccbb32fb37577a49f9a7603"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 28327,
+ "sha256": "88397e27918586c0498e35ce89e3274cf5446002a5f63e6d54c2b2a64920b525"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 28594,
+ "sha256": "30a8f23c5bd6510f30a1dc46e75999adf46b14d1fefb9d2d1355ce6ad86b8955"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27088,
+ "sha256": "c6f0c547a0ee61e4093ebd4ab87707b63752a05560c14be8d5b7999607085ccf"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17588,
+ "sha256": "d1f00486be98d045cf834afb87d6c3581e5c804d8db67be3d2b2587fe4469a7b"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 23293,
+ "sha256": "374346aa8e2542639c4ceffed41ea68c1153f35085f9e3ef71294356d8f13317"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21495,
+ "sha256": "b1d34e94d8f3daecd92268d0b3ddafbb082508ea7841eff516c1af425df5a0f9"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 81329,
+ "sha256": "1806652d3db6699323c03217141693df4538e39578e42278f2408926f1dfa9ff"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 66868,
+ "sha256": "1ccf335c649951f06d0466ab1f22d9abdf1da31ccb3aacff96df3b77b0077d35"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-lava--3864087617497570649.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 13828,
+ "sha256": "016a29795197a8e5bcd74b4686c9a9c7f665fb2f271e37eb5751128570e0bc36"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-lava--480127526815419003.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14295,
+ "sha256": "fe5d4f6f0128d4259ea405081fd66b94c39a1b9ab82eb640a49a1e8edc7e469e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 34198,
+ "sha256": "644b0dff649cf0c7bf91741481b69d648957e27327fbe2f2d2c16d536bbba5b4"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-rift--4486800185823584124.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27925,
+ "sha256": "442840bd18bd66c81639d3c6ba0330299cd69e9fcd738e1a2abfae5b62c17bb6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-rift-5929486622880958566.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 28197,
+ "sha256": "b44c8f3b68899e63a83e1d5e1b5484615101b7cb2925add8c43e52844f00c81e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-rift-7388736356304645874.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 26706,
+ "sha256": "5f7d70adb617453208a2afba0775f6f294308f22d65e3c635f3754e765476fd0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17039,
+ "sha256": "9b2c48e370ff9b4a9c5978d02d51a21ba706adb46268fc874020d78987112cad"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22890,
+ "sha256": "67a5527916e2ac2b78d43432f8c933655a079f8d347ce0928d9e9003cd7eaad5"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated-spring-2318528324537894782.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21002,
+ "sha256": "b21e18ab5936908ac381970cad9f47bbe5d0691656028ec319d5103123aa4064"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-0-generated.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 80767,
+ "sha256": "6f0e87967522c258cc548b8cbca9f91b53cae98acf38c468eb3371b47207aea7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 65772,
+ "sha256": "a9331f5e73cd200ce8a8363084719407110ff6a7a31eae0e20af71156f05c389"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-lava--4133217051590653271.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-lava--4133217051590653271.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14947,
+ "sha256": "39c756130ebbe7d211ac093f50ed5a9e0f21c466e36b9d19fdc9f03a03cddaf2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-lava--8699916113382030230.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-lava--8699916113382030230.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14402,
+ "sha256": "3b3870da367a54512d3b7e6741ffedc0a2c312c67c787f2e312dc8c8c6c87455"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 31903,
+ "sha256": "55f23ae7e9da20c5b2a2e9ab5f85af49f97d8b13536dc7e1f4dc5b7e8a1998e5"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-rift-171155121851024969.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-rift-171155121851024969.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 25375,
+ "sha256": "5d36b808eb6252c642378641e51a752f110452bcf67d9794d6bd6da113ccfdb7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-rift-2302936489416991036.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-rift-2302936489416991036.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 25998,
+ "sha256": "57c3d258fb68db21f5d42899585c6bafd4cc10544941b58fcab175ad20c8bc65"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21430,
+ "sha256": "a1f557380af4e53170331dd296e828864dc9bde9ce230dc9d18a7b69f2a414f0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-spring--470657339720053241.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-spring--470657339720053241.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22206,
+ "sha256": "c198388a2cfb29814926f507143646c50a221565bf7a2a6b20db8fbe65b03ef7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16959,
+ "sha256": "43f3532983b6f9b5bd7471ff853983e90c19b3df915abba03d92dacd4b53410a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 53528,
+ "sha256": "37831537310101571aa32bd2e1acf0381f5c08b819ac8376e67ad9af2ef41bfe"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 65113,
+ "sha256": "26d4e0b2335cb043349b635a5d4a21830a51844cdc30f9d4f36a93d6cf10ec00"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-lava--4133217051590653271.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-lava--4133217051590653271.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14404,
+ "sha256": "be1e5e409616501436d83ff76ff6c65787acf00c8bc5b9f8e8f3abd7d9981c49"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-lava--8699916113382030230.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-lava--8699916113382030230.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 13850,
+ "sha256": "fe4ad2f3b3e40911e2961106ab9bffe28394537df19f1c4d5b3f3c11aa06013d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 31317,
+ "sha256": "d33e419296bad6a74ac6e88dce870377bd373490024d9207832c13e05885aa0b"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-rift-171155121851024969.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-rift-171155121851024969.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 24857,
+ "sha256": "5107fc5fb92194d6b2bd57e847070af91499c070b618c04f8f717999ee4c740a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-rift-2302936489416991036.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-rift-2302936489416991036.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 25476,
+ "sha256": "d5efdff82113c18c68c7e3406d96179a187c58e8d7e1a94b6f59cecd4a658e9e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20770,
+ "sha256": "fe4e0b8c679967a07a77c7dde51cc0c5324738f7325cc1abdd2de14aec1d1d3d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-spring--470657339720053241.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-spring--470657339720053241.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21567,
+ "sha256": "546d7c1b066fc47559addb664a1a7d00e346f5fff928dbba7ad8b4d4e5df577d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16249,
+ "sha256": "c14099ad0fb8bcfc2396ef5e775aae359867be7b12aa3aeca68cce8e1404b61e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-42-generated.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 52847,
+ "sha256": "147507a182652e2760ce72fb0a43815b8e5b7c4fc9d099b9f78d9185659de562"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 65499,
+ "sha256": "b40462d449fd93bb2870e642b1de95cdcafeded32903fbe816216c609d768e2a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-lava--5861423135258671607.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-lava--5861423135258671607.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 15062,
+ "sha256": "1a024dee1f6925481e3e93abe25fbaf2c8ed3f603f106be49dd538153eb50721"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-lava-3423285028654801951.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-lava-3423285028654801951.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 15012,
+ "sha256": "705947af575361fa4da64392c76adf5bd65bb362becea18e16a459bb23dba8e0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 40887,
+ "sha256": "0c0e645eee684cbaa65685f65b83fab0a86675887a2cdd4551c662af74f454a0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-rift--6844189678019355418.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-rift--6844189678019355418.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27691,
+ "sha256": "6619b21f9ec6b37163663bb6d47c2e09d83acd1d102e876aff9c05666dcabca7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-rift-2611836651394526228.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-rift-2611836651394526228.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 29572,
+ "sha256": "49286cd14d384f2158ae6e74986930913fe439a04fb98207917c7c5429e4df54"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-rift-4050165116292789323.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-rift-4050165116292789323.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27697,
+ "sha256": "740cff9c9fea21d8a32426a83438e89581947bc881f239fb57a20d3a2d2a252e"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 23344,
+ "sha256": "f5506bbfdea67dff8cdbe961218ed73cfa13423e748eae72de69871265956f79"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-spring-1281588063776317571.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-spring-1281588063776317571.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22158,
+ "sha256": "d10d4bc34d104414e6e660c2ac4b1da85d132c3e7d21cfad7414fefc5fa790e6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18156,
+ "sha256": "035f3507bb09acf5d7bebca6000d4d19439f3d87ae87e0b4fbb2fbcebd97fea3"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 79579,
+ "sha256": "9d5334e4d0c0e065836bf5eca809e884f692285d23d7af73698e0a4668cbdab0"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 65228,
+ "sha256": "90f8cce162a6948b1ec714c4abeec4f5466129f352ef264f62d3a2bd12a272a6"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-lava--5861423135258671607.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-lava--5861423135258671607.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14887,
+ "sha256": "23705259b0aa1e3fd8cc53d82d5a679ec5792edbefb50b88a50a7e7b5a7296b1"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-lava-3423285028654801951.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-lava-3423285028654801951.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 14833,
+ "sha256": "e7f4d09e0f08884465aeb9614787b9ecac36fa6feedc6385221c160447c7ebec"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 40702,
+ "sha256": "c978cd66c0cfc924955832bb2e5c8c6ac3abd4818bfe573ae19ed83e0cb32fe8"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-rift--6844189678019355418.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-rift--6844189678019355418.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27541,
+ "sha256": "6953075ce1ae5683c327e855c8f87a3b1d490959f477c98ac92d70b2ef5f6ab3"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-rift-2611836651394526228.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-rift-2611836651394526228.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 29431,
+ "sha256": "ec1f4cc7130d1f903b95fa68b335a4458ac0777c04a6d752779fb2e9d11c6fae"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-rift-4050165116292789323.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-rift-4050165116292789323.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 27561,
+ "sha256": "45e666059b7ec0d2cb78dcfa50f68a52d9335246f3063ac08d35a4031f870f47"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 23196,
+ "sha256": "314169a9293ea25ca96751eaff52667757027e0b6187dc5a96f1a19a1b2e65bc"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-spring-1281588063776317571.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-spring-1281588063776317571.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21868,
+ "sha256": "ca91105de3f3099fe17767f00886c292304e851ef1356bc2ca9aa027ee371930"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17836,
+ "sha256": "52ea12f3bc310d6a4a01cc61d702c8be7a1ee4a72214b5a4d33fb86df8f9f0fb"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/rift-seed-8675309-generated.png",
+ "source": "build/alpha303-evidence/medium-0/rift-seed-8675309-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 79325,
+ "sha256": "b2bd47c2cabe88b655d5c5ce6e3134a4b2d09506428a50823c0b8ecba246d583"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/surface-hydrology-seed-0-after-200-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/surface-hydrology-seed-0-after-200-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 47429,
+ "sha256": "c35a9ca0f96cfcbbc5bae8e84742e270916068964413611f2b4d99fff3b17821"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/surface-hydrology-seed-0-generated.png",
+ "source": "build/alpha303-evidence/medium-0/surface-hydrology-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 47147,
+ "sha256": "9b01d9d90e9172f82a6df4c8085bf6c81ca7ad4deaefd63326c84d2e8ee38559"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/surface-hydrology-seed-42-after-200-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/surface-hydrology-seed-42-after-200-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 48053,
+ "sha256": "a8bf3b19c6b96f6a0f99c8ee89bc8653d32d2bff4447fa558ea0de6e4f423ae8"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/surface-hydrology-seed-42-generated.png",
+ "source": "build/alpha303-evidence/medium-0/surface-hydrology-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 47740,
+ "sha256": "27bced2870d96f007208ad592fd5c83c4fb4977805f1307abc6d1f3068b134e8"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/surface-hydrology-seed-8675309-after-200-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/surface-hydrology-seed-8675309-after-200-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 45914,
+ "sha256": "dc460839666da9d98c45c5d65ad4e594e2db7a295d5fdc0f6b6c114d0fe42b91"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/surface-hydrology-seed-8675309-generated.png",
+ "source": "build/alpha303-evidence/medium-0/surface-hydrology-seed-8675309-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 45660,
+ "sha256": "2153e406445af4723f1c1941588d79f22bba7d1804664fb90b6b9ea2bf4361d9"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 56550,
+ "sha256": "b17ccf29014db6c7e9540f7512757bcc1b3df5f4e5ff5f79343428c5d2e60a02"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 39041,
+ "sha256": "200bb12cdbf8c34ee6d0ab9672bcf9935d97638a22881082f090a2b262a6408b"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 19432,
+ "sha256": "72ad99383f9f33c380d9774c6d4940c071cbb18d0d04f4f327a28edff1a95265"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22827,
+ "sha256": "f15b6f4c446bfaff5d2994eb88f3eabb5d904f6fd5dbbdfcba548193b5c5680d"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-spring-9096382656761284700.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks-spring-9096382656761284700.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16756,
+ "sha256": "64fc8be38e0690c2adbc60ff43de07e3682f4c374726e5f5044400b85f8626c2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 64722,
+ "sha256": "a50a5f4b4e59f959025df893a7e3f8988fb1a817d594cc7ad017124fdbcd5e8c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 56031,
+ "sha256": "faffc7dd9b4699dbbc14438a3c1ae111dede5b0d5ebd504a6b952cbb15ec2333"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 38588,
+ "sha256": "a407ec4af8017085eb999b4444b25dcbabe053fe070c2896126cdac21b4afbec"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18898,
+ "sha256": "a97d25b2ebfcd8377225858c15f3f3a41999987e31352f4a78e148cd58e1dc3b"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22360,
+ "sha256": "8c98197d8df8a8c7b7955e30ef607f41759cae286608b082e4de11a25d198885"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-generated-spring-9096382656761284700.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-generated-spring-9096382656761284700.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16203,
+ "sha256": "3ff6bd7713838389102436a3a75a4ccb2a6ab74686d8db12e88cde6bcf716fce"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-0-generated.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 64205,
+ "sha256": "e9a44f307c03b1e6542c418db8cc66b86e0f689eb031a74d522f5118267b853a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 54130,
+ "sha256": "fd7bb60aeb087622b8cf0cd527d63c239dbeb816ed54c15499b3ceb8a3d2bc53"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 39768,
+ "sha256": "4668225f401eee0068e40a9103ea43adb4d731add5ec5873b7fbd04a17a78621"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21575,
+ "sha256": "d67f5f27d1c76a3a4d23e9c457ba58b7db8d8b7895f6ca01d4129af26aeab8d2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-spring-36992381830938400.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-spring-36992381830938400.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17221,
+ "sha256": "1038a565b114fab90974560e7f1404b28762769b9d761f12370f5776eb281e02"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18916,
+ "sha256": "17728cf03e8898d15e652001b87ac071b779afbc987a0b4276f7697db0a445bc"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 59697,
+ "sha256": "e23b154a10c3c6129bfe5cf5ad2dc8ace5dd27bb6df6bc945d1b5ae53ef95ea7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 53502,
+ "sha256": "7196fa7c59fcaf080ab5433a1e9eded05aefd14555f43027239c53084185769c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 39137,
+ "sha256": "52effb75df1901c81960dd58f3929ab8e2e3f067a8bfcf7c538d468a7202ad54"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-generated-spring--441664041222464660.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-generated-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 20918,
+ "sha256": "0938b01cf724a00cdb4af5f5299ad4a7b7e08f1e6496d4f2380373bb65f1282a"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-generated-spring-36992381830938400.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-generated-spring-36992381830938400.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 16509,
+ "sha256": "765dba76ffb92b7519f87968d2ffa3f609dac8794437ef1712f9ddc929ed13a4"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-generated-spring-4671302108368986432.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-generated-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 18183,
+ "sha256": "c209b21e4b7fc7c7f939313a1b267d9c83085c55b5de2b6396f30bd703419fd7"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-42-generated.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 58989,
+ "sha256": "635495d0b947952fc5fc6bc257828422e3e37aeb7480e9bffccb1a90001a58c2"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 54584,
+ "sha256": "07da2041c2050e0f269148cae3a51e3933d75ea15d79241584543ff2a8cb3155"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 36976,
+ "sha256": "dfcb293ba1d33de896da7f540f2b001abca9df085710af3e63a5a6b738c05114"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 22309,
+ "sha256": "96f24f8c29173bb94caaaac0eebbcbdbbf76d2f1fe5c407bd69b4c789908326f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-spring--4800433529807892464.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-spring--4800433529807892464.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17859,
+ "sha256": "76c795422d7515dc0467c0f8e094fe2fee2e8ce03da06cb1b2e3b4cb178b932f"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17939,
+ "sha256": "a58802cafd7b62634cf4158bab7da66b13fd482f1a5ca6cbccc4f9403202d405"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 60201,
+ "sha256": "8817be902b971cea8fd036365102831cec3661f405dc010c6377e36f27e70eb5"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-generated-canopy.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 54280,
+ "sha256": "4b766e9ee3d09b24473006ec40293aedf3e2caa398e2f0681bacb33ccb74d2cd"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-generated-lower-terraces.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 36684,
+ "sha256": "c2c2d006b794cd14e12408189d458515481171e469f7f703a7f6c971a152c7c8"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-generated-spring--1820160500694176469.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-generated-spring--1820160500694176469.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 21992,
+ "sha256": "ffc95215a5a2d0849b6871b2c050b54a25c14640827139139b41747fcca33d15"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-generated-spring--4800433529807892464.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-generated-spring--4800433529807892464.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17526,
+ "sha256": "864bf19115bc62fb1ca768982796a135a6de1bc7a54ff159083328ce4caf845c"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-generated-spring-4284240470134509366.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-generated-spring-4284240470134509366.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 17605,
+ "sha256": "3915dfb0a2c08e2048ed7a6ce2640e33dd2a408fe0d91fc3a04968db715194be"
+ },
+ {
+ "path": "assets/alpha303-evidence/medium-0/woodland-seed-8675309-generated.png",
+ "source": "build/alpha303-evidence/medium-0/woodland-seed-8675309-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 59693,
+ "sha256": "3939f33d094e661b95bf5417a5b4ce8fb89e0e397cad84c2381c5f93e7a0eb00"
+ },
+ {
+ "path": "assets/alpha303-preview/sections-1024--7228211907433324401.png",
+ "source": "build/alpha303-preview/sections-1024--7228211907433324401.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 39012,
+ "sha256": "e990e236b9a10e525229690c86179b7cd971cd543c8069321270adc425ba0d23"
+ },
+ {
+ "path": "assets/alpha303-preview/sections-512-42.png",
+ "source": "build/alpha303-preview/sections-512-42.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 26802,
+ "sha256": "6a32b6ded5ad72b64a7644b3df7679dd2a42b2e82815315ef607e5181c96cb0f"
+ },
+ {
+ "path": "assets/alpha303-preview/sections-724-0.png",
+ "source": "build/alpha303-preview/sections-724-0.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 31471,
+ "sha256": "ee0c096072173252ee7dd530d769a789ab135c12c3b4a0856807726f9eb48eb6"
+ },
+ {
+ "path": "assets/alpha303-preview/sizes--7228211907433324401.png",
+ "source": "build/alpha303-preview/sizes--7228211907433324401.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 112357,
+ "sha256": "17647858b46c89af23b1968ada94031c898ade23ec7a8f198b9d24f514ffff1f"
+ },
+ {
+ "path": "assets/alpha303-preview/sizes-0.png",
+ "source": "build/alpha303-preview/sizes-0.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 106200,
+ "sha256": "5e77e9686968b93e9151ebd7bd823e1df8276daa274f698b00234e28c9394fd6"
+ },
+ {
+ "path": "assets/alpha303-preview/sizes-42.png",
+ "source": "build/alpha303-preview/sizes-42.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.3",
+ "bytes": 108121,
+ "sha256": "0e3e1cc3da08d02bc811e3d4ad829e2289d0d0c645a322f9d76510d980f3a19d"
+ },
+ {
+ "path": "assets/alpha304-evidence/client/0001_sanctuary-alpha304-cliff.png",
+ "source": "build/alpha304-evidence/client/0001_sanctuary-alpha304-cliff.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.4",
+ "bytes": 939935,
+ "sha256": "20f0aabb46cf77014048c7f48fe102a3544c04b40059afda136a1ebac4beae2a"
+ },
+ {
+ "path": "assets/alpha304-evidence/client/0002_sanctuary-alpha304-high-river.png",
+ "source": "build/alpha304-evidence/client/0002_sanctuary-alpha304-high-river.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.4",
+ "bytes": 156498,
+ "sha256": "4f4fdfcdc366ccc2f1863a7aa237d9ddbb28534dd39488b63233734edd8efe07"
+ },
+ {
+ "path": "assets/alpha304-preview/sections-724-0.png",
+ "source": "build/alpha304-preview/sections-724-0.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.4",
+ "bytes": 32288,
+ "sha256": "047e8d8b5b8c36afc076677173ccc8379a0e1c964ee8052c0c9219db85619a8c"
+ },
+ {
+ "path": "assets/alpha304-preview/sizes-0.png",
+ "source": "build/alpha304-preview/sizes-0.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.30.4",
+ "bytes": 108901,
+ "sha256": "4a0c7aa945851cb2e932d970bf3690579e831c48edbf12594ad779fb2c7f0c69"
+ },
+ {
+ "path": "assets/alpha306-village-review.png",
+ "source": "build/alpha306-village-review.png",
+ "category": "alpha-figures",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 41131,
+ "sha256": "2c96810eb1ba0ab7db2b7e08524df6aabdeb1d067a1bc17e5be48f0e6272c007"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-canopy.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 66806,
+ "sha256": "5f132271bea2a49f22c22247e6828425159f0e4765a9af44a524916862e14fe4"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14221,
+ "sha256": "8fda5b0a536de9bd64e77bef3f91358f2dbb2b340476afe35aaf5d58d47ff2d6"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14694,
+ "sha256": "e4c31922bbc7525c1770c52662ea8f1af159ef6d87fa4643f236389a2d968e29"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 36336,
+ "sha256": "7c271f7ef103ae6044b20dc6971bb9f6c4873083011263b22ac6dac2b2c70b64"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 28348,
+ "sha256": "9fc532b73600140396520949fd8bf6b2f7d4cf54317be5c81a9e815fd5c81ebc"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 28594,
+ "sha256": "30a8f23c5bd6510f30a1dc46e75999adf46b14d1fefb9d2d1355ce6ad86b8955"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 26996,
+ "sha256": "f07c55ed734d6b6175abe0ca4cb2899f60c094c12111fb6ee238dca1bad6524d"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 17588,
+ "sha256": "d1f00486be98d045cf834afb87d6c3581e5c804d8db67be3d2b2587fe4469a7b"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 23293,
+ "sha256": "374346aa8e2542639c4ceffed41ea68c1153f35085f9e3ef71294356d8f13317"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 21371,
+ "sha256": "f497fb20971dd0bfd1d340e259eafec06e7e45d0b52499a858478c48ae02fcfa"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-after-1800-ticks.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 81290,
+ "sha256": "8871575382614de52656c04dd8e5e14ced8ac98a6dfa1db724ba568b29bbb776"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-canopy.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 66310,
+ "sha256": "69cf7e6519aa70927c2d6107d9d122a30108b495f7b89e3583623e3fc15703e3"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-lava--3864087617497570649.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 13828,
+ "sha256": "016a29795197a8e5bcd74b4686c9a9c7f665fb2f271e37eb5751128570e0bc36"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-lava--480127526815419003.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14295,
+ "sha256": "fe5d4f6f0128d4259ea405081fd66b94c39a1b9ab82eb640a49a1e8edc7e469e"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 35797,
+ "sha256": "9f23824fdb89761e573c99a91bfc961c6642d4940f45e1d37a94cdef43aabf66"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-rift--4486800185823584124.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 27949,
+ "sha256": "5d44394363a7327efa324505b1f8cb0f5006e11ac7274c428b07bc92b0e02dd6"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-rift-5929486622880958566.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 28197,
+ "sha256": "b44c8f3b68899e63a83e1d5e1b5484615101b7cb2925add8c43e52844f00c81e"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-rift-7388736356304645874.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 26611,
+ "sha256": "01d0c54959b9d34548272f19fcae2b8256c32f1db35a123248ccbbea1f9b70ca"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 17039,
+ "sha256": "9b2c48e370ff9b4a9c5978d02d51a21ba706adb46268fc874020d78987112cad"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 22890,
+ "sha256": "67a5527916e2ac2b78d43432f8c933655a079f8d347ce0928d9e9003cd7eaad5"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated-spring-2318528324537894782.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 20879,
+ "sha256": "70a69371982c024513e127c7556a8e7b1744b0cc7b3ae2eab5990a7597ed5eb4"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-0-generated.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 80693,
+ "sha256": "aaad93c08a50f9c02594b42c79360d8adc540d990f404900b91ff98800f42499"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-canopy.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 65491,
+ "sha256": "35c01c0f9459fc06a35b04f1acb7869b47c03ce492a670e1bc0ff8834965d477"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-lava--4133217051590653271.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-lava--4133217051590653271.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14947,
+ "sha256": "39c756130ebbe7d211ac093f50ed5a9e0f21c466e36b9d19fdc9f03a03cddaf2"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-lava--8699916113382030230.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-lava--8699916113382030230.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14402,
+ "sha256": "3b3870da367a54512d3b7e6741ffedc0a2c312c67c787f2e312dc8c8c6c87455"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 33980,
+ "sha256": "2a448121f99a566d2c494e09f8bd8997c1a61f576970ad29e85994f773233038"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-rift-171155121851024969.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-rift-171155121851024969.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 25375,
+ "sha256": "ce112038efa26e34b2981da163e9ca661a22f841ca82a25d8c41ec7c7e7e0f5e"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-rift-2302936489416991036.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-rift-2302936489416991036.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 25998,
+ "sha256": "57c3d258fb68db21f5d42899585c6bafd4cc10544941b58fcab175ad20c8bc65"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 21430,
+ "sha256": "a1f557380af4e53170331dd296e828864dc9bde9ce230dc9d18a7b69f2a414f0"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-spring--470657339720053241.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-spring--470657339720053241.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 22206,
+ "sha256": "c198388a2cfb29814926f507143646c50a221565bf7a2a6b20db8fbe65b03ef7"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 16959,
+ "sha256": "43f3532983b6f9b5bd7471ff853983e90c19b3df915abba03d92dacd4b53410a"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-after-1800-ticks.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 53593,
+ "sha256": "311b47031c6cab933aea256fc77d033cf5ec5146448b54f146b86d40dfefac00"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-canopy.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 64865,
+ "sha256": "0b3cda9d8d9acc6c73b30eb0b0d9fd85ba3be6c883983551c8d8923b56d82ffe"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-lava--4133217051590653271.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-lava--4133217051590653271.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14404,
+ "sha256": "be1e5e409616501436d83ff76ff6c65787acf00c8bc5b9f8e8f3abd7d9981c49"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-lava--8699916113382030230.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-lava--8699916113382030230.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 13850,
+ "sha256": "fe4ad2f3b3e40911e2961106ab9bffe28394537df19f1c4d5b3f3c11aa06013d"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-lower-terraces.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 33410,
+ "sha256": "5bc39c2826ac1eaf50651b69da235d91616f2298b24060a35cabe29116c6580c"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-rift-171155121851024969.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-rift-171155121851024969.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 24857,
+ "sha256": "27eeb27f309c4f5c23f74b59f29c660c271ba0b25d36ce6e6b935ed2bca5637d"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-rift-2302936489416991036.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-rift-2302936489416991036.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 25476,
+ "sha256": "d5efdff82113c18c68c7e3406d96179a187c58e8d7e1a94b6f59cecd4a658e9e"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-spring--441664041222464660.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-spring--441664041222464660.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 20770,
+ "sha256": "fe4e0b8c679967a07a77c7dde51cc0c5324738f7325cc1abdd2de14aec1d1d3d"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-spring--470657339720053241.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-spring--470657339720053241.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 21567,
+ "sha256": "546d7c1b066fc47559addb664a1a7d00e346f5fff928dbba7ad8b4d4e5df577d"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated-spring-4671302108368986432.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated-spring-4671302108368986432.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 16249,
+ "sha256": "c14099ad0fb8bcfc2396ef5e775aae359867be7b12aa3aeca68cce8e1404b61e"
+ },
+ {
+ "path": "assets/alpha8-before-grove-order/rift-seed-42-generated.png",
+ "source": "build/alpha8-before-grove-order/rift-seed-42-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 52927,
+ "sha256": "fd13ecaa159fc54736cf1ed1cdb4e817189e7da6549ae00989098021506f27f2"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-canopy.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 67025,
+ "sha256": "23493de257716bed742e73d0be585f26bf90886cbd12cee771d85e2624d5276a"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14221,
+ "sha256": "8fda5b0a536de9bd64e77bef3f91358f2dbb2b340476afe35aaf5d58d47ff2d6"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14694,
+ "sha256": "e4c31922bbc7525c1770c52662ea8f1af159ef6d87fa4643f236389a2d968e29"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 39242,
+ "sha256": "102bf08743698b3c51bc88dcc1ceed026a0c79ef83d99a858a478fd035d8d5d4"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 28374,
+ "sha256": "86df37568b83204cfebee4c4bfa06ba5703ba2fdf983f19ca0901dfed69ec6db"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 28620,
+ "sha256": "b10216d7c2f071c1bb6408e699b15c9ef5e430908445a7f0da27e28239dc3e45"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 27006,
+ "sha256": "849e1104079cef2b335dfb366cfaabce87d09570fd7bd30c4eeb6af2b6be0f8e"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 17588,
+ "sha256": "d1f00486be98d045cf834afb87d6c3581e5c804d8db67be3d2b2587fe4469a7b"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 23293,
+ "sha256": "374346aa8e2542639c4ceffed41ea68c1153f35085f9e3ef71294356d8f13317"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 22275,
+ "sha256": "e122c454aed3680a226e12dd0a63ab39e13e1b25e72331fca8e5919bfc10230b"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-after-1800-ticks.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 80531,
+ "sha256": "f695233b1ffc677f4aaacaf3e1d058d88e1bbe7266f0d25ca3cb023bb2f2d6f5"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-canopy.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 66529,
+ "sha256": "6fa4c66e5febeb92e0e3f4775f952b74a015b71d8dfbe62402d4ec8a9ff61dfb"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-lava--3864087617497570649.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 13828,
+ "sha256": "016a29795197a8e5bcd74b4686c9a9c7f665fb2f271e37eb5751128570e0bc36"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-lava--480127526815419003.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 14295,
+ "sha256": "fe5d4f6f0128d4259ea405081fd66b94c39a1b9ab82eb640a49a1e8edc7e469e"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 38712,
+ "sha256": "a55559c6d58ca85410266056a8de305878d691bdbfb596d9ce104be30704354d"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-rift--4486800185823584124.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 27977,
+ "sha256": "0f7e35160ac086bdad9e3a2348163f81e28c0b0bc0849bd573b5f78ef75868d1"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-rift-5929486622880958566.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 28222,
+ "sha256": "1eaa28d86241a3d23748177fa291522126a3f14827409c5d718609807b7ccb1f"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-rift-7388736356304645874.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 26621,
+ "sha256": "b6b036a314b1dec43b3550827f03165b084d754b509a943e18adc6606b62a410"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 17039,
+ "sha256": "9b2c48e370ff9b4a9c5978d02d51a21ba706adb46268fc874020d78987112cad"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 22890,
+ "sha256": "67a5527916e2ac2b78d43432f8c933655a079f8d347ce0928d9e9003cd7eaad5"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated-spring-2318528324537894782.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 21765,
+ "sha256": "ecafafe01c9029d16723637738141a2450947bff14ac8c4249f97a060d68f184"
+ },
+ {
+ "path": "assets/alpha8-first-pass/rift-seed-0-generated.png",
+ "source": "build/alpha8-first-pass/rift-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.8",
+ "bytes": 79914,
+ "sha256": "9e633947af888164ed57c5eea706bf487fb4069f507cd3f3d9abb29635639d18"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-canopy.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 67160,
+ "sha256": "b85146b74578ae132f6f22e0cee4a32f2bc743b01cbf2b021c0a0f8f1e4a4d1c"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 14221,
+ "sha256": "8fda5b0a536de9bd64e77bef3f91358f2dbb2b340476afe35aaf5d58d47ff2d6"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 14694,
+ "sha256": "e4c31922bbc7525c1770c52662ea8f1af159ef6d87fa4643f236389a2d968e29"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-lower-terraces.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 39077,
+ "sha256": "8dcd09f0800a615cc7b5317a1297080528def65ee304c8e8c9cd7ee882311e08"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 28334,
+ "sha256": "397444cfd63659b54212de3e564a9b34d29a26d0feb59fec903d1048d84a27fb"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 28431,
+ "sha256": "cac883360e62c84a3339bf3fb3e2e7a4ac7663c33a4d87b4589713c9f501531d"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 27086,
+ "sha256": "4291acef7fe4f379b48fb571c56bce7c4022777d70e137c0bd20e2ccc181944a"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 17588,
+ "sha256": "d1f00486be98d045cf834afb87d6c3581e5c804d8db67be3d2b2587fe4469a7b"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 23294,
+ "sha256": "2ae2dba486c1f0457db73dff3de218dcce1fa1b11d57fe9e2dd39ee994cf4459"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 21374,
+ "sha256": "9ea54942b6471cb729c8a1fdee0862292535e5a8d868b422763c43d3623fa2fd"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-after-1800-ticks.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 80829,
+ "sha256": "b98655eb56e740e18c6ca952eb4c339d2d60e835756cae2ea198fa04e8536219"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-canopy.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-canopy.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 66648,
+ "sha256": "65b934956fd434ee80ea1929c8557f53943566e1877829361236a9d3c17aad7e"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-lava--3864087617497570649.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-lava--3864087617497570649.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 13828,
+ "sha256": "016a29795197a8e5bcd74b4686c9a9c7f665fb2f271e37eb5751128570e0bc36"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-lava--480127526815419003.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-lava--480127526815419003.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 14295,
+ "sha256": "fe5d4f6f0128d4259ea405081fd66b94c39a1b9ab82eb640a49a1e8edc7e469e"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-lower-terraces.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-lower-terraces.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 38524,
+ "sha256": "b4b86e02da0e277a3c833b277c9bbb8789c90b91065979bb15a4a5ed58502b13"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-rift--4486800185823584124.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-rift--4486800185823584124.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 27933,
+ "sha256": "9719bd6bbfe77135d1b9a009619f9c5df11ed3565ebaa6472627c9cea40a5003"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-rift-5929486622880958566.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-rift-5929486622880958566.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 28037,
+ "sha256": "857445e0e479619cb19e3172250fcf68ec310165643c014c778441b4aaa34307"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-rift-7388736356304645874.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-rift-7388736356304645874.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 26701,
+ "sha256": "66bf0ec959131a8e7cb63dbde88b0cc65d1010de209fed118976cfb8473d0833"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-spring--1028411685902509681.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-spring--1028411685902509681.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 17039,
+ "sha256": "9b2c48e370ff9b4a9c5978d02d51a21ba706adb46268fc874020d78987112cad"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-spring--2594869253978650387.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-spring--2594869253978650387.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 22893,
+ "sha256": "40e28ec05398f1beeba079ea0ae6a89d10aca5ef7302c8d4c759eb0d81ce2df2"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated-spring-2318528324537894782.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated-spring-2318528324537894782.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 20882,
+ "sha256": "97b63cce1f2c601a1e6abc31411415e4760d31e14f3fe8f3a9ae026aa17c6e2a"
+ },
+ {
+ "path": "assets/alpha9-first-terrace-pass/cavern-seed-0-generated.png",
+ "source": "build/alpha9-first-terrace-pass/cavern-seed-0-generated.png",
+ "category": "alpha-research",
+ "status": "development-output",
+ "version_hint": "alpha.9",
+ "bytes": 80298,
+ "sha256": "5e3f687ffffe008baecf3a402898ca57fd0ca64216e2f9a6a39824d0088cc9f6"
+ },
+ {
+ "path": "assets/atlas305-first/terrain-1024-0/maps.pdf",
+ "source": "build/atlas305-first/terrain-1024-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 136626,
+ "sha256": "eea3ae87cabad2bb2ffab648b2a2c628a3660d7d5df1d5ad92791de8b77e7ab5"
+ },
+ {
+ "path": "assets/atlas305-first/terrain-1024-0/maps.png",
+ "source": "build/atlas305-first/terrain-1024-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 330078,
+ "sha256": "3a449ce94d461c478bfde95f6cb85985c52dc94d54c4594315f4cbfa404c934d"
+ },
+ {
+ "path": "assets/atlas305-first/terrain-1024-0/sections.pdf",
+ "source": "build/atlas305-first/terrain-1024-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 749835,
+ "sha256": "35ed5998889a2cc165027b29588ac5ffdc122bd302c8f6d5b1e92802e785dee4"
+ },
+ {
+ "path": "assets/atlas305-first/terrain-1024-0/sections.png",
+ "source": "build/atlas305-first/terrain-1024-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1399851,
+ "sha256": "5b1eb909c34d907f78a7a7f0823cefe2376986e7805b064f8a78f5cc26f3cda5"
+ },
+ {
+ "path": "assets/atlas305/summary.json",
+ "source": "build/atlas305/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 13001,
+ "sha256": "8179cae15f310cb25f4da2c092248ff5ef1b75321a041c8db8cc082e2a9e023d"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024--7228211907433324401/maps.pdf",
+ "source": "build/atlas305/terrain-1024--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 144541,
+ "sha256": "18afdda1f1a4a70f101ffd809ac47f6590c46e6e15e71583c03b5e0612dbb219"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024--7228211907433324401/maps.png",
+ "source": "build/atlas305/terrain-1024--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 341624,
+ "sha256": "ef8e815877a16cd398aa11303575e1f10138032685850a5187e69ab2dcfcea9a"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024--7228211907433324401/provenance.json",
+ "source": "build/atlas305/terrain-1024--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 626,
+ "sha256": "0166b1a7c7951fd309509da8de8a131607f4b25dd9f61bd18f8ec6e1aafac6c5"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024--7228211907433324401/sections.pdf",
+ "source": "build/atlas305/terrain-1024--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 750926,
+ "sha256": "ca471280834f940beff6dda1cf8eb5aa708da4977518b4e2ec02b20bc2051bb7"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024--7228211907433324401/sections.png",
+ "source": "build/atlas305/terrain-1024--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1418614,
+ "sha256": "2076cb21a2d0784bc6a12b509c8ef28449892a3895aae18a3430bc4bcbcc2d8b"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024--7228211907433324401/summary.json",
+ "source": "build/atlas305/terrain-1024--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1373,
+ "sha256": "fab575d915c89b0c96828d77045116e6a03cb38358b5a82c0d76dbb6f98e7c53"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-0/maps.pdf",
+ "source": "build/atlas305/terrain-1024-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 136626,
+ "sha256": "80651c8f97338e5e2e8c14ef28ee0bd8e2783aec01e20ebeeb43880088325bfd"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-0/maps.png",
+ "source": "build/atlas305/terrain-1024-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 330078,
+ "sha256": "3a449ce94d461c478bfde95f6cb85985c52dc94d54c4594315f4cbfa404c934d"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-0/provenance.json",
+ "source": "build/atlas305/terrain-1024-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 607,
+ "sha256": "5d888cea04b0a5aa57e7047defe6783e8cd42af126418379e2ae9231d86f89e5"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-0/sections.pdf",
+ "source": "build/atlas305/terrain-1024-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 749835,
+ "sha256": "6ccbafda9fdbf16387692c1416ca31ab0cf81d570daef9d2f592decef145e3c4"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-0/sections.png",
+ "source": "build/atlas305/terrain-1024-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1399851,
+ "sha256": "5b1eb909c34d907f78a7a7f0823cefe2376986e7805b064f8a78f5cc26f3cda5"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-0/summary.json",
+ "source": "build/atlas305/terrain-1024-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1365,
+ "sha256": "4d3a76ff1a27907d30b40d12497db3f1dfd1c7f11227c9573787c0dac57a2c14"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-42/maps.pdf",
+ "source": "build/atlas305/terrain-1024-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 141311,
+ "sha256": "e7594f192c72535e03d42fa6289fc237fbcf92bd3a8f34a12d0ae278b1e5bf4d"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-42/maps.png",
+ "source": "build/atlas305/terrain-1024-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 336536,
+ "sha256": "8d6daec7f3bebbeed3a6593029343865f477e1ccfba7bbd2e15118063f2f755c"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-42/provenance.json",
+ "source": "build/atlas305/terrain-1024-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 608,
+ "sha256": "b1030a5e53362a02de4f52162f08071e12e9c60d65574b2605e2d99afac9bf80"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-42/sections.pdf",
+ "source": "build/atlas305/terrain-1024-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 738157,
+ "sha256": "c5120c966bf9df94c53e263c45cab6983dc6af8c14bc567ed5f430cdfb7a0b99"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-42/sections.png",
+ "source": "build/atlas305/terrain-1024-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1407456,
+ "sha256": "a4adae2d8603edad730508dac0b17758f5ccc844027dbefa3fffa51c3be826e4"
+ },
+ {
+ "path": "assets/atlas305/terrain-1024-42/summary.json",
+ "source": "build/atlas305/terrain-1024-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1368,
+ "sha256": "b44b34e6b72c5d5267df8fef6779756e2ee333f5ad8378660352e02a2b72742e"
+ },
+ {
+ "path": "assets/atlas305/terrain-512--7228211907433324401/maps.pdf",
+ "source": "build/atlas305/terrain-512--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 78362,
+ "sha256": "26f06e223b0382bb769de062b853f37a8e7a2894cfee7bc300dd05547899224c"
+ },
+ {
+ "path": "assets/atlas305/terrain-512--7228211907433324401/maps.png",
+ "source": "build/atlas305/terrain-512--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 224511,
+ "sha256": "ebc94cf1cd1eede5edb9cf81165bfcaf91f548cfc2fedb2ea35e486f2d7dae21"
+ },
+ {
+ "path": "assets/atlas305/terrain-512--7228211907433324401/provenance.json",
+ "source": "build/atlas305/terrain-512--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 625,
+ "sha256": "b344697f410ea7fc831b5495d191fd9028019dd7a1434095d5f955fc151ed972"
+ },
+ {
+ "path": "assets/atlas305/terrain-512--7228211907433324401/sections.pdf",
+ "source": "build/atlas305/terrain-512--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 329136,
+ "sha256": "be4131cf4299ba1214f520e4cef301ee27b6b839b719a84c4da909ba06f2b51a"
+ },
+ {
+ "path": "assets/atlas305/terrain-512--7228211907433324401/sections.png",
+ "source": "build/atlas305/terrain-512--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 736802,
+ "sha256": "34c73d9147388152d14607476a9131a11727f7070131bf369aea6b382e530e09"
+ },
+ {
+ "path": "assets/atlas305/terrain-512--7228211907433324401/summary.json",
+ "source": "build/atlas305/terrain-512--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1377,
+ "sha256": "eb64b480c8fcad55d47322fd5ff8fe23d241530c44abdff2cdde46617155e979"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-0/maps.pdf",
+ "source": "build/atlas305/terrain-512-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 79576,
+ "sha256": "0235efe20e16778b867e16893e0853fa2590cb0688fb421d4f98c3c96a32fbe9"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-0/maps.png",
+ "source": "build/atlas305/terrain-512-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 223282,
+ "sha256": "ec05cc8bbb7c62eba98670256f7a0038b15295c1b01750749c5b853f6b077b54"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-0/provenance.json",
+ "source": "build/atlas305/terrain-512-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 606,
+ "sha256": "8a46774c5654a23b08c50e68664df61acd852ad4faa9d726779c3739ab90bed8"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-0/sections.pdf",
+ "source": "build/atlas305/terrain-512-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 373068,
+ "sha256": "03ed17559e32de27fa47cc91fcc289f434e9bbc16863f78211aa4a228f90da3d"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-0/sections.png",
+ "source": "build/atlas305/terrain-512-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 804773,
+ "sha256": "b4e1048888a5e8d98a94c800334dfae3de55bfcd9cc0c5d81eab59f596ca5dda"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-0/summary.json",
+ "source": "build/atlas305/terrain-512-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1357,
+ "sha256": "4719e8ebf21de506b12885b1284796018a335c8c884dd7f03790ab25d970f695"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-42/maps.pdf",
+ "source": "build/atlas305/terrain-512-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 77867,
+ "sha256": "c490bedce1959b55befb4ba2e02c0e73d07106f639713e206098b3a4049cc55b"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-42/maps.png",
+ "source": "build/atlas305/terrain-512-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 223052,
+ "sha256": "76b4ce01bf8c9eee9b6f9f7d702a22d3dfe739db2832d517c440dde4aa95f960"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-42/provenance.json",
+ "source": "build/atlas305/terrain-512-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 607,
+ "sha256": "a582d4f37a1261852de54782e22fd150ef66b0f9d792cf4134cccd224ed3b0e1"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-42/sections.pdf",
+ "source": "build/atlas305/terrain-512-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 357787,
+ "sha256": "99a1f2ca007851093017dc362638bf85e0467b399f2cff2a814df020805d4b0e"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-42/sections.png",
+ "source": "build/atlas305/terrain-512-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 786449,
+ "sha256": "d911e2f15399fdf3e8259a783121cfef549cf968830328365118b17070e5a710"
+ },
+ {
+ "path": "assets/atlas305/terrain-512-42/summary.json",
+ "source": "build/atlas305/terrain-512-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1357,
+ "sha256": "dbc623f54e6d2cd34ee32fdb6381d38a99c3e3c589debd06506bba1d41dc5e8c"
+ },
+ {
+ "path": "assets/atlas305/terrain-724--7228211907433324401/maps.pdf",
+ "source": "build/atlas305/terrain-724--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 101847,
+ "sha256": "edc60aa48267f3a6231301c4376cce5e241950f3cb97bbf8199584090698ddac"
+ },
+ {
+ "path": "assets/atlas305/terrain-724--7228211907433324401/maps.png",
+ "source": "build/atlas305/terrain-724--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 271225,
+ "sha256": "8f6cff95f2cc08a43ebe8220393d9380e6f3e44b1914479f9adb60ac9f17e717"
+ },
+ {
+ "path": "assets/atlas305/terrain-724--7228211907433324401/provenance.json",
+ "source": "build/atlas305/terrain-724--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 625,
+ "sha256": "db2ffa2abddf5a4992a9ac512b33bccaa516888cd9094c097b3572bf0109d45e"
+ },
+ {
+ "path": "assets/atlas305/terrain-724--7228211907433324401/sections.pdf",
+ "source": "build/atlas305/terrain-724--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 527224,
+ "sha256": "74309e16875d83faab4b6e2b3be48fa7d46ec0b9e25c5429ec39b41221fa9122"
+ },
+ {
+ "path": "assets/atlas305/terrain-724--7228211907433324401/sections.png",
+ "source": "build/atlas305/terrain-724--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1077887,
+ "sha256": "c94811f45f95f2d2bf7ddd6d8f92862050cfd1c003184480ff7f21a7153fc4f3"
+ },
+ {
+ "path": "assets/atlas305/terrain-724--7228211907433324401/summary.json",
+ "source": "build/atlas305/terrain-724--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1378,
+ "sha256": "56e060f8be58efd1a80b8ec5d24fd175f0963150745c78859f91601735397f6b"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-0/maps.pdf",
+ "source": "build/atlas305/terrain-724-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 98488,
+ "sha256": "381730f99119291ab4a4924fec72cf185c45189722f1041f3bbf9b3043b8d33d"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-0/maps.png",
+ "source": "build/atlas305/terrain-724-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 265537,
+ "sha256": "5e86f1bf3e75b77e9c2c117617145404da7ac2c0bf0de8902d1a6b27a4862347"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-0/provenance.json",
+ "source": "build/atlas305/terrain-724-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 606,
+ "sha256": "cd8a7325ccd85d8292ce7d8c3292774266b24f59fa0fbd9dcb61be44ee73d46f"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-0/sections.pdf",
+ "source": "build/atlas305/terrain-724-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 536267,
+ "sha256": "7dbf89ed8e9110d80fd4505edb3c51434b33bdd89b488314450cf705709ff07c"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-0/sections.png",
+ "source": "build/atlas305/terrain-724-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1085957,
+ "sha256": "ea2abb4a6f9734f809075f2df4c80e61704913dbd4b32c77efc4f3d27316863e"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-0/summary.json",
+ "source": "build/atlas305/terrain-724-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1366,
+ "sha256": "00fc58593a630835712635d1eb14b7fa55d3423f4706b8c6f87cc7a288d8aa9b"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-42/maps.pdf",
+ "source": "build/atlas305/terrain-724-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 97369,
+ "sha256": "13aa3b46a27c66f0738d5ea3b19f4b1c700ec22039a8856825d8b0f1460b2242"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-42/maps.png",
+ "source": "build/atlas305/terrain-724-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 265473,
+ "sha256": "30c165922ade6aa6bd64614ed131e937bb070e57035ece25c09ba8eeca1a02fc"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-42/provenance.json",
+ "source": "build/atlas305/terrain-724-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 607,
+ "sha256": "c44c79b66347bbbfdd1204a553c9eeaa0943cac605e4d27d614a9f4b5568a4fe"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-42/sections.pdf",
+ "source": "build/atlas305/terrain-724-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 538583,
+ "sha256": "2f08f39b101de3efcc477a0733ad9eeffd112bc6cff0ba3be1932ae0a4472dbd"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-42/sections.png",
+ "source": "build/atlas305/terrain-724-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1094950,
+ "sha256": "f8bec5ae2617784dbf51f9ae2437111b6fc75a4784750ea288d23dd8293a7dd8"
+ },
+ {
+ "path": "assets/atlas305/terrain-724-42/summary.json",
+ "source": "build/atlas305/terrain-724-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.5",
+ "bytes": 1364,
+ "sha256": "21b63544aab45b9f741ad657883dca9cff79ebcc0577017919fb6c7ba554ab75"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-1024--7228211907433324401.json",
+ "source": "build/atlas306/raw/terrain-1024--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 9181055,
+ "sha256": "ff5bf1c3152ae4b42233fe0d73c335a59643b423c055c0ced4aed8a55bfa582e"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-1024-0.json",
+ "source": "build/atlas306/raw/terrain-1024-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 9132777,
+ "sha256": "c55d87cb62ca856a61e8dafb02c670b6296493fd940f7cccfbfd445ba85b020d"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-1024-42.json",
+ "source": "build/atlas306/raw/terrain-1024-42.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 9201263,
+ "sha256": "d23b42afd1588e0f6eba6c9c14e806f5ec33929cdcaff5aa6e7def8d98d2c21c"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-512--7228211907433324401.json",
+ "source": "build/atlas306/raw/terrain-512--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 4833123,
+ "sha256": "5947af08d47c42bf5ed89e79eaf7b17006f2d3ab05a7d2d4dd15964e849804d3"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-512-0.json",
+ "source": "build/atlas306/raw/terrain-512-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 4767726,
+ "sha256": "1940cd3eb65dc6eddccdafbb9af470a125e20924d89a1448ac938cc4314a3a43"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-512-42.json",
+ "source": "build/atlas306/raw/terrain-512-42.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 4788175,
+ "sha256": "bf6f3dfe966a4e4bebc74cfd2c10e1a388dd5b4a9edc479ff8f21c8c564cd74a"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-724--7228211907433324401.json",
+ "source": "build/atlas306/raw/terrain-724--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 6561030,
+ "sha256": "1ba9211ba877429a8cadcc06b3178925c67dd1af4a9a108a513191525a3059be"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-724-0.json",
+ "source": "build/atlas306/raw/terrain-724-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 6503784,
+ "sha256": "061b6819b03c5adbfe2c06557f515fee80767f526007fd14bc6d20ddc3441a88"
+ },
+ {
+ "path": "assets/atlas306/raw/terrain-724-42.json",
+ "source": "build/atlas306/raw/terrain-724-42.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 6555978,
+ "sha256": "ad5f56d2b8de978f392b9a6dc8fe0f6f3efa5af80e09069c7072ec45503cc9cf"
+ },
+ {
+ "path": "assets/atlas306/structures-grand/structures-1024--7228211907433324401-reseau.pdf",
+ "source": "build/atlas306/structures-grand/structures-1024--7228211907433324401-reseau.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 49475,
+ "sha256": "4a3e59738cc1a1e40c1348a966ba19d15b1a1e4643821a01d2d733c04eda1ead"
+ },
+ {
+ "path": "assets/atlas306/structures-grand/structures-1024--7228211907433324401-reseau.png",
+ "source": "build/atlas306/structures-grand/structures-1024--7228211907433324401-reseau.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 113201,
+ "sha256": "c73db72323fd7f9691e5c9d690d8f09f8a8055c0b59c08ffcdeee471914ebaed"
+ },
+ {
+ "path": "assets/atlas306/structures-grand/structures-1024--7228211907433324401-summary.json",
+ "source": "build/atlas306/structures-grand/structures-1024--7228211907433324401-summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 434,
+ "sha256": "25e1225cb17dfafc79ff2bfaa7b767a9373f504e3f4086fac13b904b7ec8c422"
+ },
+ {
+ "path": "assets/atlas306/structures-medium0/structures-724-0-reseau.pdf",
+ "source": "build/atlas306/structures-medium0/structures-724-0-reseau.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 42990,
+ "sha256": "117285b967a436af729d8d24421272683f2744fc87a2b358b1d940c1b937c895"
+ },
+ {
+ "path": "assets/atlas306/structures-medium0/structures-724-0-reseau.png",
+ "source": "build/atlas306/structures-medium0/structures-724-0-reseau.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 107773,
+ "sha256": "3892655e804da1de65c8745d853473f1b5786f3d59ef9165958f3fa5a5a9e007"
+ },
+ {
+ "path": "assets/atlas306/structures-medium0/structures-724-0-summary.json",
+ "source": "build/atlas306/structures-medium0/structures-724-0-summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 410,
+ "sha256": "fe037fd0106b45de193ed9658a5dfe99a5fbacdaa4977e06cb80855725fa6aa6"
+ },
+ {
+ "path": "assets/atlas306/structures-medium0/structures-724-0-village.pdf",
+ "source": "build/atlas306/structures-medium0/structures-724-0-village.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 28575,
+ "sha256": "89ef4dca2b724152fe8830f0cd8df2927f30f99e6ff468bb3552eb8ee51272e9"
+ },
+ {
+ "path": "assets/atlas306/structures-medium0/structures-724-0-village.png",
+ "source": "build/atlas306/structures-medium0/structures-724-0-village.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 127063,
+ "sha256": "006e9108d4936fa9536d8fc8e2207bee36eb6bc137bad546cf3b8f8c8c1544b5"
+ },
+ {
+ "path": "assets/atlas306/summary.json",
+ "source": "build/atlas306/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 12998,
+ "sha256": "1c13efac0a30bc896e30a646331656c90a35982ad2e15005d97adada55316ea0"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/maps.pdf",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 143451,
+ "sha256": "34ee759ec834863e0e1496000f9c01d2075ade9936be6200d95239efeadbd672"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/maps.png",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 334280,
+ "sha256": "430dc14e20f63cda25aafed2ac1b02183b4e0e995bcfa37718d4f114d6e8686b"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/provenance.json",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 630,
+ "sha256": "db35e8017555595eddeb876e5821c0a8f494d4f43403a875fdc28f4e4a0f179d"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/sections.pdf",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 745433,
+ "sha256": "cf3055f211083c50a1355c6de7658ccc5f9e483e03aa2cfba9f6d7caf837de46"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/sections.png",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1408971,
+ "sha256": "8c0381bda87ee1b44e10f27535186f551dc5a08ce183f2663fa4b362e64aa855"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/summary.json",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1374,
+ "sha256": "425bb9efd805d6b5bf936dc46722408263e71ec6a9a0dbc12eedaaa7241a37d3"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 21588,
+ "sha256": "b705588647539af941bef022e4c58d0e67493e3d3c48ba11a377c53bea9f2172"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024--7228211907433324401/upper-terrain.png",
+ "source": "build/atlas306/terrain-1024--7228211907433324401/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 85984,
+ "sha256": "0d8c4ca0f05a211a8c72485ae3c2c158f1be97b766cff666d1db96e4c73f2054"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/maps.pdf",
+ "source": "build/atlas306/terrain-1024-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 135490,
+ "sha256": "b886d5a486c778ab44ca730f6e80dc4a8ddb54ae5f13e020d6ca1ba8d9a215fd"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/maps.png",
+ "source": "build/atlas306/terrain-1024-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 321514,
+ "sha256": "c9caf1039ce86268e98cd794ffaac007d9a5a88335cc9c471e005e0c74fecc59"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/provenance.json",
+ "source": "build/atlas306/terrain-1024-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 611,
+ "sha256": "4a75c40d28153950b41d9c8b0292f4d1e2188a24b4e8e900d8157f236fd64296"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/sections.pdf",
+ "source": "build/atlas306/terrain-1024-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 740745,
+ "sha256": "c71b70be62356a4c0e34b05cadb96b6140cfeb218e9405ddc9f7ef42977671ee"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/sections.png",
+ "source": "build/atlas306/terrain-1024-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1385260,
+ "sha256": "9149b3442502dfdda59458bfd4ef13279bb10f1827d107f49bbaf226013ffdc8"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/summary.json",
+ "source": "build/atlas306/terrain-1024-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1365,
+ "sha256": "7d67ef609d48ddf778d837ce0d852603897f7e75867a29c02bce35bf95c2b3ec"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-1024-0/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 21404,
+ "sha256": "3cdd772a370dd23f090365c3b7cc63f6be9c656f9d6d8a1e6ac934fc26bec972"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-0/upper-terrain.png",
+ "source": "build/atlas306/terrain-1024-0/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 82491,
+ "sha256": "9e57e8a05518509a8ac113786bbef935790a68e7453ff72ea50fb66d173cd06c"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/maps.pdf",
+ "source": "build/atlas306/terrain-1024-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 140175,
+ "sha256": "cce8d91fb373b012d9d107f7c8321bb32393cf21b06282f35fc90e67d56c3908"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/maps.png",
+ "source": "build/atlas306/terrain-1024-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 325656,
+ "sha256": "e891028195fb7bc69be8cd56fde377434634363bb8dab4a3766bd4ed271c1260"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/provenance.json",
+ "source": "build/atlas306/terrain-1024-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 612,
+ "sha256": "eb50e6d71ce99f40890ebbe0065c7f488aa227c00e1bbf1876fce2109d472fbd"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/sections.pdf",
+ "source": "build/atlas306/terrain-1024-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 726710,
+ "sha256": "6bb4a6666409140c9afef2ed23e09d1f920e7f92f1b3049603402110df46bdeb"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/sections.png",
+ "source": "build/atlas306/terrain-1024-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1385628,
+ "sha256": "9c465121f2c69fc36dabac58d7d954deb643d88bc9042d9c6c7c05a8ca56bcc7"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/summary.json",
+ "source": "build/atlas306/terrain-1024-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1366,
+ "sha256": "028f231593210f28f1d6a378613eee6138381abd1c441374ef652cd31ba73192"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-1024-42/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 21591,
+ "sha256": "b17b84dc51ff5acf1cc0905d24e9430acae0bfa92cb82909d49bb5f83a9f33a5"
+ },
+ {
+ "path": "assets/atlas306/terrain-1024-42/upper-terrain.png",
+ "source": "build/atlas306/terrain-1024-42/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 79897,
+ "sha256": "2a51667f0bd2f42f62d6ac4286da5e2f7b9994ffbb237637bfba661923a8fe8c"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/maps.pdf",
+ "source": "build/atlas306/terrain-512--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 78523,
+ "sha256": "c79e58001934db22f9d5875f7a6ea3eede5f7d241eb9c8d6cfb13ca5cae7b9ae"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/maps.png",
+ "source": "build/atlas306/terrain-512--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 224942,
+ "sha256": "85268c1fb579503960d8c99bbf9dac055bb85c755ef19d71c9364cb3d4e759c7"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/provenance.json",
+ "source": "build/atlas306/terrain-512--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 629,
+ "sha256": "67475c877a3d3118651db7a256d3c8d97e7074943127d1ca4b0874560074ff52"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/sections.pdf",
+ "source": "build/atlas306/terrain-512--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 309334,
+ "sha256": "8fa3dfebc804b87a949d34f0585142200e8d66a2e43eba150d8638657dcc021c"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/sections.png",
+ "source": "build/atlas306/terrain-512--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 692037,
+ "sha256": "04593df4daab04b85fe4b697cb2abe08686b33e028b7827f94603054fe8dad92"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/summary.json",
+ "source": "build/atlas306/terrain-512--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1377,
+ "sha256": "abe60b6a08a5f3bae7e58c952004556d3cd7488d2d8225e9e8a2b69b2fef1055"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-512--7228211907433324401/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 20954,
+ "sha256": "153adaa7bc80d4bf5df853c671487d309a83269047d9ff5dbbb4505bd13658b1"
+ },
+ {
+ "path": "assets/atlas306/terrain-512--7228211907433324401/upper-terrain.png",
+ "source": "build/atlas306/terrain-512--7228211907433324401/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 64421,
+ "sha256": "2b582762839b2a74b9c40397e30b062b431f73945b7aadc88333983e509e96d6"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/maps.pdf",
+ "source": "build/atlas306/terrain-512-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 79446,
+ "sha256": "e17df0cc6b8beb1066bcc8e9d491be77145eabfe02d4b038d0ce176d93be3dab"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/maps.png",
+ "source": "build/atlas306/terrain-512-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 223325,
+ "sha256": "08d722f74193d8d7580bddccb19960a40645de89b7344be99522ed97a3e084c9"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/provenance.json",
+ "source": "build/atlas306/terrain-512-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 610,
+ "sha256": "6ea590a2ac2b340dddb6069bb90cd751a0bde7013742eecc1ba57da8f76984c4"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/sections.pdf",
+ "source": "build/atlas306/terrain-512-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 357721,
+ "sha256": "1f99a7de2aa10809fdff08f47218b6210c13632d494561bf6ecb15a5823f9c53"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/sections.png",
+ "source": "build/atlas306/terrain-512-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 761010,
+ "sha256": "121bd6b7f7196d58316934ba8cabd3c337a82090ed4dcebeed1ad6f78b9af0c9"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/summary.json",
+ "source": "build/atlas306/terrain-512-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1357,
+ "sha256": "f8bdcf7b8983019d8f4cd2610cdac715cb0b67673f3b0d9c8679d9db4f305836"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-512-0/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 20533,
+ "sha256": "3f2f0f1689cfe3810711377c7b849fa07d81a496cbf1d667648e01c07a01d5f6"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-0/upper-terrain.png",
+ "source": "build/atlas306/terrain-512-0/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 62434,
+ "sha256": "e5d54df8aae911828f057c9fe5968cd195a3a2f460271b55de186110d7716642"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/maps.pdf",
+ "source": "build/atlas306/terrain-512-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 77651,
+ "sha256": "aa17c5c8cc9043d5b58e27b8ebf3b5621cf4a29569b020357398b81e95868303"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/maps.png",
+ "source": "build/atlas306/terrain-512-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 222940,
+ "sha256": "8a567dcc562421faa3a7b304915a8c95596ecc0813f54c40069e9b6da4f61701"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/provenance.json",
+ "source": "build/atlas306/terrain-512-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 611,
+ "sha256": "85b16524206ee614ffc6cfa1a399b34a838e5c5e0ece5e00c043bb90441f6e2a"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/sections.pdf",
+ "source": "build/atlas306/terrain-512-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 335545,
+ "sha256": "b9f24614432171377a2598a3049fb9cc98439c704d63254e34cb1d7753be26b3"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/sections.png",
+ "source": "build/atlas306/terrain-512-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 735769,
+ "sha256": "a3aec03ed1a68f1eb1ff391a0d0956256dcb470f7f1f12736f99319a5b7c37a1"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/summary.json",
+ "source": "build/atlas306/terrain-512-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1357,
+ "sha256": "bf2848f62ed00f1f2b53a97b1c3b151d475919be3ef7495c435413e35effe273"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-512-42/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 20442,
+ "sha256": "3145876e7ffce3aaaf3298ac52c8c42436ea14b3a0689b662634ff9ae94a45e9"
+ },
+ {
+ "path": "assets/atlas306/terrain-512-42/upper-terrain.png",
+ "source": "build/atlas306/terrain-512-42/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 62804,
+ "sha256": "38e840a87e04c25d3be3809ee79ca29b54bf0008e56c43560c2271ee2a015c4d"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/maps.pdf",
+ "source": "build/atlas306/terrain-724--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 101683,
+ "sha256": "c1703ee8d4711cd1551a69893949344485e8e9dccd252353f2651a9ac5d93527"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/maps.png",
+ "source": "build/atlas306/terrain-724--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 264446,
+ "sha256": "c50e9404c023ee62d52144751c153a043568183f774498bc6fdb1df5f599bc9a"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/provenance.json",
+ "source": "build/atlas306/terrain-724--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 629,
+ "sha256": "b02b03edf8d4717cdfc9241d2dc57b38d40e1d9955bfba4cc46c0604c468c6d7"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/sections.pdf",
+ "source": "build/atlas306/terrain-724--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 499107,
+ "sha256": "dedb17d5d5b79f28a5751bc5c6c84bb814f4b991bd570b24a7917b0c76bab8f4"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/sections.png",
+ "source": "build/atlas306/terrain-724--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1016047,
+ "sha256": "9c398dd7d45105c280d08285b42b8389db8663269fd2d9271596a37fcb3615b7"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/summary.json",
+ "source": "build/atlas306/terrain-724--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1377,
+ "sha256": "db37a2c79188e0746a7427512ab7d4281d0ec5be4fc637873cff723aa9a58498"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-724--7228211907433324401/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 20706,
+ "sha256": "1de884f833be410f68e3085fc4ad9c2de401a20285ccb196efc5e101d3ce778e"
+ },
+ {
+ "path": "assets/atlas306/terrain-724--7228211907433324401/upper-terrain.png",
+ "source": "build/atlas306/terrain-724--7228211907433324401/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 64836,
+ "sha256": "970504440d3eb7c82673256c54439009603d5f310126af163f8ee90f7b5e81a5"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/maps.pdf",
+ "source": "build/atlas306/terrain-724-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 98397,
+ "sha256": "6bedac55e45fa71854f97d8a58f91cb25dcbc93c38a6af7354d7629da3bb0dcd"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/maps.png",
+ "source": "build/atlas306/terrain-724-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 258422,
+ "sha256": "e1b93c71a2e44715fa936a69e87d18705e814b65b609b6651cd6c3090c78bff7"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/provenance.json",
+ "source": "build/atlas306/terrain-724-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 610,
+ "sha256": "6bbe1ff4f631251aa9a1b395f210dbd06bf614f308f2eb6e1f8bff6981665876"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/sections.pdf",
+ "source": "build/atlas306/terrain-724-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 509097,
+ "sha256": "7c78b42c5cea59883df27874d3425344e8b9dad45db93d9e0f1810f17a272672"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/sections.png",
+ "source": "build/atlas306/terrain-724-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1022861,
+ "sha256": "78556bbc279f6cb0e61d3e7d5956de9a9e3ca8a33088125fd3b705da8b8e5760"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/summary.json",
+ "source": "build/atlas306/terrain-724-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1365,
+ "sha256": "8ad30317b632c340dacab0d0ef474c32e95abd27d51eaf035445a51be69097e8"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-724-0/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 20592,
+ "sha256": "5be68c8c94d67d22e0a15ae72f286bdb1f2c40bde1b6178b27927866c8d1b667"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-0/upper-terrain.png",
+ "source": "build/atlas306/terrain-724-0/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 64900,
+ "sha256": "029d0db5752ef782b54b4158eff4847f657f65369ffcb8daa91ea8a5380a69e6"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/maps.pdf",
+ "source": "build/atlas306/terrain-724-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 96982,
+ "sha256": "0a36093f8eb6d83c2a5857d9cebd2f8143fcc877980345e8462614d3bcf5f370"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/maps.png",
+ "source": "build/atlas306/terrain-724-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 257625,
+ "sha256": "4c78711102c2aa7a33766bcefb20d7ab85e96ea3946f2f6e593f27436ba0cc12"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/provenance.json",
+ "source": "build/atlas306/terrain-724-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 611,
+ "sha256": "fa8f38d0d6f1dff88f33e1500b9cc7106002eeead848a90b082b37b908679f93"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/sections.pdf",
+ "source": "build/atlas306/terrain-724-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 503350,
+ "sha256": "b0a9802cf904f8caad5293ed15dd585f6fed7087b8367a1b17ec0a9e187c59b8"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/sections.png",
+ "source": "build/atlas306/terrain-724-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1018398,
+ "sha256": "29eedaf0b30f93d58235ba478cab91ff9e3eb57a90c1a69967979c790f844177"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/summary.json",
+ "source": "build/atlas306/terrain-724-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 1364,
+ "sha256": "75f0488c4ed02963c710825169f54c6bbcd51a4a3d88bc878468769abfda3c4a"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/upper-terrain.pdf",
+ "source": "build/atlas306/terrain-724-42/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 20716,
+ "sha256": "aa73fe3c97c96468d5cbcb6cd8c0848c20659666b57b9835e1d02e84e6625e20"
+ },
+ {
+ "path": "assets/atlas306/terrain-724-42/upper-terrain.png",
+ "source": "build/atlas306/terrain-724-42/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.6",
+ "bytes": 61413,
+ "sha256": "07709302d925b1e7623f3690f5329ab1377173f294c8e6be3e0cb9a84c957f1b"
+ },
+ {
+ "path": "assets/atlas307-preview/installations-724-0.pdf",
+ "source": "build/atlas307-preview/installations-724-0.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 44027,
+ "sha256": "c7026f4a08662d9c205df05b07a9cf4ba8a072315985fe2ca89c9be29ed801fc"
+ },
+ {
+ "path": "assets/atlas307-preview/installations-724-0.png",
+ "source": "build/atlas307-preview/installations-724-0.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 115845,
+ "sha256": "c3e17b0629adb4e1c1caf7af0a464fa793b723bde434babc8ef8868f66ced5b4"
+ },
+ {
+ "path": "assets/atlas307/installations/installations-1024--7228211907433324401-provenance.json",
+ "source": "build/atlas307/installations/installations-1024--7228211907433324401-provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 935,
+ "sha256": "a4f444f215de75e70fbf76b9e384c6c80124d6124269a5aa7968417f3bdbe229"
+ },
+ {
+ "path": "assets/atlas307/installations/installations-1024--7228211907433324401.pdf",
+ "source": "build/atlas307/installations/installations-1024--7228211907433324401.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 50138,
+ "sha256": "39e90082f694de5dff4f6dccf15bf073a2672634d32b26bc6baa04a89f1634e2"
+ },
+ {
+ "path": "assets/atlas307/installations/installations-1024--7228211907433324401.png",
+ "source": "build/atlas307/installations/installations-1024--7228211907433324401.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 119369,
+ "sha256": "2ffd3731b978aef468558e0d952a52c539959e5be0543610f4835ebce4ba3535"
+ },
+ {
+ "path": "assets/atlas307/installations/installations-724-0-provenance.json",
+ "source": "build/atlas307/installations/installations-724-0-provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 835,
+ "sha256": "69a4af3619287763bdda655b23c7b1abc4bfecfdc1d98c417101b5b9bc472e63"
+ },
+ {
+ "path": "assets/atlas307/installations/installations-724-0.pdf",
+ "source": "build/atlas307/installations/installations-724-0.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 44137,
+ "sha256": "3eda2a558cb10066de75b6cb934184db7516fbb6f8b36397f511cf584b5de65e"
+ },
+ {
+ "path": "assets/atlas307/installations/installations-724-0.png",
+ "source": "build/atlas307/installations/installations-724-0.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 115971,
+ "sha256": "812220efa373c325ae2c6e7bdc8e0b41624eb64fb44d542ad5a6fd5ccd6c0bcf"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-1024--7228211907433324401.json",
+ "source": "build/atlas307/raw/terrain-1024--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 9181045,
+ "sha256": "7a825a4562b0f987636aeb497aa49b4adb3c79d22e1dade53739ea837faa2892"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-1024-0.json",
+ "source": "build/atlas307/raw/terrain-1024-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 9132767,
+ "sha256": "a9b328a6440147ba78396d6b6c7dbdd0dafcdb12d0de7e00ef16c49d80b526bd"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-1024-42.json",
+ "source": "build/atlas307/raw/terrain-1024-42.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 9201251,
+ "sha256": "a36a66a568b2010ebed613bd2c70ff83e36d3d8796f1ac48c866263bc975407e"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-512--7228211907433324401.json",
+ "source": "build/atlas307/raw/terrain-512--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 4833114,
+ "sha256": "bb40ecbd0de892d7683ceaf5b5ed305239c47191a9067b16ccf137dd2e7c4bb2"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-512-0.json",
+ "source": "build/atlas307/raw/terrain-512-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 4767716,
+ "sha256": "a0ecde4ddd3527c496b07b13ac013651804fb36d49c928428a1e5e1c875f15c2"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-512-42.json",
+ "source": "build/atlas307/raw/terrain-512-42.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 4788165,
+ "sha256": "3405b5b1875220629817011b94f484c9950178b584b7307dcbeefbd23db626df"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-724--7228211907433324401.json",
+ "source": "build/atlas307/raw/terrain-724--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 6561020,
+ "sha256": "079dc01b60518e4992d8418538915af2292928cf78d4eeea390b8483fc9f7f13"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-724-0.json",
+ "source": "build/atlas307/raw/terrain-724-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 6503774,
+ "sha256": "54908a92c2480bbb037dd7274dff1adf0a712f55d17010425e0bdb69fc0ba979"
+ },
+ {
+ "path": "assets/atlas307/raw/terrain-724-42.json",
+ "source": "build/atlas307/raw/terrain-724-42.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 6555968,
+ "sha256": "9d614a2171cb88947f598bfb94c0864eaec1a54645b82ecd991c7bed06eb4b50"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/aerial24-1024--7228211907433324401.json",
+ "source": "build/atlas307/structure-reports/aerial24-1024--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 32310,
+ "sha256": "6fecd453204bb7e449eff26ab5b601daf5fdfbad97a87051e095ebbb827acb22"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/aerial24-724-0.json",
+ "source": "build/atlas307/structure-reports/aerial24-724-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 25768,
+ "sha256": "e7d32e5ab8d46db1ad7a1f6c93a477e70004ddb47452385d636274a497baa804"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/density-306-307-comparison.json",
+ "source": "build/atlas307/structure-reports/density-306-307-comparison.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 2719,
+ "sha256": "75ffcf65be5a1500066357dbaf46bc4baa4e07aeb105862e6baea4f071ad5521"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/drained307-1024--7228211907433324401.json",
+ "source": "build/atlas307/structure-reports/drained307-1024--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 2362,
+ "sha256": "d6c075655b01f2cf702b14bb742257f862d24eb848ab0f0333d58602173deed8"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/drained307-724-0.json",
+ "source": "build/atlas307/structure-reports/drained307-724-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1815,
+ "sha256": "4a4ba9cb43b3621c468304431f5dca97a3a81f6f0784c7fd2917ee72eb6802ac"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/transit29-1024--7228211907433324401.json",
+ "source": "build/atlas307/structure-reports/transit29-1024--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1726,
+ "sha256": "16201908877ab2cecc38e4db660f53215ff49508131d46dc0d21fed029cdd712"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/transit29-724-0.json",
+ "source": "build/atlas307/structure-reports/transit29-724-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 2750,
+ "sha256": "7af50980ce062a7571c7122b2993ea374f9b73f9cec5996122ec994a3c9f4bd5"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/village306-1024--7228211907433324401.json",
+ "source": "build/atlas307/structure-reports/village306-1024--7228211907433324401.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 849155,
+ "sha256": "82cc3d9b6c2a68b8ef2ebe3587d484ac51ff91b13cf8d26561004ba1c2aeace4"
+ },
+ {
+ "path": "assets/atlas307/structure-reports/village306-724-0.json",
+ "source": "build/atlas307/structure-reports/village306-724-0.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1105245,
+ "sha256": "00139c604405789be228e2c1a33f10e224977cd2013d22879444803cedde4be8"
+ },
+ {
+ "path": "assets/atlas307/summary.json",
+ "source": "build/atlas307/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 12998,
+ "sha256": "1c13efac0a30bc896e30a646331656c90a35982ad2e15005d97adada55316ea0"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/maps.pdf",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 143440,
+ "sha256": "ab28f89a7da11732a8c32ffcdeb1221b25211dcc09774765efda0d8f0258fd23"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/maps.png",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 333310,
+ "sha256": "ab0796c9dba59e99e460837040ea58a3491ff1f4e59b6a162acee2c8e04c018e"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/provenance.json",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 620,
+ "sha256": "386c7a993a1422585fbf162ea95b2d1298dd074cd3d97f46be7f2e099e76d4a3"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/sections.pdf",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 745418,
+ "sha256": "5c5fa71a99bc95868fefec848b9eff3e63ee2a8b3232098a7e8967fea12c3c7d"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/sections.png",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1407846,
+ "sha256": "e61283c2e4c1b0e4900d0c1319b305cff7ff584e28fd3cb5e8a932a923be467e"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/summary.json",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1374,
+ "sha256": "425bb9efd805d6b5bf936dc46722408263e71ec6a9a0dbc12eedaaa7241a37d3"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 21588,
+ "sha256": "0a5230c3940af77b187a69a7a25615a0edf6fa697208d9e3b359f72091a3d075"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024--7228211907433324401/upper-terrain.png",
+ "source": "build/atlas307/terrain-1024--7228211907433324401/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 85984,
+ "sha256": "0d8c4ca0f05a211a8c72485ae3c2c158f1be97b766cff666d1db96e4c73f2054"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/maps.pdf",
+ "source": "build/atlas307/terrain-1024-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 135478,
+ "sha256": "a92548aeefb6dca319215f9d61f54e2b6d44389aba572b1e30e4c191a19fc458"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/maps.png",
+ "source": "build/atlas307/terrain-1024-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 320522,
+ "sha256": "c8d07ac78a9d1aa0e47329a8880f2b8498dde8ac58f3ec3715af85602ce5a4dc"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/provenance.json",
+ "source": "build/atlas307/terrain-1024-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 601,
+ "sha256": "96ce36dd562f42d0d75737a31b6a60007efd41f69e64063cbb8c5df8bbda6481"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/sections.pdf",
+ "source": "build/atlas307/terrain-1024-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 740729,
+ "sha256": "0ba73322a382dd745b4d5ff117eb813af93d8d5489f21477e2baf78b5988bbab"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/sections.png",
+ "source": "build/atlas307/terrain-1024-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1384128,
+ "sha256": "87e511e42bd9debcc4519b3242320cfb0f5a6bd8dc140834aafbab65409d088d"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/summary.json",
+ "source": "build/atlas307/terrain-1024-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1365,
+ "sha256": "7d67ef609d48ddf778d837ce0d852603897f7e75867a29c02bce35bf95c2b3ec"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-1024-0/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 21404,
+ "sha256": "8701f017912c82b601ce1a458990ec93e5264ee8d78e211967c5ef9d9439d1d8"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-0/upper-terrain.png",
+ "source": "build/atlas307/terrain-1024-0/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 82491,
+ "sha256": "9e57e8a05518509a8ac113786bbef935790a68e7453ff72ea50fb66d173cd06c"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/maps.pdf",
+ "source": "build/atlas307/terrain-1024-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 140163,
+ "sha256": "86eb4674289011f600ca2e3aa42549298d93dd9c88bc89edfd682da5cc8710ce"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/maps.png",
+ "source": "build/atlas307/terrain-1024-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 324683,
+ "sha256": "ba7dc3cb4e899124371ca7f5c71da788c1fcb665986666c1102ee3e9d138f62e"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/provenance.json",
+ "source": "build/atlas307/terrain-1024-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 602,
+ "sha256": "ff937c8f03a6efa3b30890aa9b70f796b347e9b56989e7548b00ac3026a700aa"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/sections.pdf",
+ "source": "build/atlas307/terrain-1024-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 726693,
+ "sha256": "f30e7d2d7140722b486e7f6b04e74e5423639ea65a950830cba7be1ec2133c56"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/sections.png",
+ "source": "build/atlas307/terrain-1024-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1384494,
+ "sha256": "543a04d5369dd1f51b210a59c29ecb83099d00c1ce7bf9db9515259a06286d7d"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/summary.json",
+ "source": "build/atlas307/terrain-1024-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1366,
+ "sha256": "028f231593210f28f1d6a378613eee6138381abd1c441374ef652cd31ba73192"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-1024-42/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 21591,
+ "sha256": "b747b65967932a068bc7498091605f687e123322ef0bb61a67f7163c10bb34e8"
+ },
+ {
+ "path": "assets/atlas307/terrain-1024-42/upper-terrain.png",
+ "source": "build/atlas307/terrain-1024-42/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 79897,
+ "sha256": "2a51667f0bd2f42f62d6ac4286da5e2f7b9994ffbb237637bfba661923a8fe8c"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/maps.pdf",
+ "source": "build/atlas307/terrain-512--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 78511,
+ "sha256": "cc6e41908cb33f2d08262bd92c2059147026014f20199fa328a606e270fb1362"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/maps.png",
+ "source": "build/atlas307/terrain-512--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 223967,
+ "sha256": "3f765c992477f906c43ff4b71f462cd74a6e7342a489b6a2ba20cb1fa44497eb"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/provenance.json",
+ "source": "build/atlas307/terrain-512--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 619,
+ "sha256": "b0c516c6f7d8044232de6352db4411c9e1a8c0443531f7aa1d71ac39ac9f637c"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/sections.pdf",
+ "source": "build/atlas307/terrain-512--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 309319,
+ "sha256": "f5a55e9c54eccfb54da19ae192c110e5f8c230f3aa029d27eec133e388a1d5a3"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/sections.png",
+ "source": "build/atlas307/terrain-512--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 690908,
+ "sha256": "183fd8cdf91cb94536546ce0ce62975ddf25f7ff6270b610529ebeb1f71ff7ce"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/summary.json",
+ "source": "build/atlas307/terrain-512--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1377,
+ "sha256": "abe60b6a08a5f3bae7e58c952004556d3cd7488d2d8225e9e8a2b69b2fef1055"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-512--7228211907433324401/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 20954,
+ "sha256": "9a5caa67f46b2ff53b5f5aa4a3aefb4ddbe0bb8cbe5de106d8581febe91f5865"
+ },
+ {
+ "path": "assets/atlas307/terrain-512--7228211907433324401/upper-terrain.png",
+ "source": "build/atlas307/terrain-512--7228211907433324401/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 64421,
+ "sha256": "2b582762839b2a74b9c40397e30b062b431f73945b7aadc88333983e509e96d6"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/maps.pdf",
+ "source": "build/atlas307/terrain-512-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 79433,
+ "sha256": "29ef4e46b9f8e4e0edaa7ec9c95653861ad7943f530e50982bb08f7107a91ab7"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/maps.png",
+ "source": "build/atlas307/terrain-512-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 222345,
+ "sha256": "86d7708e8413060a9848a4ada9bc88203e12a9ac2a9a1f2a9da416b816a67b1d"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/provenance.json",
+ "source": "build/atlas307/terrain-512-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 600,
+ "sha256": "7ad3b65dad7fdb54d88a8c5f5c74a575b55860efc20360def9a7ec6ea7701177"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/sections.pdf",
+ "source": "build/atlas307/terrain-512-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 357705,
+ "sha256": "835312e85c856d01de2c2a6521575265723b69350f9a0b8b48cc149b38ed1822"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/sections.png",
+ "source": "build/atlas307/terrain-512-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 759917,
+ "sha256": "065b5d6e32f80b4933fd7a058a59c4e3a3d2017e59cdce845a0571489de2ca82"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/summary.json",
+ "source": "build/atlas307/terrain-512-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1357,
+ "sha256": "f8bdcf7b8983019d8f4cd2610cdac715cb0b67673f3b0d9c8679d9db4f305836"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-512-0/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 20533,
+ "sha256": "ad7dc81286816259955038360e4fe8c53df332b00fa43aa834e87b355181bc4e"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-0/upper-terrain.png",
+ "source": "build/atlas307/terrain-512-0/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 62434,
+ "sha256": "e5d54df8aae911828f057c9fe5968cd195a3a2f460271b55de186110d7716642"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/maps.pdf",
+ "source": "build/atlas307/terrain-512-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 77639,
+ "sha256": "ac7a971ccad30358c0ab8ae71031fd3ac5d20a2e3d64badbbcc7baf53106d147"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/maps.png",
+ "source": "build/atlas307/terrain-512-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 221960,
+ "sha256": "f646f1b02d97c9dd67cf72199b7139e4f31e5a040acdd83b9dd5ecf16909be4a"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/provenance.json",
+ "source": "build/atlas307/terrain-512-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 601,
+ "sha256": "12d9eba235cb30efbc2dd8ebc5a33fa5b7d0f3d32a87b6c63411986534786cf7"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/sections.pdf",
+ "source": "build/atlas307/terrain-512-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 335529,
+ "sha256": "dd5b196393d64153560271a8d6d16348a448bdd2e7bded9deb3272e73aa8d6ec"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/sections.png",
+ "source": "build/atlas307/terrain-512-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 734646,
+ "sha256": "0deca57c21ba57606d3b3bca7191f039ee26417396f95796349df4cdd549d32c"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/summary.json",
+ "source": "build/atlas307/terrain-512-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1357,
+ "sha256": "bf2848f62ed00f1f2b53a97b1c3b151d475919be3ef7495c435413e35effe273"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-512-42/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 20442,
+ "sha256": "cfdb9d8dee58afdd5c259cc52bf2c3cab7ac224cc3f06a233d344ead69f6b20e"
+ },
+ {
+ "path": "assets/atlas307/terrain-512-42/upper-terrain.png",
+ "source": "build/atlas307/terrain-512-42/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 62804,
+ "sha256": "38e840a87e04c25d3be3809ee79ca29b54bf0008e56c43560c2271ee2a015c4d"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/maps.pdf",
+ "source": "build/atlas307/terrain-724--7228211907433324401/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 101672,
+ "sha256": "0408d2049d4d107bec12b8ab8b28e6116be6ea553bd669d513b1658f0dba8996"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/maps.png",
+ "source": "build/atlas307/terrain-724--7228211907433324401/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 263447,
+ "sha256": "f06b0930d708afeefbd0ce6028b3cecc435c8258b86f362fc2ac23a5d2c66149"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/provenance.json",
+ "source": "build/atlas307/terrain-724--7228211907433324401/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 619,
+ "sha256": "cfdc2b540c7224f83639d6d31ead8451e58206adb5435444c7234fb188baf5dc"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/sections.pdf",
+ "source": "build/atlas307/terrain-724--7228211907433324401/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 499091,
+ "sha256": "12f0e6fec120a2cf73423cfb04c19fb575adb5aec06a9cdf2b88e79a332c9c5a"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/sections.png",
+ "source": "build/atlas307/terrain-724--7228211907433324401/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1014913,
+ "sha256": "40cdd07ee07162a9e89c686cfad10736b2e1fc3a9445e570a9ac2a54a1247c78"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/summary.json",
+ "source": "build/atlas307/terrain-724--7228211907433324401/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1377,
+ "sha256": "db37a2c79188e0746a7427512ab7d4281d0ec5be4fc637873cff723aa9a58498"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-724--7228211907433324401/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 20706,
+ "sha256": "0ac576882cc05f4e5c896df61c2c61870426177a52a87609c5e70f0222ef54b4"
+ },
+ {
+ "path": "assets/atlas307/terrain-724--7228211907433324401/upper-terrain.png",
+ "source": "build/atlas307/terrain-724--7228211907433324401/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 64836,
+ "sha256": "970504440d3eb7c82673256c54439009603d5f310126af163f8ee90f7b5e81a5"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/maps.pdf",
+ "source": "build/atlas307/terrain-724-0/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 98386,
+ "sha256": "ee54b39f027da8483c3b2c28480f24471ee32f1a27f54d0b6175fbfcaf421544"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/maps.png",
+ "source": "build/atlas307/terrain-724-0/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 257432,
+ "sha256": "7894b7502655e47a5f2261ed5726fe2a71f557106e26493183fe28d1bfd60b12"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/provenance.json",
+ "source": "build/atlas307/terrain-724-0/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 600,
+ "sha256": "cc0c2212800c44f27ef87870a7116c4f478a690fb6a7ccdf053bb05240d96f9f"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/sections.pdf",
+ "source": "build/atlas307/terrain-724-0/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 509082,
+ "sha256": "d709c79c0acfc84a72d3e77342ac436907e5610d0f57ba6264b7334b8b470bf4"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/sections.png",
+ "source": "build/atlas307/terrain-724-0/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1021757,
+ "sha256": "846ea616cf57cf92ac9e38b8af041b75a2d6973eda00a765e76467d1548a001c"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/summary.json",
+ "source": "build/atlas307/terrain-724-0/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1365,
+ "sha256": "8ad30317b632c340dacab0d0ef474c32e95abd27d51eaf035445a51be69097e8"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-724-0/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 20592,
+ "sha256": "125799e44da4d68fce02d0e67024f29f5cfcfb8328b729969f8d13d747901b4d"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-0/upper-terrain.png",
+ "source": "build/atlas307/terrain-724-0/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 64900,
+ "sha256": "029d0db5752ef782b54b4158eff4847f657f65369ffcb8daa91ea8a5380a69e6"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/maps.pdf",
+ "source": "build/atlas307/terrain-724-42/maps.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 96970,
+ "sha256": "7cd60f07dfdcfe099379e4ab148b01ea7f52d0574ab881ea428bfc45c855471a"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/maps.png",
+ "source": "build/atlas307/terrain-724-42/maps.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 256637,
+ "sha256": "2ccc597493092a08613a57fac133ed74e95320eacb4ea43c469773da64aeb7a0"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/provenance.json",
+ "source": "build/atlas307/terrain-724-42/provenance.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 601,
+ "sha256": "33a8047c7fbb5a4e8a0ef6ca57c2e221436cb1277d827f3d554acdf619340122"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/sections.pdf",
+ "source": "build/atlas307/terrain-724-42/sections.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 503334,
+ "sha256": "fd807f7d572b277fe878870042edd3fbbf6276f72564e36f7580bbbedd8a7f17"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/sections.png",
+ "source": "build/atlas307/terrain-724-42/sections.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1017198,
+ "sha256": "d7f5c733974b85c865da3a1e486c2279d163732063b107a7a8587ad8438a2fd3"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/summary.json",
+ "source": "build/atlas307/terrain-724-42/summary.json",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 1364,
+ "sha256": "75f0488c4ed02963c710825169f54c6bbcd51a4a3d88bc878468769abfda3c4a"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/upper-terrain.pdf",
+ "source": "build/atlas307/terrain-724-42/upper-terrain.pdf",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 20716,
+ "sha256": "733d7cd59a0e6b4f696d80feef2a594e2c3ef163bfbf5379b502d18d53ab50df"
+ },
+ {
+ "path": "assets/atlas307/terrain-724-42/upper-terrain.png",
+ "source": "build/atlas307/terrain-724-42/upper-terrain.png",
+ "category": "alpha-atlas",
+ "status": "development-output",
+ "version_hint": "alpha.30.7",
+ "bytes": 61413,
+ "sha256": "07709302d925b1e7623f3690f5329ab1377173f294c8e6be3e0cb9a84c957f1b"
+ },
+ {
+ "path": "assets/beta003-preview/0000_sanctuary-beta003-hello-minimum.png",
+ "source": "build/beta003-preview/0000_sanctuary-beta003-hello-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 274640,
+ "sha256": "69ec7c8476ca21a832d3a6c53933c246758b42d45f8803aede605af347c8b4bf"
+ },
+ {
+ "path": "assets/beta003-preview/0001_sanctuary-beta003-hello-large.png",
+ "source": "build/beta003-preview/0001_sanctuary-beta003-hello-large.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 274640,
+ "sha256": "69ec7c8476ca21a832d3a6c53933c246758b42d45f8803aede605af347c8b4bf"
+ },
+ {
+ "path": "assets/beta003-preview/0002_sanctuary-beta003-hud-333.png",
+ "source": "build/beta003-preview/0002_sanctuary-beta003-hud-333.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 491748,
+ "sha256": "bac5a290f691668d4ea208c402447528e6617becbb1e86a3328d485e0183f880"
+ },
+ {
+ "path": "assets/beta003-preview/0003_sanctuary-beta003-first-aptitude.png",
+ "source": "build/beta003-preview/0003_sanctuary-beta003-first-aptitude.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 285855,
+ "sha256": "29b33726fd5f5a408e56d2889a29b8e51b49515d83cb96f6b1895e3509e7d9b4"
+ },
+ {
+ "path": "assets/beta003-preview/0004_sanctuary-beta003-progression-minimum.png",
+ "source": "build/beta003-preview/0004_sanctuary-beta003-progression-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 285956,
+ "sha256": "6391b051f8fa737cbecc51dbd966b66ecdac107164b554e9418758b3dcd8efaf"
+ },
+ {
+ "path": "assets/beta003-preview/0005_sanctuary-beta003-inhabitant.png",
+ "source": "build/beta003-preview/0005_sanctuary-beta003-inhabitant.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 317912,
+ "sha256": "cf6e766d00470fc872b8fa5031c06ee5ec8957d64b1190504c1b778b84d001f9"
+ },
+ {
+ "path": "assets/beta003-preview/0006_sanctuary-beta003-history.png",
+ "source": "build/beta003-preview/0006_sanctuary-beta003-history.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 352410,
+ "sha256": "3d4f8d46c348acc4c64561f48de1bd1faa338795ef9f5c3018c772bab2808f72"
+ },
+ {
+ "path": "assets/beta003-preview/0007_sanctuary-beta003-after-death.png",
+ "source": "build/beta003-preview/0007_sanctuary-beta003-after-death.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.003",
+ "bytes": 290859,
+ "sha256": "0edffd86affa56a016c71634e829740240a4186eeb3efb442d91b8279b65c3b6"
+ },
+ {
+ "path": "assets/beta004-preview/0003_sanctuary-beta004-progress-854-scale1.png",
+ "source": "build/beta004-preview/0003_sanctuary-beta004-progress-854-scale1.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 340157,
+ "sha256": "29df55c692c5845b5dbbc1df853f6176778934658ed53e981fc38f309aaf7003"
+ },
+ {
+ "path": "assets/beta004-preview/0004_sanctuary-beta004-progress-854-scale0.png",
+ "source": "build/beta004-preview/0004_sanctuary-beta004-progress-854-scale0.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 311000,
+ "sha256": "7c45fe3c7ba966cc006122592d4e6fe856523af8093380f161c86f75b55193b2"
+ },
+ {
+ "path": "assets/beta004-preview/0005_sanctuary-beta004-progress-960-scale1.png",
+ "source": "build/beta004-preview/0005_sanctuary-beta004-progress-960-scale1.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 522282,
+ "sha256": "1fde96a9798b8d002d30af75f9ef8419a2101bb6f1cc42e1529696afb6e5e12b"
+ },
+ {
+ "path": "assets/beta004-preview/0006_sanctuary-beta004-progress-960-scale0.png",
+ "source": "build/beta004-preview/0006_sanctuary-beta004-progress-960-scale0.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 452908,
+ "sha256": "db164b62fc7c46d4bcf5f252e6987cd87918c325561b1eea2a314af62c84b5e5"
+ },
+ {
+ "path": "assets/beta004-preview/0007_sanctuary-beta004-discovery-minimum.png",
+ "source": "build/beta004-preview/0007_sanctuary-beta004-discovery-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 453071,
+ "sha256": "a1685e744e91beaa62c5e3176d3261d91b26db79ef22ca1fbf4137cee5ba5fcb"
+ },
+ {
+ "path": "assets/beta004-preview/0008_sanctuary-beta004-story-minimum.png",
+ "source": "build/beta004-preview/0008_sanctuary-beta004-story-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 572457,
+ "sha256": "eb1d62739f11df85fad480aed125d5942ed3e6799b48838579291c79668424e3"
+ },
+ {
+ "path": "assets/beta004-preview/0009_sanctuary-beta004-inhabitant-minimum.png",
+ "source": "build/beta004-preview/0009_sanctuary-beta004-inhabitant-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 506641,
+ "sha256": "0a165d7b0c1bcaa06340cd8747322dc1a3e7800f0207b47c7bdfd909c455c3fc"
+ },
+ {
+ "path": "assets/beta004-preview/0010_sanctuary-beta004-progression-fr-minimum.png",
+ "source": "build/beta004-preview/0010_sanctuary-beta004-progression-fr-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 453464,
+ "sha256": "aa3bf5e6029ddce6f2392fec7dcc90a541f93669e4e01a2f6e61ef5105885684"
+ },
+ {
+ "path": "assets/beta004-preview/0011_sanctuary-beta004-decouvertes-fr-minimum.png",
+ "source": "build/beta004-preview/0011_sanctuary-beta004-decouvertes-fr-minimum.png",
+ "category": "beta-previews",
+ "status": "development-output",
+ "version_hint": "beta.004",
+ "bytes": 454260,
+ "sha256": "d16c201d43d36d15549f7341083daddabe725afd2d9282f1f34c3e1a6551631b"
+ },
+ {
+ "path": "assets/progression-icons/breathing.png",
+ "source": "mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/breathing.png",
+ "category": "creator-icons",
+ "status": "original-user-asset",
+ "version_hint": null,
+ "bytes": 266,
+ "sha256": "82d78d12a1ab0bbcafc45c71f4fe49cfcbe967952238f09205d22da0b5ad16e3"
+ },
+ {
+ "path": "assets/progression-icons/building.png",
+ "source": "mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/building.png",
+ "category": "creator-icons",
+ "status": "original-user-asset",
+ "version_hint": null,
+ "bytes": 272,
+ "sha256": "78000680f6cacea73e6e93260f7c92ed647c4c595237a611538ff298790f9f3c"
+ },
+ {
+ "path": "assets/progression-icons/heart.png",
+ "source": "mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/heart.png",
+ "category": "creator-icons",
+ "status": "original-user-asset",
+ "version_hint": null,
+ "bytes": 266,
+ "sha256": "93e31f24a613b6256524c91918a5510caa8d58efc0bf894d3d45528bd2893bc2"
+ },
+ {
+ "path": "assets/progression-icons/hunger.png",
+ "source": "mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/hunger.png",
+ "category": "creator-icons",
+ "status": "original-user-asset",
+ "version_hint": null,
+ "bytes": 264,
+ "sha256": "31286772a71e0034a80528f0331c68d942584ecf051ab75bbc6e40e3d04c6c4c"
+ },
+ {
+ "path": "assets/progression-icons/inventory.png",
+ "source": "mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/inventory.png",
+ "category": "creator-icons",
+ "status": "original-user-asset",
+ "version_hint": null,
+ "bytes": 243,
+ "sha256": "b840bd34e03973cde3f0f8838b1461a1394e0070c413622b4aa808c00e309961"
+ },
+ {
+ "path": "assets/progression-icons/mining.png",
+ "source": "mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/mining.png",
+ "category": "creator-icons",
+ "status": "original-user-asset",
+ "version_hint": null,
+ "bytes": 273,
+ "sha256": "89c34620d2fcc32661f47018bbe92458545aa96f873589af62d06ae4fe73ac6e"
+ },
+ {
+ "path": "assets/title/minecraft_title.png",
+ "source": "ressources-pack/helloworld/assets/minecraft_title.png",
+ "category": "title-reference",
+ "status": "project-reference",
+ "version_hint": null,
+ "bytes": 43647,
+ "sha256": "b20db1db3047d63c6b20497d796b2078fa3b341b319221685f3c31fc82138a52"
+ },
+ {
+ "path": "atlases/Sanctuary-Atlas-0.1.0-alpha.30.5.zip",
+ "source": "build/Sanctuary-Atlas-0.1.0-alpha.30.5.zip",
+ "category": "alpha-release-atlas",
+ "status": "original-manifest-verified",
+ "version_hint": "alpha.30.5",
+ "bytes": 25914065,
+ "sha256": "3da0d0b2422453689c403ca49d66cb54727948259fd0b791d54d233298a05ef2"
+ },
+ {
+ "path": "atlases/Sanctuary-Atlas-0.1.0-alpha.30.6.zip",
+ "source": "build/Sanctuary-Atlas-0.1.0-alpha.30.6.zip",
+ "category": "alpha-release-atlas",
+ "status": "original-manifest-verified",
+ "version_hint": "alpha.30.6",
+ "bytes": 26253274,
+ "sha256": "7ffc4cd6adea67b6e7e3e2ebfd9f5f60fec8dbe41ab080f689f3067b6ffe5522"
+ },
+ {
+ "path": "atlases/Sanctuary-Atlas-0.1.0-alpha.30.7.zip",
+ "source": "build/Sanctuary-Atlas-0.1.0-alpha.30.7.zip",
+ "category": "alpha-release-atlas",
+ "status": "original-manifest-verified",
+ "version_hint": "alpha.30.7",
+ "bytes": 26114253,
+ "sha256": "997779ae13d4784551d3be4defe988c3f8991c9e0f6569926e16b77b4420f38d"
+ },
+ {
+ "path": "blocodex-navigation/README.md",
+ "source": "archives/web/blocodex-navigation-v1/README.md",
+ "category": "blocodex-concept",
+ "status": "approved-concept-fictional-data",
+ "version_hint": null,
+ "bytes": 1761,
+ "sha256": "75a5cc1f94daea090afe453ad769a87096e87d691c97597e23c7212524ff97f5"
+ },
+ {
+ "path": "blocodex-navigation/fragment.html",
+ "source": "archives/web/blocodex-navigation-v1/fragment.html",
+ "category": "blocodex-concept",
+ "status": "approved-concept-fictional-data",
+ "version_hint": null,
+ "bytes": 13166,
+ "sha256": "23d7a194edb5a8035d05bd6982d647c280143fe95c0e9a08d6d4a4f1136d2690"
+ },
+ {
+ "path": "blocodex-navigation/index.html",
+ "source": "archives/web/blocodex-navigation-v1/index.html",
+ "category": "blocodex-concept",
+ "status": "approved-concept-fictional-data",
+ "version_hint": null,
+ "bytes": 13793,
+ "sha256": "aab6b1eaa3d0da885864fd7bd44f0721b19a9a1c533686be71fd37e3ac2a17d0"
+ },
+ {
+ "path": "references/CHANGELOG.md",
+ "source": "CHANGELOG.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 38575,
+ "sha256": "2b8e24c9ecb87ee6eba334a0ee78e6c8d60b2743f71b7b7ca0643adb48ea2710"
+ },
+ {
+ "path": "references/LICENSE",
+ "source": "LICENSE",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 35201,
+ "sha256": "f6618bb89c5f3bf60b11e1a067d4e948e7e59838587acdb1de987fcbf3f9722e"
+ },
+ {
+ "path": "references/README.md",
+ "source": "README.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 48448,
+ "sha256": "7f6c7b8f395acf19cea70190875c558291cceb050f96d311cb6c76a2fc9b741b"
+ },
+ {
+ "path": "references/THIRD_PARTY_NOTICES.md",
+ "source": "THIRD_PARTY_NOTICES.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 2333,
+ "sha256": "e82bbb25cf900cb4bbded8c86ebfadf34bf00311d44a9d0503d3d9d2f37ef337"
+ },
+ {
+ "path": "references/docs/alpha12-laboratory.md",
+ "source": "docs/alpha12-laboratory.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.12",
+ "bytes": 11703,
+ "sha256": "0c145759b7c0b6eafb544e4200b7dc5d0269d36896419139b77bc965ee85bffd"
+ },
+ {
+ "path": "references/docs/alpha13-worldgen.md",
+ "source": "docs/alpha13-worldgen.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.13",
+ "bytes": 11443,
+ "sha256": "eda9e4f0e9d63ad77ee8cf88c1eb7bab5ed781fe48e3fd55df6ada6d05a08beb"
+ },
+ {
+ "path": "references/docs/architecture-alpha20.md",
+ "source": "docs/architecture-alpha20.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.20",
+ "bytes": 10550,
+ "sha256": "0957f8dee6a78bf4ed2ac8af1e51f14d4e4b214c4f2e635ba2dbe9d936554515"
+ },
+ {
+ "path": "references/docs/architecture-alpha21.md",
+ "source": "docs/architecture-alpha21.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.21",
+ "bytes": 1312,
+ "sha256": "10a897d1ba068153d2459ed3b5b6c75dd3d43566aed70ecb34d9ab16e198938f"
+ },
+ {
+ "path": "references/docs/backlog.md",
+ "source": "docs/backlog.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 74643,
+ "sha256": "42592d5c84b9dd6e8daa087eaf92db96f75d00f1126ef4d0c32d6665d5c749b2"
+ },
+ {
+ "path": "references/docs/benchmarks/alpha11-generation.json",
+ "source": "docs/benchmarks/alpha11-generation.json",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.11",
+ "bytes": 17881,
+ "sha256": "e84d244e1aa0a106c492de9222f39fa2a3f933eebfd434f14bf00a8d7722c497"
+ },
+ {
+ "path": "references/docs/benchmarks/alpha12-expansion.json",
+ "source": "docs/benchmarks/alpha12-expansion.json",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.12",
+ "bytes": 10772,
+ "sha256": "9adec7a95590a57cad9ca55737cab5958242413392cb579b3b642e7ad8ee2552"
+ },
+ {
+ "path": "references/docs/benchmarks/alpha121-expansion.json",
+ "source": "docs/benchmarks/alpha121-expansion.json",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.121",
+ "bytes": 13132,
+ "sha256": "eec350b7b6624c80f7607466fa50baec67d7c28ce809f48a9269c0dd5a71bb90"
+ },
+ {
+ "path": "references/docs/benchmarks/alpha13-worldgen.json",
+ "source": "docs/benchmarks/alpha13-worldgen.json",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.13",
+ "bytes": 74341,
+ "sha256": "f6326fe75bb0ae8fb706019c5498c7012074a25b94bed5363202f7b10635cb20"
+ },
+ {
+ "path": "references/docs/benchmarks/alpha131-journal.json",
+ "source": "docs/benchmarks/alpha131-journal.json",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.131",
+ "bytes": 3531,
+ "sha256": "e24f7b077abbdd3a7412d7883d718ddcd0c777c59e10026dd9553d4402569f8c"
+ },
+ {
+ "path": "references/docs/blocodex-beta004.md",
+ "source": "docs/blocodex-beta004.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "beta.004",
+ "bytes": 5666,
+ "sha256": "dcaa7635f2d3d6ee7e4efa41ea69dd67d202db05c599909efb251c74aa75a455"
+ },
+ {
+ "path": "references/docs/blocodex-navigation.md",
+ "source": "docs/blocodex-navigation.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 10642,
+ "sha256": "48c3a3796753288b59e81f17b837b5448ef948922a9739ac4e29ce041312b445"
+ },
+ {
+ "path": "references/docs/blocodex.md",
+ "source": "docs/blocodex.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 15931,
+ "sha256": "f483a62f7367699da1162d55b3ac0ead603330cdb86c1e2a94026dfd9ed5f113"
+ },
+ {
+ "path": "references/docs/cartographie-open-source.md",
+ "source": "docs/cartographie-open-source.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 5113,
+ "sha256": "9e4025f3b64a10de18d57cdb729017345515b27625300eea379852ba3b7c1012"
+ },
+ {
+ "path": "references/docs/cosmologie.md",
+ "source": "docs/cosmologie.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 17724,
+ "sha256": "f1538e383cd1761ddd3c53bd0ed2b02bb9ea19d64e6611695863f5b72c6cc4b9"
+ },
+ {
+ "path": "references/docs/expansion.md",
+ "source": "docs/expansion.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 18715,
+ "sha256": "91d6f928777fadcbd4efe603c07a331e2be86d73e2a81e98313f76516c7d2867"
+ },
+ {
+ "path": "references/docs/expeditions-beta001.md",
+ "source": "docs/expeditions-beta001.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "beta.001",
+ "bytes": 8864,
+ "sha256": "f8aef578634eefb604f2b3a7670aa887dabd0e64ee223e64df3d508864606449"
+ },
+ {
+ "path": "references/docs/expeditions-beta002.md",
+ "source": "docs/expeditions-beta002.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "beta.002",
+ "bytes": 6304,
+ "sha256": "17eee8740af8adce1e4ab403a093cb74ba06bbc8a66abb4fff8abd211f108f16"
+ },
+ {
+ "path": "references/docs/generation-alpha23.1.md",
+ "source": "docs/generation-alpha23.1.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.23",
+ "bytes": 6441,
+ "sha256": "d24311ed8ddfa8814d740f07ff2aa3087017c56cdb561ba2c710ce0543554f6a"
+ },
+ {
+ "path": "references/docs/generation-alpha24.md",
+ "source": "docs/generation-alpha24.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.24",
+ "bytes": 5357,
+ "sha256": "e5a9416351bbe5603a1038d3fd900d897117979817e34c9a6bad840e02f0f2a2"
+ },
+ {
+ "path": "references/docs/generation-alpha27.md",
+ "source": "docs/generation-alpha27.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.27",
+ "bytes": 3721,
+ "sha256": "af72160ad5aa0644e258be81216541174fc8e42b7384b60dc7f85241db7b0093"
+ },
+ {
+ "path": "references/docs/generation-alpha28.md",
+ "source": "docs/generation-alpha28.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.28",
+ "bytes": 3582,
+ "sha256": "56bc02aec344392e96bd6c7ebd178240da3ccb5e30403a4be9b346c4244191ce"
+ },
+ {
+ "path": "references/docs/generation-alpha29.md",
+ "source": "docs/generation-alpha29.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.29",
+ "bytes": 3372,
+ "sha256": "7032783a37450a3c7943508015be6a5ea977c8086169fb1f299ffd03df4c36f2"
+ },
+ {
+ "path": "references/docs/generation-alpha30.1.md",
+ "source": "docs/generation-alpha30.1.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 2755,
+ "sha256": "dc749d5258e4ecf6ffc28dfdfb2764206b53fce0597540181b06f72f12047a64"
+ },
+ {
+ "path": "references/docs/generation-alpha30.2.md",
+ "source": "docs/generation-alpha30.2.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 2909,
+ "sha256": "deb1c9122170d3190ab6965fd5399d84c5345adb64d8b845a00485b18b1b59e8"
+ },
+ {
+ "path": "references/docs/generation-alpha30.3.md",
+ "source": "docs/generation-alpha30.3.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 3575,
+ "sha256": "4cdb3cb38872656e9e12051ac036d847b08b7ef98030a7efe98d51f4e431b1d8"
+ },
+ {
+ "path": "references/docs/generation-alpha30.4.md",
+ "source": "docs/generation-alpha30.4.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 3090,
+ "sha256": "8b2a892c907952c5cc09c3189f98ee001e115a666fb0ad17d56586d9fa2644e0"
+ },
+ {
+ "path": "references/docs/generation-alpha30.5.md",
+ "source": "docs/generation-alpha30.5.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 2997,
+ "sha256": "688a1acdc5ec3f15b7d61870e574504bd33f0c7e9c978b1b3aa4ad6b9f52bec4"
+ },
+ {
+ "path": "references/docs/generation-alpha30.6.md",
+ "source": "docs/generation-alpha30.6.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5765,
+ "sha256": "0c0000c337443b67b1d0847165e80b5bac70076214345ff545dd1f32253d07c9"
+ },
+ {
+ "path": "references/docs/generation-alpha30.7.md",
+ "source": "docs/generation-alpha30.7.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 4032,
+ "sha256": "7bdd3243c1f44da39bacd8fcb538babf3f36799bbf8842d5516ce65505291e3e"
+ },
+ {
+ "path": "references/docs/generation-alpha30.md",
+ "source": "docs/generation-alpha30.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 4639,
+ "sha256": "689912df99afb1d3508ef24f17ff50f49313973b8af6bf706e6d2f2dbe1dfb70"
+ },
+ {
+ "path": "references/docs/images/architecture-alpha21-overview.png",
+ "source": "docs/images/architecture-alpha21-overview.png",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.21",
+ "bytes": 2522128,
+ "sha256": "3d1ebb51a457a90d0c7f33c475a5b4f2b9c7216df1935b1c7dd2b228ebac61db"
+ },
+ {
+ "path": "references/docs/images/architecture-alpha21-roofs.png",
+ "source": "docs/images/architecture-alpha21-roofs.png",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.21",
+ "bytes": 313776,
+ "sha256": "8d7ffc0c17b8f7be614697314ea346d088c86d610590c220ffdb97457818cce6"
+ },
+ {
+ "path": "references/docs/images/architecture20-overview.png",
+ "source": "docs/images/architecture20-overview.png",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 2795611,
+ "sha256": "79c76e1d3a33b52d117bcc23bb9b3f2213cd7f1246c7f955c64bd201852b6b53"
+ },
+ {
+ "path": "references/docs/images/heritage19-seed0.svg",
+ "source": "docs/images/heritage19-seed0.svg",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 19253,
+ "sha256": "80fffdabe470b34278ad8081cec0bb1f666cf57839dbe03ad3cba55bcfb4a192"
+ },
+ {
+ "path": "references/docs/migration-26.2.md",
+ "source": "docs/migration-26.2.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 10754,
+ "sha256": "a659090b3c5c0e6003317da5b1bc8095c91677df8c0aeba846cf6942cb33ea28"
+ },
+ {
+ "path": "references/docs/packwiz.md",
+ "source": "docs/packwiz.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 62647,
+ "sha256": "99eac2d7bce99c6020250beaadbed5767a7756c448671fb24b1d6deefa442b8c"
+ },
+ {
+ "path": "references/docs/progression-beta003-contract.md",
+ "source": "docs/progression-beta003-contract.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "beta.003",
+ "bytes": 3895,
+ "sha256": "c929e2ef3ea1c7d3f735a930cbdc33a75f51c1bc429afbd92bff745d997076c7"
+ },
+ {
+ "path": "references/docs/progression-beta003-release.md",
+ "source": "docs/progression-beta003-release.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "beta.003",
+ "bytes": 7173,
+ "sha256": "869c26dbd20f785d8cf637d211eca2711816eec206c44e558828312b9a7310e6"
+ },
+ {
+ "path": "references/docs/progression-beta003.md",
+ "source": "docs/progression-beta003.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "beta.003",
+ "bytes": 13725,
+ "sha256": "edba9809688e2821be02c3b47c38f3ae257f4953917da95b8dfe774b591db465"
+ },
+ {
+ "path": "references/docs/structures-audit.md",
+ "source": "docs/structures-audit.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 15036,
+ "sha256": "06a434ce5b34b1fae6da56a535f84643a4fcd814300ba8f583b11a1eaa9ae26a"
+ },
+ {
+ "path": "references/docs/structures-conception.md",
+ "source": "docs/structures-conception.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 15946,
+ "sha256": "d11ed3f15292cddbd3e1e26f8f90087d9b95eac6d1d256c134d4c379ad7ec2fc"
+ },
+ {
+ "path": "references/docs/terrain-atlas.md",
+ "source": "docs/terrain-atlas.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 7205,
+ "sha256": "5f576c33c29e1360014abb71a701359c43fcd39011a3aa9817204f27e92d0d30"
+ },
+ {
+ "path": "references/docs/testing-alpha14.md",
+ "source": "docs/testing-alpha14.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.14",
+ "bytes": 22971,
+ "sha256": "2599f89ee3ce9e8e5cb0376683dae53dff6acbe8060d7e3792493bb1a19db519"
+ },
+ {
+ "path": "references/docs/testing-alpha15.md",
+ "source": "docs/testing-alpha15.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.15",
+ "bytes": 11614,
+ "sha256": "eb32ceb444d1985eab48e412b2dedbe61aea3018e8b613d2f1105ba4ba158623"
+ },
+ {
+ "path": "references/docs/testing-alpha16.md",
+ "source": "docs/testing-alpha16.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.16",
+ "bytes": 8675,
+ "sha256": "5c9556690433cb0a8106e8c10b013af9d1e3f699047992946ca6384b19dfbbd4"
+ },
+ {
+ "path": "references/docs/testing-alpha17.md",
+ "source": "docs/testing-alpha17.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.17",
+ "bytes": 5646,
+ "sha256": "daa5df92166b536966b30ae6e342771d83f753cb1abcd5284cad409707215ed8"
+ },
+ {
+ "path": "references/docs/testing-alpha19.md",
+ "source": "docs/testing-alpha19.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.19",
+ "bytes": 10188,
+ "sha256": "a73681440c44332c4339d09a86d2ee2970b6868307609d98fbd0910def4bcac9"
+ },
+ {
+ "path": "references/docs/testing-alpha20.md",
+ "source": "docs/testing-alpha20.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.20",
+ "bytes": 19109,
+ "sha256": "5dac810a068c0c53ccfceb1d344ec76437074ac9b26b44defb99aad46d0c6bc5"
+ },
+ {
+ "path": "references/docs/testing-alpha21.md",
+ "source": "docs/testing-alpha21.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.21",
+ "bytes": 8010,
+ "sha256": "42b9ec70e5ab33e76f54b504e9ef802f3df70dd2ba60d36237d10ba09d815dc7"
+ },
+ {
+ "path": "references/docs/testing-alpha22.md",
+ "source": "docs/testing-alpha22.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.22",
+ "bytes": 8758,
+ "sha256": "0d3012dc4a720d8eecd22ff22234a72861f04d2ece8e2a57156448c3f03c8f74"
+ },
+ {
+ "path": "references/docs/testing-alpha23.1.md",
+ "source": "docs/testing-alpha23.1.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.23",
+ "bytes": 26491,
+ "sha256": "0de11b45a211a9a8a5ee6ec07075ae2b64bead3a603750d70c368c18d36d2fc5"
+ },
+ {
+ "path": "references/docs/testing-alpha23.md",
+ "source": "docs/testing-alpha23.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.23",
+ "bytes": 5898,
+ "sha256": "0173efa9c359643e1ce7a5ab978bc3190eeefc5b9c6d298d39a3b9c751034fd3"
+ },
+ {
+ "path": "references/docs/testing-alpha24.md",
+ "source": "docs/testing-alpha24.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.24",
+ "bytes": 16300,
+ "sha256": "93020454c75a3fde84fbd57c5948e91bfab7eecc202b834c103f3166512e1eb0"
+ },
+ {
+ "path": "references/docs/testing-alpha27.md",
+ "source": "docs/testing-alpha27.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.27",
+ "bytes": 5179,
+ "sha256": "cd62a77fa91a03c51eaf331fb7405064e60061461f2d5659ebfde8e396c12867"
+ },
+ {
+ "path": "references/docs/testing-alpha28.md",
+ "source": "docs/testing-alpha28.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.28",
+ "bytes": 5596,
+ "sha256": "70248afdf424e3c42cc6d11665c86d964ecbfcbf9684ce6acc04f8f1e1e576d1"
+ },
+ {
+ "path": "references/docs/testing-alpha29.md",
+ "source": "docs/testing-alpha29.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.29",
+ "bytes": 6132,
+ "sha256": "cec41f833f65fe8157478805e445689d5bdbbc23ad01c6d1e980ab6787eb8a77"
+ },
+ {
+ "path": "references/docs/testing-alpha30.1.md",
+ "source": "docs/testing-alpha30.1.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5786,
+ "sha256": "fa8d29142c2966fa3e4bf02ce1308aa4c0b94509934e12084356ec5c97bff724"
+ },
+ {
+ "path": "references/docs/testing-alpha30.2.md",
+ "source": "docs/testing-alpha30.2.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5900,
+ "sha256": "9be015f30cf459dddb19431934c7680025130d55e4fd37ff3012fe4470008394"
+ },
+ {
+ "path": "references/docs/testing-alpha30.3.md",
+ "source": "docs/testing-alpha30.3.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5959,
+ "sha256": "e6f14b691c345e8d0366e644f47755764a6da3ec0809b18f5c2d0ffac652f160"
+ },
+ {
+ "path": "references/docs/testing-alpha30.4.md",
+ "source": "docs/testing-alpha30.4.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5348,
+ "sha256": "ac8901cb73ed9876d532aef22d6623330027b2e64a79e45a980479861b0490b3"
+ },
+ {
+ "path": "references/docs/testing-alpha30.5.md",
+ "source": "docs/testing-alpha30.5.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5418,
+ "sha256": "7d9b81c7f997b827d5cdb9e5ffca33fb1d07063b5f0e796787b83059aa8c2077"
+ },
+ {
+ "path": "references/docs/testing-alpha30.6.md",
+ "source": "docs/testing-alpha30.6.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 6176,
+ "sha256": "74467f1bae5f393595d0575edb5c8dbf8d2beb091a2adc4eba7af993399da1c1"
+ },
+ {
+ "path": "references/docs/testing-alpha30.7.md",
+ "source": "docs/testing-alpha30.7.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 5057,
+ "sha256": "65f2c983d76eb13dc7e5486231cf585540f7c50b5bedfe8226aa7021fc6485c4"
+ },
+ {
+ "path": "references/docs/testing-alpha30.md",
+ "source": "docs/testing-alpha30.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": "alpha.30",
+ "bytes": 8786,
+ "sha256": "2bd766007dfdb5bb29fbb7aa4c84c65a22bc69b2e9b73ccb4ea03754fb5e2d07"
+ },
+ {
+ "path": "references/docs/testing.md",
+ "source": "docs/testing.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 77545,
+ "sha256": "e00e519a268440eab18263677d6a1ffc9ffdff121a5c566e88e92211db6fc0b9"
+ },
+ {
+ "path": "references/docs/versioning.md",
+ "source": "docs/versioning.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 6168,
+ "sha256": "c5b55d65cf243d89b36d639a5147c46f5ad4e32e6549ba4ec0d9ccd2f48b982d"
+ },
+ {
+ "path": "references/docs/vision.md",
+ "source": "docs/vision.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 49347,
+ "sha256": "38a6b6be71371a714d6d5c6e6e8d394ff9bae341321acf032a1a0bee29a7b3e5"
+ },
+ {
+ "path": "references/docs/worldgen.md",
+ "source": "docs/worldgen.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 52098,
+ "sha256": "f00469cfd04cf51250b0dc3b95979f973f08e46d556c1b028e29202f9300f0c9"
+ },
+ {
+ "path": "references/docs/xaero-experimental.md",
+ "source": "docs/xaero-experimental.md",
+ "category": "documentation",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 4279,
+ "sha256": "26de8890d1eb42ce6c6a8ef8e9714565b416a15f37e8210f176f4893defdf287"
+ },
+ {
+ "path": "references/integration/open-maps-intake-2026-09-13.json",
+ "source": "archives/web/references/open-maps-intake-2026-09-13.json",
+ "category": "integration-intake",
+ "status": "metadata-only",
+ "version_hint": null,
+ "bytes": 16827,
+ "sha256": "2eec64927172c62f2ff4675bf457751b54da796085ceeb31042f002adf468983"
+ },
+ {
+ "path": "references/integration/xaero-intake-2026-09-13.json",
+ "source": "archives/web/references/xaero-intake-2026-09-13.json",
+ "category": "integration-intake",
+ "status": "metadata-only",
+ "version_hint": null,
+ "bytes": 3742,
+ "sha256": "49698693aaed9c8c5ca04b7401469c2b71aeb0a3520f1b084f0c592343f5eba5"
+ },
+ {
+ "path": "references/scripts/archive_terrain_atlas.py",
+ "source": "scripts/archive_terrain_atlas.py",
+ "category": "reproduction-script",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 5307,
+ "sha256": "78466c72304f2ace0a111755610b2da258500d092ff21eaa84ac66544a2ec447"
+ },
+ {
+ "path": "references/scripts/archive_web_assets.py",
+ "source": "scripts/archive_web_assets.py",
+ "category": "reproduction-script",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 13375,
+ "sha256": "f4167899e26c47dd6dbc7b29724e2c6579cf8b31c69e42cb34b9a4983aea85af"
+ },
+ {
+ "path": "references/scripts/finishing_atlas.py",
+ "source": "scripts/finishing_atlas.py",
+ "category": "reproduction-script",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 3602,
+ "sha256": "06914c452f10ac8420db27a993b779fadfd5080a50dcc9a1e0e16520e69204ed"
+ },
+ {
+ "path": "references/scripts/structure_atlas.py",
+ "source": "scripts/structure_atlas.py",
+ "category": "reproduction-script",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 5616,
+ "sha256": "6bd3d071645b4b495630fcc3384afaa6305468c6945ce0bde874cb7d20e654c3"
+ },
+ {
+ "path": "references/scripts/terrain-atlas-requirements.txt",
+ "source": "scripts/terrain-atlas-requirements.txt",
+ "category": "reproduction-script",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 32,
+ "sha256": "2b7af937bbd6f44236acaeb7316c2aa376d5a1a81c1d776a2d66ae70a54b837b"
+ },
+ {
+ "path": "references/scripts/terrain_atlas.py",
+ "source": "scripts/terrain_atlas.py",
+ "category": "reproduction-script",
+ "status": "snapshot-at-archive-date",
+ "version_hint": null,
+ "bytes": 8019,
+ "sha256": "603c36ac40c711e954909049daaf2ab9a52f1582fcc477f44ae4769dfeefdacb"
+ },
+ {
+ "path": "index.html",
+ "source": "generated-by:scripts/archive_web_assets.py",
+ "category": "archive-index",
+ "status": "archive-navigation",
+ "version_hint": null,
+ "bytes": 219040,
+ "sha256": "c4d6e6e4cc649d792aa6b646bb868f2b535b1f3551b8964d4e87a0d287e91646"
+ },
+ {
+ "path": "README.md",
+ "source": "generated-by:scripts/archive_web_assets.py",
+ "category": "archive-index",
+ "status": "archive-navigation",
+ "version_hint": null,
+ "bytes": 1243,
+ "sha256": "ef02c2f5f1c7d5220bd056beeef0bfff05631dd3382f7e7882f61b761afe3aac"
+ }
+ ]
+}
diff --git a/archives/web/receipt-2026-09-13-web-v1.json b/archives/web/receipt-2026-09-13-web-v1.json
new file mode 100644
index 0000000..5132098
--- /dev/null
+++ b/archives/web/receipt-2026-09-13-web-v1.json
@@ -0,0 +1,28 @@
+{
+ "id": "2026-09-13-web-v1",
+ "zip": "snapshots/Sanctuary-Web-Archive-2026-09-13-v1.zip",
+ "zip_bytes": 196242020,
+ "zip_sha256": "5e6277942e791a928b70c2b11490d5367f6b10a4b6d3b196c9cef703b1e431d5",
+ "manifest_sha256": "735e08a304a398fdb3d95c73c9c8f95c87027ae9d0459f708cfac044dd837622",
+ "file_count": 782,
+ "counts": {
+ "alpha-figures": 13,
+ "alpha-research": 406,
+ "alpha-atlas": 248,
+ "beta-previews": 17,
+ "creator-icons": 6,
+ "title-reference": 1,
+ "alpha-release-atlas": 3,
+ "blocodex-concept": 3,
+ "documentation": 74,
+ "integration-intake": 2,
+ "reproduction-script": 6,
+ "archive-index": 2
+ },
+ "checks": [
+ "copies_sha256",
+ "three_original_atlas_manifests",
+ "zip_crc",
+ "zip_members_sha256"
+ ]
+}
diff --git a/archives/web/references/open-maps-intake-2026-09-13.json b/archives/web/references/open-maps-intake-2026-09-13.json
new file mode 100644
index 0000000..16d71b0
--- /dev/null
+++ b/archives/web/references/open-maps-intake-2026-09-13.json
@@ -0,0 +1,584 @@
+{
+ "checked_at": "2026-09-13",
+ "target": "26.3-pre-2",
+ "projects": [
+ {
+ "slug": "simple-atlas",
+ "title": "Simple Atlas",
+ "license": {
+ "id": "MIT",
+ "name": "MIT License",
+ "url": null
+ },
+ "source_url": "https://github.com/RubberToe-06/simple_atlas",
+ "description": "A no-nonsense Vanilla+ Atlas for all your mapping needs! Heavily inspired by Map Atlases by Pepperoni-Jabroni",
+ "fabric_latest": [
+ {
+ "id": "6Hy4rXil",
+ "version_number": "1.2.0",
+ "date_published": "2026-06-18T20:58:16.322322Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric",
+ "quilt"
+ ],
+ "dependencies": [
+ {
+ "version_id": "Nv3xnWXd",
+ "project_id": "9s6osm5g",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [
+ {
+ "id": "6Hy4rXil",
+ "version_number": "1.2.0",
+ "date_published": "2026-06-18T20:58:16.322322Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric",
+ "quilt"
+ ],
+ "dependencies": [
+ {
+ "version_id": "Nv3xnWXd",
+ "project_id": "9s6osm5g",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_target": []
+ },
+ {
+ "slug": "improved-maps",
+ "title": "Improved Maps",
+ "license": {
+ "id": "MIT",
+ "name": "MIT License",
+ "url": null
+ },
+ "source_url": "https://github.com/craftycorvid/ImprovedMaps",
+ "description": "Server-side mod (with optional client-side features) implementing Atlases and other map features",
+ "fabric_latest": [
+ {
+ "id": "kS9puVhR",
+ "version_number": "1.0",
+ "date_published": "2026-08-21T22:56:45.560111Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "mOgUt4GM",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "xGdtZczs",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "1eAoo2KR",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [
+ {
+ "id": "kS9puVhR",
+ "version_number": "1.0",
+ "date_published": "2026-08-21T22:56:45.560111Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "mOgUt4GM",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "xGdtZczs",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "1eAoo2KR",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_target": []
+ },
+ {
+ "slug": "ultimate_map_atlases",
+ "title": "Ultimate Map Atlases",
+ "license": {
+ "id": "GPL-3.0-only",
+ "name": "GNU General Public License v3.0 only",
+ "url": null
+ },
+ "source_url": "https://github.com/nanakytim/Ultimate_Map_Atlases",
+ "description": "Atlases can be crafted and store Maps, making exploration easier and more fun! Works in tandem with a Compass.",
+ "fabric_latest": [
+ {
+ "id": "bgd39YOI",
+ "version_number": "26.2",
+ "date_published": "2026-06-17T13:27:13.516403Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": []
+ }
+ ],
+ "fabric_26_2": [
+ {
+ "id": "bgd39YOI",
+ "version_number": "26.2",
+ "date_published": "2026-06-17T13:27:13.516403Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": []
+ }
+ ],
+ "fabric_target": []
+ },
+ {
+ "slug": "bluemap",
+ "title": "BlueMap",
+ "license": {
+ "id": "MIT",
+ "name": "MIT License",
+ "url": "https://github.com/BlueMap-Minecraft/BlueMap/blob/master/LICENSE"
+ },
+ "source_url": "https://github.com/BlueMap-Minecraft/BlueMap",
+ "description": "A Minecraft mapping tool that creates 3D models of your Minecraft worlds and displays them in a web viewer.",
+ "fabric_latest": [
+ {
+ "id": "xvccCRD9",
+ "version_number": "5.24-fabric",
+ "date_published": "2026-09-10T15:38:16.522019Z",
+ "game_versions": [
+ "26.1",
+ "26.1.1",
+ "26.1.2",
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [
+ {
+ "id": "xvccCRD9",
+ "version_number": "5.24-fabric",
+ "date_published": "2026-09-10T15:38:16.522019Z",
+ "game_versions": [
+ "26.1",
+ "26.1.1",
+ "26.1.2",
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_target": []
+ },
+ {
+ "slug": "map-atlases-forge",
+ "title": "Map Atlases [Forge]",
+ "license": {
+ "id": "GPL-3.0-only",
+ "name": "GNU General Public License v3.0 only",
+ "url": null
+ },
+ "source_url": "https://github.com/MehVahdJukaar/mapatlases-neoforge",
+ "description": "A world map/mini map mod based on vanilla Maps!",
+ "fabric_latest": [],
+ "fabric_26_2": [],
+ "fabric_target": []
+ },
+ {
+ "slug": "antique-atlas",
+ "title": "Antique Atlas",
+ "license": {
+ "id": "GPL-3.0-only",
+ "name": "GNU General Public License v3.0 only",
+ "url": null
+ },
+ "source_url": "https://github.com/AntiqueAtlasTeam/AntiqueAtlas",
+ "description": "Antique Atlas is a craftable item that enables a special map screen.",
+ "fabric_latest": [
+ {
+ "id": "BTztSCC6",
+ "version_number": "7.1.1-fabric-mc1.18.2",
+ "date_published": "2023-03-30T19:15:50.719038Z",
+ "game_versions": [
+ "1.18.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": "j5zDzQqi",
+ "project_id": "lhGA9TYQ",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": "BLMp2TRt",
+ "project_id": "9s6osm5g",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": "95QMsRyb",
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [],
+ "fabric_target": []
+ },
+ {
+ "slug": "antique-atlas-4",
+ "title": "Antique Atlas 4",
+ "license": {
+ "id": "LGPL-3.0-or-later",
+ "name": "GNU Lesser General Public License v3.0 or later",
+ "url": null
+ },
+ "source_url": "https://github.com/sisby-folk/antique-atlas",
+ "description": "A hand-drawn clientside world map, with map sharing, structure discovery, and less!\nA map frontend for Surveyor based on hunternif's Antique Atlas.",
+ "fabric_latest": [
+ {
+ "id": "2sDsTAId",
+ "version_number": "3.1.2+1.21",
+ "date_published": "2026-01-05T08:52:18.929507Z",
+ "game_versions": [
+ "1.21.1"
+ ],
+ "loaders": [
+ "fabric",
+ "neoforge",
+ "quilt"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "tNmWwdI2",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": "4AkOEqGy",
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": "iLOZqG06",
+ "project_id": "4KjqhPc9",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [],
+ "fabric_target": []
+ },
+ {
+ "slug": "surveyor",
+ "title": "Surveyor Map Framework",
+ "license": {
+ "id": "LGPL-3.0-or-later",
+ "name": "GNU Lesser General Public License v3.0 or later",
+ "url": null
+ },
+ "source_url": "https://github.com/sisby-folk/surveyor",
+ "description": "Maps with friends! A world map backend with multiplayer sharing, automatic structure/POI marking, and unified mod compatibility.",
+ "fabric_latest": [
+ {
+ "id": "V7SEIRs6",
+ "version_number": "1.2.4+26.1",
+ "date_published": "2026-05-10T08:39:52.185927Z",
+ "game_versions": [
+ "26.1.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "tNmWwdI2",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": "mmmOfXE7",
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [],
+ "fabric_target": []
+ },
+ {
+ "slug": "voxelmap-updated",
+ "title": "VoxelMap-Updated",
+ "license": {
+ "id": "LicenseRef-All-Rights-Reserved",
+ "name": "",
+ "url": null
+ },
+ "source_url": "https://github.com/fantahund/VoxelMap",
+ "description": "Minimap and Worldmap. Have an overview of your surroundings, or view the entire world. Create waypoints.",
+ "fabric_latest": [
+ {
+ "id": "BjsGekii",
+ "version_number": "26.2-1.16.10",
+ "date_published": "2026-08-30T03:23:58.999214Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [
+ {
+ "id": "BjsGekii",
+ "version_number": "26.2-1.16.10",
+ "date_published": "2026-08-30T03:23:58.999214Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_target": []
+ },
+ {
+ "slug": "xaeroplus",
+ "title": "XaeroPlus",
+ "license": {
+ "id": "MIT",
+ "name": "MIT License",
+ "url": null
+ },
+ "source_url": "https://github.com/rfresh2/XaeroPlus/",
+ "description": "Xaero WorldMap / Minimap Extra Features",
+ "fabric_latest": [
+ {
+ "id": "kbQweqjZ",
+ "version_number": "2.36.1+fabric-1.21.11",
+ "date_published": "2026-09-10T21:10:52.358083Z",
+ "game_versions": [
+ "1.21.11"
+ ],
+ "loaders": [
+ "fabric",
+ "quilt"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "FlFKBOIX",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "1bokaNcj",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "NcUtCpym",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_26_2": [
+ {
+ "id": "Ts3KSCxm",
+ "version_number": "2.36.1+fabric-26.2",
+ "date_published": "2026-09-10T21:07:47.448275Z",
+ "game_versions": [
+ "26.2"
+ ],
+ "loaders": [
+ "fabric",
+ "quilt"
+ ],
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "1bokaNcj",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "NcUtCpym",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "FlFKBOIX",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ]
+ }
+ ],
+ "fabric_target": []
+ }
+ ],
+ "licenses_verified_in_source": [
+ {
+ "repository": "craftycorvid/ImprovedMaps",
+ "default_branch": "main",
+ "license_spdx": "MIT",
+ "license_url": "https://github.com/craftycorvid/ImprovedMaps/blob/main/LICENSE",
+ "license_git_sha": "e67ce634ddce2140ca1546d2e5beb1c04859f60f",
+ "license_sha256": "a566859281767627d275c468319d0713d86f317b6f798ba9bf48d6da07991b32",
+ "archived": false
+ },
+ {
+ "repository": "RubberToe-06/simple_atlas",
+ "default_branch": "master",
+ "license_spdx": "MIT",
+ "license_url": "https://github.com/RubberToe-06/simple_atlas/blob/master/LICENSE",
+ "license_git_sha": "705cd5167be9a1045008e89018f67e9e501b272f",
+ "license_sha256": "9766022d74839286286f97d9f3928033bd15445605bdaab4ff4806b434c08c67",
+ "archived": false
+ },
+ {
+ "repository": "nanakytim/Ultimate_Map_Atlases",
+ "default_branch": "main",
+ "license_spdx": "GPL-3.0",
+ "license_url": "https://github.com/nanakytim/Ultimate_Map_Atlases/blob/main/LICENSE",
+ "license_git_sha": "f288702d2fa16d3cdf0035b15a9fcbc552cd88e7",
+ "license_sha256": "3972dc9744f6499f0f9b2dbf76696f2ae7ad8af9b23dde66d6af86c9dfb36986",
+ "archived": false
+ },
+ {
+ "repository": "BlueMap-Minecraft/BlueMap",
+ "default_branch": "master",
+ "license_spdx": "MIT",
+ "license_url": "https://github.com/BlueMap-Minecraft/BlueMap/blob/master/LICENSE",
+ "license_git_sha": "0a98021f607f47cc173f081a768e99e12bd2f6a3",
+ "license_sha256": "f1c190d4a1ff29606cbed7a0d97fc7b7117cc2e1169e19caa753198957dbbfd6",
+ "archived": false
+ }
+ ]
+}
diff --git a/archives/web/references/xaero-intake-2026-09-13.json b/archives/web/references/xaero-intake-2026-09-13.json
new file mode 100644
index 0000000..5a81c7d
--- /dev/null
+++ b/archives/web/references/xaero-intake-2026-09-13.json
@@ -0,0 +1,116 @@
+{
+ "checked_at": "2026-09-13",
+ "target": "26.3-pre-2",
+ "projects": [
+ {
+ "project": "xaeros-minimap",
+ "license": {
+ "id": "LicenseRef-All-Rights-Reserved",
+ "name": "",
+ "url": null
+ },
+ "source_url": null,
+ "versions": {
+ "26.2": {
+ "count": 9,
+ "latest": [
+ {
+ "id": "tBIPWIKW",
+ "version_number": "fabric-26.2-26.5.0",
+ "date_published": "2026-09-10T11:44:27.035690Z",
+ "version_type": "release",
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ },
+ {
+ "version_id": null,
+ "project_id": "gF3BGWvG",
+ "file_name": null,
+ "dependency_type": "optional"
+ }
+ ],
+ "files": [
+ {
+ "id": "MR2dtOXn",
+ "hashes": {
+ "sha1": "40282debf4cd8e91712ba26d90e78f2e3eb067e1",
+ "sha512": "b00df8bb410fae12b08788044f2f03cec34bfc4506da419d11d364e2f229b814dbb272d297fbe4b0cdfac18beeeed4dc8f1bdfc96fb113867857527e31965049"
+ },
+ "url": "https://cdn.modrinth.com/data/1bokaNcj/versions/tBIPWIKW/xaerominimap-fabric-26.2-26.5.0.jar",
+ "filename": "xaerominimap-fabric-26.2-26.5.0.jar",
+ "primary": true,
+ "size": 2221887,
+ "file_type": null
+ }
+ ]
+ }
+ ]
+ },
+ "26.3-pre-2": {
+ "count": 0,
+ "latest": []
+ }
+ },
+ "metadata_source": "https://api.modrinth.com/v2/project/xaeros-minimap"
+ },
+ {
+ "project": "xaeros-world-map",
+ "license": {
+ "id": "LicenseRef-All-Rights-Reserved",
+ "name": "",
+ "url": null
+ },
+ "source_url": null,
+ "versions": {
+ "26.2": {
+ "count": 10,
+ "latest": [
+ {
+ "id": "DxU1zijU",
+ "version_number": "fabric-26.2-1.46.0",
+ "date_published": "2026-09-10T11:32:01.420372Z",
+ "version_type": "release",
+ "dependencies": [
+ {
+ "version_id": null,
+ "project_id": "gF3BGWvG",
+ "file_name": null,
+ "dependency_type": "optional"
+ },
+ {
+ "version_id": null,
+ "project_id": "P7dR8mSH",
+ "file_name": null,
+ "dependency_type": "required"
+ }
+ ],
+ "files": [
+ {
+ "id": "Acre79W6",
+ "hashes": {
+ "sha512": "37284868eed7bb105362ba1df3da38e860a0f889e50dcae43ddaf167c33817b8ece289770f89b138e327b145d15ae36a4df850202770236a4eefd7b413158438",
+ "sha1": "1056e2f81860045a8c1fb1d6823ca3f4c832dc60"
+ },
+ "url": "https://cdn.modrinth.com/data/NcUtCpym/versions/DxU1zijU/xaeroworldmap-fabric-26.2-1.46.0.jar",
+ "filename": "xaeroworldmap-fabric-26.2-1.46.0.jar",
+ "primary": true,
+ "size": 1477476,
+ "file_type": null
+ }
+ ]
+ }
+ ]
+ },
+ "26.3-pre-2": {
+ "count": 0,
+ "latest": []
+ }
+ },
+ "metadata_source": "https://api.modrinth.com/v2/project/xaeros-world-map"
+ }
+ ]
+}
diff --git a/build.gradle b/build.gradle
index e66d49d..6af33b6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,8 +3,8 @@ plugins {
id 'net.fabricmc.fabric-loom' version "${loom_version}" apply false
}
-tasks.named('build') { dependsOn(':sanctuary:build') }
-tasks.named('check') { dependsOn(':sanctuary:check', 'verifyPack') }
+tasks.named('build') { dependsOn(':sanctuary:build', ':demeure:build', ':jei:build', ':sanctuary-test:build') }
+tasks.named('check') { dependsOn(':sanctuary:check', ':demeure:check', ':jei:check', ':sanctuary-test:check', 'verifyPack') }
tasks.register('verifyPack', Exec) {
group = 'verification'
@@ -18,3 +18,10 @@ tasks.register('assemblePack', Exec) {
dependsOn(':sanctuary:build', 'verifyPack')
commandLine('python3', 'scripts/pack.py', 'assemble')
}
+
+tasks.register('assembleTestPack', Exec) {
+ group = 'distribution'
+ description = 'Stage the separate flat-world Sanctuary Test profile.'
+ dependsOn('assemblePack', ':sanctuary-test:build')
+ commandLine('python3', 'scripts/test_pack.py')
+}
diff --git a/docs/atlas-altitude-beta058.md b/docs/atlas-altitude-beta058.md
new file mode 100644
index 0000000..332b62c
--- /dev/null
+++ b/docs/atlas-altitude-beta058.md
@@ -0,0 +1,96 @@
+# beta.058 — altitude de la carte
+
+Ticket `codex/atlas-altitude-beta058`.
+
+## Contrat avant implémentation
+
+Deux boutons ↑ / ↓ sous Recentrer et le zoom choisissent les plafonds
+0, 140, 280, 420, 560 et 640. La carte ouvre à 640 (surface habituelle) ;
+chaque plafond montre le premier bloc coloré situé à cette altitude ou plus
+bas. C'est une coupe horizontale : une roche coupée reste visible, sans
+chercher automatiquement une grotte. Le dernier pas fait 80 blocs.
+La position et le zoom ne changent pas. Les extrémités désactivent leur bouton.
+Chunks et Demeure sont désactivés par défaut, réactivables par leurs boutons.
+Le cadre central et ses quatre panneaux assombris sont retirés. Le nord
+conserve son emplacement.
+
+Les relevés restent personnels et utilisent le même masque d'exploration
+horizontal qu'auparavant. Les six plafonds sont relevés lors des nouvelles
+visites, même avant l'achat de Grande carte. Aucun chunk distant n'est chargé
+ou généré. La vue opérateur inspecte seulement les chunks chargés et n'inscrit
+pas ses consultations dans les découvertes personnelles.
+
+## Contrat de données additif
+
+Le fichier existant `data/sanctuary-atlas/.json` et son schéma 2 restent
+la référence de la surface à 640. Aucune conversion ni réinterprétation des
+anciens pixels. Les cinq autres plafonds utilisent le même format dans des
+fichiers distincts `data/sanctuary-atlas/layers//.json`.
+Les anciens relevés de surface ne permettent pas d'inventer leurs couleurs
+inférieures : ces dernières apparaissent lors des revisites. Un retour à
+beta.057 ignore simplement les fichiers supplémentaires ; la surface reste
+lisible. Aucun monde personnel n'est ouvert ou modifié pendant le travail.
+
+Chaque plafond conserve la limite de 16 384 chunks. Le client ne garde que
+la couche affichée ; le token réseau change avec l'altitude, empêchant les
+anciens paquets de repeindre une autre couche. Le protocole `atlas_request`
+ajoute un plafond validé : client et serveur doivent avoir la même version.
+Le serveur relève un chunk à la fois et saute les sections entièrement vides.
+Les sauvegardes périodiques des couches sont étalées dans le temps.
+
+## Vérifications
+
+Le parcours natif `Atlas058ClientChecks` réussit sur la version finale en **1 min 44 s**, avec un
+vrai monde de développement Sanctuary de 640 blocs de haut :
+
+- couleurs différentes aux six plafonds, coupe inclusive, verre transparent,
+ colonne vide, accord entre le relevé d'une couche et celui des six couches ;
+- absence de chargement d'un chunk distant et masque d'exploration conservé ;
+- clics réels sur les boutons, limites 0/640, changement rapide, rejet des
+ anciens paquets terrain et Demeure, cadrage/zoom stables après rafraîchissement ;
+- relecture des six fichiers, absence de réécriture quand rien ne change,
+ achat de Grande carte après relevé, refus d'une requête opérateur non autorisée ;
+- codec réseau, refus des altitudes invalides, libellés FR/EN, petite interface ;
+- Chunks/Demeure désactivés à l'ouverture mais réactivables, absence du cadre
+ assombri et emplacement du nord conservé (captures inspectées).
+
+Commande :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -x :sanctuary:runGameTest \
+ -PsanctuaryClientTests=true -PsanctuaryAtlas058ClientTests=true \
+ -PsanctuaryQuickTests=true
+```
+
+Journal : `build/atlas058-client.log`, marqueur `ATLAS058_PASS`.
+Captures inspectées : `build/atlas058-evidence/`.
+
+Le brouillard reste horizontal, comme dans les cartes existantes : ce lot
+n'introduit pas de visibilité optique ni de journal de découverte par caverne.
+Demeure reste une empreinte de chunk, sans séparation verticale des bâtiments.
+Au plafond maximal, les pixels de surface restent compatibles ; les anciens
+parcours ne sont pas reconstruits aux autres altitudes sans revisite.
+La charge d'un serveur rempli pendant une saison n'a pas été mesurée. Le
+plafond théorique est de 24 Mio de couleurs brutes par joueur pour six couches
+pleines, hors index et sérialisation, contre 4 Mio pour la surface seule.
+Chaque fichier reste borné à 12 Mio ; un fichier invalide est préservé et
+suspend l'atlas du joueur concerné. Les sauvegardes tournent sur les six
+couches toutes les 12 secondes, puis sont vidées à la déconnexion/fermeture.
+Aucun serveur dédié démarré, aucune EULA acceptée.
+
+
+## Livraison vérifiée
+
+`check build assemblePack assembleTestPack`, avec compilation des tests client
+et exclusion du serveur dédié `runGameTest`, réussit en **2 min 23 s**
+(`build/atlas058-check.log`). 1 601 classes du JAR correspondent au build.
+ZIP, versions, ressources, isolation du mod de test et JAR embarqués contrôlés.
+JEI et le code Demeure sont inchangés. Les archives beta.057 restent intactes.
+
+- Pack normal : `build/Sanctuary-beta.058.mrpack`, 5627223 octets ;
+ SHA-256 `bc9af60be020fd32f99a173638e9006cab128cf942df666e34e03824bac6f892`.
+- Pack de test : `build/Sanctuary-Test-beta.058.mrpack`, 5646157 octets ;
+ SHA-256 `010eb08df83117669d2f13d3c2e8641c965fac3ddec11fbab419f01a9cb94942`.
+- Reçu : `build/atlas058-artifact.json`.
+
+Aucune publication de canal ni synchronisation d'instance personnelle.
diff --git a/docs/atlas-beta005.md b/docs/atlas-beta005.md
new file mode 100644
index 0000000..5f2e47b
--- /dev/null
+++ b/docs/atlas-beta005.md
@@ -0,0 +1,118 @@
+# MAP-01 — carte Sanctuary native, beta.005
+
+Ticket autorisé le 13 septembre 2026, branche `codex/native-map-beta005`.
+Carte de dessus écrite dans Sanctuary, inspirée des interactions de Xaero
+(glisser, zoomer, se recentrer). Aucun code, asset ou dépendance Xaero repris.
+
+## Comportement livré
+
+Le Blocodex ouvre une carte de surface de l'Overworld : nord en haut, terrain
+coloré, position et orientation de l'habitant, déplacement, zoom, recentrage.
+L'aptitude **Grande carte** coûte provisoirement 4 niveaux, configurables.
+L'exploration commence avant cet achat, conformément au choix du créateur.
+La mini-carte, les marqueurs et le partage volontaire entre habitants restent
+des tickets distincts. Aucun partage automatique de découvertes.
+
+La vue habitant affiche ses propres relevés. La vue opérateur est un choix
+explicite contrôlé par le serveur (`COMMANDS_GAMEMASTER`, commandes autorisées
+en solo). Elle peut inspecter les chunks déjà chargés, sans les inscrire dans
+les découvertes personnelles et sans charger/générer de terrain supplémentaire.
+La carte représente la surface supérieure, sans carte des grottes ni radar.
+
+Les couleurs de création d'habitant affichent des noms FR/EN (Azur,
+Pamplemousse, Sauge…) associés à leur teinte, en caractères Minecraft normaux.
+Les boutons et leur texte coloré restent natifs ; l'attribution RGB ne change
+pas. Les longs noms disposent du défilement et de l'infobulle vanilla.
+La carte laisse le monde évoluer pendant sa consultation, y compris en solo.
+
+## Contrat de sauvegarde et migration, écrit avant le code
+
+Les identifiants, chunks, graines et contrats de génération sont inchangés.
+Seuls les mondes dont la progression Sanctuary est déjà active participent.
+
+L'attachement `sanctuary:progression` passe du schéma 1 au schéma 2 : ajout du
+booléen `atlas`, initialement faux. Le lecteur valide les anciens champs et
+l'historique puis les conserve ; un prochain achat écrit le schéma 2. Les rangs,
+l'aptitude Noms, les coûts déjà payés et dates ne changent pas. Les morts et
+reconnexions conservent l'achat de Grande carte. Aucun prestige introduit.
+
+`progression.json` schéma 1 reste accepté sans réécriture : `atlasCost` vaut 4
+par défaut. Les nouvelles configurations utilisent le schéma 2 et ce champ.
+Pour régler le prix dans un ancien fichier, passer `schema` à 2 et ajouter
+`"atlasCost": 4` (ou le prix voulu), en conservant les autres valeurs.
+
+Des fichiers additifs `data/sanctuary-atlas/.json` (schéma 1) conservent
+les relevés de surface de l'Overworld, liés à l'UUID et à la graine. Une tuile
+contient 16×16 couleurs de carte vanilla. Les relevés portent sur les chunks
+chargés dans un rayon de deux chunks autour de l'habitant, progressivement.
+Il s'agit d'une proximité cartographiée, pas d'une visibilité optique exacte.
+Les revisites actualisent le terrain ; les zones lointaines restent le dernier
+relevé. Les parcours antérieurs à beta.005 ne peuvent pas être reconstitués.
+
+Écriture atomique périodique et à la déconnexion/fermeture ; les derniers relevés
+peuvent être perdus lors d'un arrêt brutal. Une limite de 16 384 chunks par
+habitant borne ce premier lot ; une carte pleine reste consultable et signale
+qu'elle ne peut plus ajouter de zones. Un fichier invalide est conservé et
+désactive la carte concernée, sans effacer les données ni bloquer les autres.
+
+Avant une utilisation dans une sauvegarde personnelle, conserver une sauvegarde
+complète. Un retour à beta.004 exige la copie antérieure à la migration : son
+lecteur de progression ne connaît pas les achats `atlas`. Aucun monde personnel
+n'est ouvert par ce ticket, les essais utilisent des mondes de développement.
+
+## Vérification
+
+- `./gradlew check build -PsanctuaryFocusedTests=progression,map` : contrats
+ de progression et de relevés, et **7/7 tests natifs serveur** réussis.
+- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true
+ -PsanctuaryProgressionClientTests=true` : réussi, **1 min 46 s**. Vrai client,
+ création avant spawn, noms de couleurs FR, achat réel de Grande carte,
+ relevés antérieurs à l'achat, zoom/glisser/recentrer, GUI 320×240 et autres
+ tailles, commandes désactivées puis activées, inspection explicite,
+ révocation et effacement des tuiles opérateur, retour au menu pause,
+ mort et redémarrage du même monde de test.
+- La maquette archivée et les packs beta.003/004 restent immuables.
+- Captures : `build/beta005-preview/`. Logs : `build/beta005-client.log`,
+ `build/beta005-check-build.log`, `build/beta005-delivery.log`.
+
+Les essais ne mesurent pas encore une charge de 30 joueurs ni la durée d'une
+saison complète. La suite générale garde les deux assertions worldgen
+historiques signalées en beta.004 (hauteur 384 attendue contre 640 et support
+de sédiment seed 0) ; ce lot n'a pas modifié la génération.
+
+## Tester
+
+Installer le pack beta.005 côté client et la même version côté serveur.
+Dans le Blocodex (**B** ou **Échap**), acheter **Grande carte** dans Progression,
+puis ouvrir **Monde → Grande carte**. Glisser pour déplacer, molette ou boutons
+pour zoomer, Recentrer ou Début pour revenir au joueur. Les flèches déplacent
+la vue. Une zone noire n'est pas encore relevée ; le vide relevé est bleu sombre.
+Le relevé complet des environs immédiats prend environ cinq secondes.
+
+Avec les commandes autorisées en solo ou les droits opérateur, ouvrir la même
+carte puis choisir **Vue opérateur**. Cet accès ne nécessite pas l'achat de
+l'aptitude ; revenir à **Vue habitant** retrouve ses seuls relevés et l'exigence
+de l'aptitude. L'inspection ne charge pas de terrain distant : une zone absente
+peut simplement ne plus être chargée côté serveur.
+
+Les relevés couvrent l'Overworld supérieur, par proximité de deux chunks, sans
+reconstituer les parcours antérieurs à beta.005. Les marqueurs, mini-carte,
+partages, grottes, statistiques/advancements dans le Blocodex, filtrage des blocs
+inconnus et vitrines/favoris de l'habitant restent les prochains tickets.
+
+
+## Livraison locale vérifiée
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,map`
+réussit en 4 min 09 s ; **7/7 tests natifs serveur**. Export packwiz puis
+vérification du ZIP, JAR embarqué, versions internes, dépendance Fabric API,
+index packwiz, libellés FR/EN, six icônes originales et absence du mod de test.
+
+- MRpack : `build/Sanctuary-beta.005.mrpack`, 2 248 215 octets.
+- SHA-256 : `4a71465349af334b5bdeed4b0cc42dbef55de94e6acb7f0ab798cda477e6dc0e`.
+- Reçu : `build/atlas005-artifact.json`.
+- JAR : `mods/sanctuary/build/libs/sanctuary-beta.005.jar`.
+
+Les empreintes des MRpacks beta.003/004 et des deux ZIP d'archives Web v1 sont
+inchangées. Aucune publication distante, synchronisation Prism ou installation
+sur un serveur personnel n'a été effectuée.
diff --git a/docs/atlas-immersive-beta007.md b/docs/atlas-immersive-beta007.md
new file mode 100644
index 0000000..d717a8b
--- /dev/null
+++ b/docs/atlas-immersive-beta007.md
@@ -0,0 +1,82 @@
+# MAP-03 — Carte immersive — beta.007
+
+Branche : `codex/atlas-immersive`. Cible : Minecraft **26.3-pre-2**, Fabric
+Loader **0.19.5**, Fabric API **0.160.0+26.3**. Livraison locale d'essai.
+
+## Résultat
+
+**B → Monde → Grande carte** utilise toute la surface de l'écran. La carte
+continue sous les onglets, les commandes et le pied de page. Le carré central
+reste clair ; les abords sont assombris par des panneaux translucides. Le carré
+ne limite ni le terrain ni les gestes de navigation.
+
+Journal, recentrage et zoom sont superposés à gauche ; vue habitant/opérateur,
+grille de chunks et empreintes Demeure à droite. Le titre et les onglets natifs
+restent en haut, **Done / Terminé** en bas. Les coordonnées sous le pointeur,
+le zoom et la vue active sont affichés en bas à gauche sur un fond contrasté.
+Les autres écrans du Blocodex gardent leur cadre.
+
+Le glisser-déplacer et la molette fonctionnent sur les zones sombres jusqu'aux
+bords de l'écran. Les boutons gardent la priorité sur la carte, même lorsqu'ils
+sont désactivés. Grille, empreintes et indicateur du joueur utilisent les mêmes
+coordonnées que le terrain. Le zoom minimal s'adapte à la taille du GUI pour
+préserver la couverture de la fenêtre cartographique de 1 024 × 1 024 blocs.
+
+## Transparence et données
+
+L'inconnu et le vide observé sont transparents dans la texture ; le fond natif
+du jeu reste visible derrière. Leurs états restent distincts dans les relevés.
+La grille peut traverser une zone transparente lorsqu'elle est activée ; les
+empreintes ne peignent que les pixels de terrain effectivement connus.
+
+Les relevés personnels, l'exploration avant l'achat de Grande carte et le
+brouillard de beta.006 sont conservés. La vue opérateur reste une action
+explicite vérifiée par le serveur. Un changement de vue ou une révocation
+efface l'image précédente avant son prochain rendu.
+
+Ce ticket modifie le rendu et les entrées client. Il ne change ni les formats
+de sauvegarde, ni le protocole des relevés, ni le terrain ou les expansions.
+Demeure garde son rôle d'empreinte, sans réservation ni protection. Mini-carte,
+marqueurs et partage restent dans leurs tickets respectifs.
+
+## Vérifications et artefact
+
+- Client Minecraft natif :
+ `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryProgressionClientTests=true`
+ réussi en **1 min 51 s**. Paquets réels, achat de Grande carte, texture alpha,
+ molette et gestes via `MouseHandler`, glissements successifs et dans les quatre
+ marges, priorité des boutons, recentrage, grille, empreintes, changement de
+ rôle, révocation, fermeture native et retour au Blocodex vérifiés.
+- Captures inspectées à **640×480**, **1280×720** (GUI automatique),
+ **1920×1080** (échelles 1 et 3), ainsi qu'en français. Les boutons ne se
+ chevauchent pas. La vue opérateur montre le terrain continu derrière le
+ haut, le bas et les côtés. Captures conservées dans `build/beta007-preview/`.
+- Un premier passage a détecté que `AbstractWidget.isMouseOver` ignore les
+ boutons désactivés : leur zone laissait passer la molette. Le calcul utilise
+ désormais les limites du bouton visible ; le parcours complet a été relancé
+ avec succès. Journaux `build/beta007-client.log` (diagnostic) et
+ `build/beta007-client-final.log` (validation).
+
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,map,demeure`
+ réussi en **4 min 4 s**, dont **8/8 tests natifs serveur**. Les contrôles de
+ contrats Atlas/Demeure et de progression passent également. La sélection
+ native est ciblée ; les deux assertions worldgen générales déjà connues en
+ beta.003/005 ne sont pas revendiquées corrigées. Journal :
+ `build/beta007-delivery.log`.
+- MRpack exporté avec packwiz puis vérifié : CRC ZIP, contenu exact, identité
+ des JAR construits, versions internes, dépendance Fabric API, index packwiz,
+ notices et licence Demeure, 378 clés FR/EN alignées et six icônes originales.
+ Les exports beta.003, .004, .005 et .006 conservent leurs empreintes.
+
+## Artefact d'essai
+
+[Sanctuary-beta.007.mrpack](../build/Sanctuary-beta.007.mrpack), **2 299 820 octets**.
+Demeure reste embarqué dans le JAR Sanctuary ; aucun ajout manuel nécessaire.
+
+- SHA-256 MRpack : `bc082e9143e15aea8f231b1205ed5e27cd9bf7b4e6a81d4895f69ff2a11e660d`.
+- SHA-256 Sanctuary : `7d14d6019176f0d413dfe27a2b0e9a8fe419ed88aad812d9c045dfb13f809752`.
+- SHA-256 Demeure : `70108b80df46e4dbcf2540b7a260b1c247bb8ea28db684f49b15b407d8853e43`.
+
+Reçu : `build/atlas007-artifact.json` ; vérificateur :
+`build/verify-beta007-pack.py`. Export local uniquement. Aucun monde personnel,
+canal de publication ou instance Prism n'a été modifié.
diff --git a/docs/atlas-markers-beta059.md b/docs/atlas-markers-beta059.md
new file mode 100644
index 0000000..a2b49bd
--- /dev/null
+++ b/docs/atlas-markers-beta059.md
@@ -0,0 +1,85 @@
+# beta.059 — bannières et cartes au trésor
+
+Ticket `codex/atlas-markers-beta059`. Prolonge les altitudes et l'affichage
+sans cadre de beta.058.
+
+## Contrat avant implémentation
+
+- Un clic droit sur une bannière, main vide ou avec une carte remplie, ajoute
+ son repère personnel à l'atlas. Le même geste le retire ; la couleur et le
+ nom renommé sont repris. Une carte native tenue continue aussi son propre
+ enregistrement vanilla. Aucun achat supplémentaire : l'observation peut
+ précéder l'aptitude Grande carte, qui reste nécessaire pour ouvrir l'atlas.
+- Utiliser une carte au trésor remplie importe ses repères dans la World Map.
+ La carte physique reste disponible pour être partagée. Les cartes natives
+ d'exploration et leurs bannières sont prises en charge ; leur terrain n'est
+ pas copié dans le brouillard personnel.
+- Les trésors des îlots miniers sont lus dans le plan aérien déjà chargé et
+ sauvegardé, sans nouvelle recherche ni génération. Les opérateurs les voient
+ automatiquement. Un client normal ne reçoit jamais les coordonnées des
+ trésors qu'il n'a pas importés depuis une carte.
+- Les deux navires fournissent des cartes vers les premiers îlots miniers.
+ Les coffres miniers peuvent donner la carte de l'îlot suivant, notamment
+ le troisième des mondes Grand. Ce sont des cartes Minecraft remplies avec
+ le marqueur de trésor natif, sans nouvel objet ni texture.
+- Les repères utilisent les sprites de carte Minecraft. Les bannières nommées
+ ont leur texte ; le survol donne le nom et les coordonnées. Un point dont
+ l'altitude est connue disparaît sous un plafond inférieur à ce point.
+ Les cartes natives sans altitude restent visibles sur tous les plafonds.
+
+## Données et mondes existants
+
+Fichier personnel additif `data/sanctuary-atlas/markers/.json`, schéma 1,
+lié à l'UUID et à la graine. Les journaux de terrain, couches, monde et plans
+restent inchangés. Limite de 512 repères ; écritures atomiques et conservation
+d'un fichier invalide. Reconnexion et prestige ne suppriment pas les repères.
+Un retour à beta.058 ignore ce fichier sans conversion.
+
+Les trois tables de butin aériennes conservent leurs objets et les quantités
+qu'elles déclarent, et ajoutent une carte. Leurs anciens champs `functions`
+/ `function`, ignorés dans cette version, sont adaptés en `modifier` / `type`
+pour appliquer effectivement ces quantités et la création de la carte.
+Seuls les coffres dont le butin n'a pas encore été généré utilisent ces tables ;
+aucun coffre déjà ouvert n'est rempli, aucun contenu existant n'est réécrit.
+Les coffres et positions sont ceux des plans d'origine, sans migration de
+structures, de chunks ou de graine. Les tests ne touchent que des mondes de
+laboratoire. Aucune publication ou installation personnelle implicite.
+
+## Vérifications
+
+- `atlas059Smoke` : persistance Unicode, isolation UUID/graine, limite de
+ 512 repères, suppression, absence de réécriture sans changement, filtrage
+ du cadrage et de l'altitude, conservation des fichiers corrompus.
+- Parcours client Minecraft 26.3-pre-2 sur un monde Sanctuary de laboratoire,
+ graine 42, taille moyenne, structures activées : clic droit réel sur une
+ bannière renommée, retrait et ajout, changement de nom, import depuis une
+ carte en main gauche, lecture des trois tables de butin natives, quantités
+ conservées, cartes dirigées vers les deux coffres du plan, utilisation
+ physique de la carte au trésor, conservation de l'objet et absence de doublon.
+- Le client normal reçoit uniquement le trésor découvert. Le mode opérateur
+ montre les deux sans les enregistrer ; sa désactivation et un paquet tardif
+ ne rétablissent pas les coordonnées cachées. Suppression d'une bannière
+ chargée, relecture du journal, codec avec altitude inconnue et libellés FR/EN
+ vérifiés. Sprites natifs contrôlés et captures inspectées.
+- `:sanctuary:atlas059Smoke :sanctuary:runClientGameTest` réussi en 2 min 57 s,
+ avec `-PsanctuaryClientTests=true -PsanctuaryAtlas059ClientTests=true
+ -PsanctuaryQuickTests=true`. Journal : `build/atlas059-client.log`, marqueur
+ `ATLAS059_PASS`.
+- `./gradlew check build assemblePack assembleTestPack` réussi en 2 min 9 s,
+ 122 tâches, avec les mêmes propriétés et `-x :sanctuary:runGameTest`.
+ Journal : `build/atlas059-check.log`. Aucun serveur dédié ni acceptation
+ d'EULA ; les tests natifs utilisent le serveur intégré de développement.
+- Les deux exports packwiz sont vérifiés par `build/verify-atlas059.py` :
+ 1 607 classes conformes au build, changements limités aux classes prévues,
+ ressources existantes préservées hors libellés et trois tables documentées,
+ JEI inchangé et archives beta.058 intactes.
+
+Archives locales : [pack normal](../build/Sanctuary-beta.059.mrpack) et
+[monde plat rapide](../build/Sanctuary-Test-beta.059.mrpack).
+Reçu détaillé : `build/atlas059-artifact.json` ; captures dans
+`build/atlas059-evidence/`.
+
+Le parcours natif couvre les deux trésors du monde moyen. Le troisième îlot
+du monde Grand suit le même plan et la même règle de carte suivante, mais n'a
+pas fait l'objet d'un second monde de test. Aucun essai avec plusieurs clients
+humains simultanés ni mesure de FPS n'est revendiqué ici.
diff --git a/docs/backlog.md b/docs/backlog.md
index db4fd3d..b196a16 100644
--- a/docs/backlog.md
+++ b/docs/backlog.md
@@ -1,8 +1,527 @@
-# Backlog de démarrage
+# Backlog Sanctuary
-Ce fichier prépare les premiers tickets à publier sur le Git du projet. **Aucun identifiant ci-dessous n'est un numéro d'issue distante et aucun ticket n'est présumé publié.** Les identifiants `BOOT-*` et `WG-*` servent seulement à relier les travaux tant que les issues n'existent pas.
+## ANO-01 — Les doubles de Steve
-Le [document de vision](vision.md) conserve les idées à long terme. Ce backlog organise uniquement le socle et les premiers incréments de génération. Une fonctionnalité n'est considérée comme livrée que lorsque son résultat et sa validation figurent dans le dépôt ou dans son issue.
+**Conception du 15 septembre 2026 ; non implémentée.**
+[Comportements, lots proposés et critères](steve-anomalies.md).
+Branche de conception réservée `codex/steve-anomalies-design`.
+
+Herobrine harcèle les explorateurs des Backrooms, frappe, pose des blocs et
+enferme, puis disparaît lorsqu'il est vu. Le mineur fantôme montre l'arrière de
+sa tête de face comme de dos et creuse surtout les chunks les moins demeurés
+**dans l'Overworld et les Backrooms**. Le Steve bugué, extrêmement rare, reste
+en T-pose avec des UV mal placés ; une fois découvert, il persiste et peut être
+transporté en bateau. Les lots distinguent leurs dépendances : BR-01 pour
+Herobrine, Demeure pour le mineur, persistance et transport pour le Steve bugué.
+Les lieux d'apparition de ce dernier et les réglages de gameplay restent à
+fixer. Aucune génération ni entité activée par cette préparation documentaire.
+
+## BR-01 — Backrooms après le suivi du ballast
+
+**Ticket préparé le 15 septembre 2026 ; non implémenté.** Dépend de la livraison
+et de la validation du **suivi du ballast**, dont le ticket amont reste à
+référencer. Les relevés d'activité actuels ne satisfont pas ce prérequis.
+[Ticket d'implémentation et critères d'acceptation](backrooms-implementation.md).
+Branche documentaire `codex/backrooms-ticket` ; branche d'implémentation prévue
+`codex/backrooms-generation`, sans numéro de livraison réservé.
+
+Résultat attendu : sommeil vers une salle personnelle dans les Backrooms
+communes, passage derrière un tableau, réseau procédural de couloirs et salles
+avec poolrooms, dreamcore et matière issue du ballast. Les autres joueurs
+peuvent découvrir la salle à pied ; tout lit des Backrooms ramène chacun à son
+lit d'origine dans l'Overworld. Orientation manuelle sans coordonnées, secteurs
+persistants, visites et retours à deux joueurs vérifiables. La préparation
+documentaire n'active aucune génération et ne change aucun binaire.
+
+## FAM-DESIGN-03 — les 88 familiers comme partenaires de combat
+
+**Proposition documentaire du 15 septembre 2026**, branche
+`codex/familiar-combat-design`. Le [catalogue de combat et de vie commune](familiar-combat-design.md)
+définit pour chacune des 88 espèces une initiative autonome, une signature
+commandée, une variante à apprendre et une utilité quotidienne. Règles proposées
+pour les ordres, le K.-O., les soins, les combinaisons et l'attachement ; premier
+prototype prévu sur loup, Slime et tortue. Présence et raretés des 88 identifiants
+vérifiées contre le registre. Aucun changement de gameplay ou de binaire livré ;
+chiffres d'équilibrage et contrat de migration à établir avant implémentation.
+
+## beta.053 — brume progressive, saturation et accueil
+
+Branche `codex/progressive-fog-beta053`. Brume atmosphérique dès le point de vue,
+saturation 0–200 % dans Options → Shaders et œufs de Hello World alignés à gauche.
+[Contrat et vérifications](progressive-fog-beta053.md). Build, parcours client
+natif FR/EN et deux MRpacks vérifiés localement. Aucun changement serveur ;
+compatibilité avec des moteurs de shaders externes non revendiquée.
+
+## beta.052 — nom du familier de départ
+
+Branche `codex/starter-name-beta052`. Nom facultatif dans Hello World,
+enregistrement serveur avec le choix et composant de nom natif sur l'œuf.
+[Contrat et migration](starter-name-beta052.md). Build, 8 tests serveur,
+parcours client FR/EN en deux tailles et reconnexion vérifiés ; packs locaux.
+Connexion à plusieurs clients distants restant à confirmer.
+
+## beta.051 — visée des projectiles du distributeur porté
+
+Branche `codex/head-dispenser-aim-beta051`. Visée horizontale et verticale au
+moment du tir, origine suivant la tête et immunité initiale du porteur.
+[Contrat et vérifications](head-dispenser-beta051.md).
+Build, 34 tests serveur et deux MRpacks vérifiés localement. Treize munitions
+testées dans huit directions ; session multijoueur distante restant à confirmer.
+
+## beta.050 — bateaux compacts et redstone mobile
+
+Branche `codex/head-blocks-beta050`. Formats 1 × 2 et 2 × 1, livres enchantés,
+étagères et pupitres visibles, lits complets décoratifs, pots et commandes sur
+la tête. Sources redstone mobiles pour les circuits réels adjacents.
+[Contrat et vérifications](head-blocks-beta050.md). Build, 40 tests serveur,
+parcours client FR/EN et deux MRpacks vérifiés localement. Session avec
+plusieurs clients distants restant à confirmer en partie.
+
+## beta.049 — rectangles et retour des blocs portés
+
+Branche `codex/rectangular-boats-beta049`. Quatre formats dans onze bois,
+recettes selon leur disposition, deux passagers par module (principaux d’abord),
+sons pour tous les participants, aliments et
+ouvertures visibles, façades corrigées et signal à l'impact.
+[Contrat](boats-head-beta049.md). Build, 35 tests serveur, parcours client FR/EN
+et deux MRpacks vérifiés localement. Session avec deux clients distants restant
+à confirmer en partie.
+
+## beta.048 — aéronautique de poule
+
+Branche `codex/poultry-aeronautics-beta048`. Bateaux collectifs dans onze bois,
+sept moteurs animaux, commandes partagées, poids et collisions par pile.
+Modèles composés de fonds natifs répétés, rebords extérieurs et deux rames.
+Happy Ghast à vitesse doublée. [Contrat](poultry-aeronautics-beta048.md).
+Livré localement : build, 33 tests serveur, parcours client et exports vérifiés.
+Équilibrage à plusieurs clients distants restant à éprouver en partie.
+
+## beta.047 — lecture des étoiles et survol
+
+Branche codex/spyglass-star-info-beta047. Noms, origines et constellations
+à la longue-vue ; retrait du trait avec Maj + clic droit, boutons jaunes au
+survol. [Contrat](spyglass-stars-beta047.md). Build, 12 tests serveur, parcours
+client FR/EN et deux MRpacks vérifiés localement.
+
+## beta.046 — factions, ciel, vol et zoom
+
+Branche codex/sky-flight-zoom-beta046. Deux places à la fondation,
+constellations à la souris, bateau à poule, aptitude de zoom et identité native.
+[Contrats](sky-flight-zoom-beta046.md) · [Factions](faction-two-slots-beta046.md).
+Livré localement : build, 20 tests serveur, parcours client natif FR/EN et
+deux MRpacks vérifiés. Les dix icônes d’aptitudes sont intégrées au mod et aux
+sources du resource pack.
+
+## beta.045 — menu principal
+
+Branche `codex/title-menu-beta045`. [Ticket](title-menu-beta045.md).
+Version affichée en haut à gauche ; Friends / Amis en bouton normal sous
+Multiplayer ; retrait de Realms et des icônes compactes. Parcours client FR/EN
+vérifié, avec l'ordre clavier et les routes natives Amis et Options.
+Livré localement : build, quatre tests serveur et deux MRpacks vérifiés.
+
+
+### beta.044 — audit des familiers et icône finale
+
+Livraison locale : attaques natives, bloc de Slime pendant cinq secondes,
+corrections des transferts et du Vex, commande d'heure en LAN réparée. [Ticket](spawn-eggs-audit-beta044.md).
+Les rangs I–III, Anomaly et l'équilibrage multijoueur restent à traiter séparément.
+
+
+## beta.043 — blocs portés et icône officielle
+
+Branche `codex/functional-head-blocks-beta043`.
+[Ticket](functional-head-blocks-beta043.md). Build et 41 tests serveur réussis ;
+deux exports vérifiés localement. Les contraintes du monde réel restent requises
+pour les fondations, les circuits, portails et actions physiques.
+
+## beta.042 — portage et diagnostic LAN
+
+Branche `codex/lan-carry-beta042`. [Ticket](lan-carry-beta042.md).
+Ancrage du bassin sur la tête, piles et tailles ; parcours graphique natif et
+39 tests serveur réussis. Les commandes de l'hôte fonctionnent dans le scénario
+LAN reproduit : diagnostic ouvert en attente de la commande et de l'erreur précises.
+
+## beta.041 — bébés portés
+
+Branche `codex/carry-babies-beta041`. [Contrat](carry-babies-beta041.md) :
+pas de ralentissement ajouté par les bébés natifs ou les familiers en bébé,
+y compris au sommet d'une pile de joueurs ; recalcul lorsque l'animal grandit.
+Livré localement : build, 43 tests serveur et deux MRpacks vérifiés.
+Les ressources et exports beta.040 sont conservés.
+
+## beta.040 — opérateur, habitant et étoiles
+
+Branche `codex/operator-inhabitant-beta040`. [Contrat](operator-inhabitant-stars-beta040.md) :
+Allow Commands comme autorité, aperçu du familier et cadrage du personnage,
+ciel persistant alimenté par les événements serveur, [Vanilla Light](shaders-beta040.md),
+[correctif du Hello World multijoueur](multiplayer-hello-beta040.md) et
+[reprise des grands catalogues](recipe-storage-beta040.md). Livré localement :
+50 tests serveur, parcours client à froid et réouverture du monde réussis ;
+deux MRpacks vérifiés. Connexion réseau dédiée non testée, EULA non acceptée.
+
+## beta.039 — accueil et inventaires
+
+Branche `codex/food-knowledge-beta039`. [Hello World et familier de départ](hello-starter-beta039.md),
+[savoir alimentaire et panoramas](food-panorama-beta039.md),
+[inventaire de mort à gauche, inventaire actuel normal à droite](grave-inventory-beta039.md).
+Tirage permanent de trois choix, reprise de création, remise unique de l’œuf ;
+textures existantes conservées. Build, 68 tests serveur et parcours client natif
+réussis ; contrats et limites dans les trois tickets.
+
+## INTRO-01 — apparition de l'habitant, beta.031
+
+Branche `codex/introduction-beta031`. UUID serveur en SGA sur fond noir,
+glyphes spectraux, spirale vers un portail grandissant et flash blanc sur le spawn.
+Contrat, commande de relecture et vérifications dans [le ticket](introduction-beta031.md).
+
+
+Ce fichier prépare les tickets à publier sur le Git du projet. **Aucun identifiant ci-dessous n'est un numéro d'issue distante et aucun ticket n'est présumé publié.** Les identifiants locaux servent seulement à relier les travaux tant que les issues n'existent pas.
+
+Le [document de vision](vision.md) conserve les idées à long terme. Ce backlog organise les incréments jouables et conserve l’historique de génération. Une fonctionnalité n'est considérée comme livrée que lorsque son résultat et sa validation figurent dans le dépôt ou dans son issue.
+
+## Prochaine séance — priorités du 14 septembre 2026
+
+**GRAVE/FOOD/FACTION-038 — [têtes-tombes, nourriture et fondation](graves-food-beta038.md) :**
+branche `codex/graves-beta038`, récupération publique et portable du butin de mort,
+grille grisée en retrait seul, mémoire du décès, icônes de faim/saturation
+adaptées à la progression et textures remplaçables. Fondation sans prestige
+pour dix niveaux, une place initiale, extensions par charges. Livré en beta.038 : 55 tests
+serveur, parcours client natif, 14 784 comparaisons alimentaires et deux MRpacks
+vérifiés. Les anciennes factions et livraisons restent conservées.
+
+**TIME-037 — [temps réel et suivi commun](realtime-beta037.md) :**
+soleil saisonnier à l’heure de la machine en solo et configurable sur serveur,
+retour vanilla explicite, contrat de sauvegarde additif, sommeil sans saut de nuit.
+Moteur commun d’objectifs avec adaptateur advancement ; les quêtes et leurs
+récompenses restent un ticket distinct. Livré en beta.037 : 29 tests serveur,
+deux parcours client, 7 765 assertions dédiées et deux MRpacks vérifiés.
+
+**HUD-036 — [notifications choisies et corrections](corrections-beta036.md) :**
+placement en haut à droite et évitement F3, expiration, clic pour suivre les
+advancements, plané avec animal porté et discrétion au sol. Inclut le correctif
+des sorties de recettes masquées par JEI après déblocage des collections.
+Livré en beta.036 : 36 tests serveur, deux parcours client et deux exports vérifiés.
+
+**QUESTS — suivant, non implémenté :** suivi en haut à gauche après l’ajout des
+rubis/saphirs et des blocs nécessaires ; contrat des objectifs côté serveur.
+
+**REALTIME — à cadrer avant implémentation :** horloge/fuseau système en solo,
+fuseau serveur en multi, profil géographique approximatif pour les levers et
+couchers saisonniers. Aucun domicile précis collecté ou publié. Le fuseau ne
+détermine pas à lui seul une latitude ; définir le choix ou profil de secours
+solaire en solo, changements d’heure et migration du temps des sauvegardes.
+
+**RECIPES-035 — [collections Minecraft](collections-beta035.md) :** intégration
+des 67 familles documentées, 2 042 recettes et 126 advancements. Connaissances
+conservées, noms FR/EN et notifications de collections ; fiches d’obtention et
+musée visuel exclus de ce ticket.
+Livré en beta.035 : 28 tests serveur, parcours client natif et deux exports vérifiés.
+
+**HUD-034 — [notifications et hotbar](notifications-beta034.md) :** fil de
+premières découvertes, options natives, barres de collections et suivi des
+advancements. Retrait des boutons Factions/History du menu pause et hotbar
+personnelle agrandie. Livré en beta.034 : build, 22 tests serveur, parcours client
+natif et deux MRpacks vérifiés. Quêtes, bounties et panneaux exclus de cette livraison.
+
+
+**UI-033 / DEV-033 — beta.033 :** [menus directs et Discovery](menus-discovery-beta033.md),
+flamme du gâteau corrigée et [module de test sur monde plat](test-rapide-beta033.md).
+Le profil rapide conserve la fiche d’habitant et les systèmes réels ; le pack
+normal garde sa génération et sa cinématique.
+
+
+**FAM-CORE-01 à FAM-CATALOG-06 — [beta.032](spawn-eggs-v2-beta032.md) :**
+les 88 œufs de la [refonte v2](spawn-eggs-refonte-v2.md) sont implémentés, avec
+[catalogue chiffré](spawn-eggs-v2-catalogue-beta032.md), budgets partagés persistants,
+zones coopératives, outils et stocks réels. Build, 85 tests serveur, parcours
+client natif et export local vérifiés. Essais d’équilibre solo/2–4 joueurs,
+acquisition FAM-ACQUIRE-07, rangs et Anomaly restent à poursuivre.
+
+**LIGHT-01 — [beta.030](dynamic-lights-beta030.md)** ajoute l’éclairage porté,
+les cosmétiques lumineux et les objets jetés regroupés par cellules.
+Build, 21 tests serveur, parcours client natif et MRpack local vérifiés.
+Les limites du rendu figurent dans le ticket.
+
+[beta.029](familiar-hit-feedback-beta029.md) ajoute le cri et le recul natifs
+aux coups sur les familiers. Build, 38 tests serveur et MRpack local vérifiés ;
+écoute et essai visuel à confirmer.
+
+[beta.028](dive-until-collision-beta028.md) maintient le plongeon jusqu’à
+collision, avec une seule impulsion et la chute native. Build, 32 tests serveur
+et MRpack local vérifiés ; essai visuel à confirmer.
+
+[beta.027](player-stacks-beta027.md) autorise les piles de joueurs, avec
+ralentissement progressif selon le nombre total de passagers au-dessus.
+Build, 27 tests serveur et MRpack local vérifiés ; essai visuel à confirmer.
+
+[beta.026](demeure-map-performance-beta026.md) corrige le coût du rendu Demeure
+sur la carte : couche de texture en cache et mises à jour seulement sur changement.
+Les règles Demeure et les sauvegardes restent identiques. Build, 9 tests serveur
+et MRpack local vérifiés ; mesure graphique en attente d’une session déverrouillée.
+
+[beta.025](skill-xp-bars-beta025.md) remplace les jauges ASCII des compétences
+par des barres d’XP natives, avec le prix au centre. Le palier d’inventaire
+supplémentaire reste à discuter ; plafond maintenu à six rangées.
+24 tests serveur, parcours graphique et MRpack local vérifiés.
+
+[beta.023](head-cosmetics-beta021.md) finalise les cosmétiques de tête, les
+infobulles des huit cases d’équipement et la TNT amorcée par un coup. Le grand
+bouton est retiré : interactions directement sur la case avec l’objet au curseur.
+Le correctif de chargement beta.022 est intégré et revérifié. Pack local et
+parcours serveur/client validés.
+
+[beta.024](recipes-jei-beta024.md) intègre JEI depuis les sources 26.2 : catalogue
+à 4 niveaux, recettes des advancements et collections du bois/de l’alchimie.
+La fabrication manuelle reste libre et la vue opérateur est explicite. Transfert
+sur six rangées, prestige, mort et redémarrage vérifiés. Les futures collections
+de recettes spéciales restent à définir.
+
+[beta.020](familiar-interactions-beta020.md) ajoute le portage, les bébés familiers,
+la taille propre à l’œuf, le nom persistant, les infobulles cryptiques et leur
+aptitude de lecture. Le réglage d’aide rejoint Habitant. Le plongeon, la perception
+à travers les parois, les téléportations et le freinage de chute sont vérifiés.
+Export local et parcours serveur/client validés. Suite : équilibrage des pouvoirs
+en partie réelle, puis rangs et œufs Anomaly, toujours séparés de la taille visuelle.
+
+[beta.019](companion-powers-beta019.md) implémente les 88 passifs et actifs, le lien,
+les recharges, le rendu corrigé et les locomotions de familiers. Le fondu de
+proximité s’applique aux volants. Export local et contrôles serveur/client validés.
+Suite dédiée : essais d’équilibrage par espèce, puis cadrage des rangs I–III et
+des œufs Anomaly ; acquisition en survie à concevoir.
+
+[beta.018](companions-cosmetics-beta018.md) ajoute les cases familier/cape/tête,
+les nouvelles icônes et le familier mobile que l’on peut caresser. Les pouvoirs
+de compagnons sont désormais implémentés dans beta.019.
+S’allonger devient gratuit ; seul Se reposer reste achetable pour ce geste,
+avec un rendu de sommeil distinct. L’acquisition des œufs en survie et le visuel
+définitif de Cape Zéro restent à définir.
+Ce lot ajoute aussi le transfert continu par Maj + clic gauche maintenus et
+la commutation de rangée par Tab / Maj + Tab pendant le jeu, sans ouvrir l’inventaire.
+Minage et construction fixent aussi leur groupe pendant le maintien, avec regard
+libre et orientation initiale des poses conservée. Export local beta.018 vérifié,
+67 tests serveur et parcours client natif réussis.
+
+[beta.017](swimming-recipebook-beta017.md) ajoute Nage, S’allonger et Se reposer
+et retire le livre de recettes natif. Prix des rangs et sons d'XP des Premiers pas
+inclus ; tests serveur/client et export local validés. JEI suit séparément.
+
+[beta.016](tutorial-xp-beta016.md) ajoute les récompenses uniques des cinq gestes
+du tutoriel natif ; 57 tests serveur, client natif et export local vérifiés.
+
+[beta.015](progression-textures-beta015.md) remplace les six icônes de progression
+et prépare les mêmes PNG dans les sources du resource pack Sanctuary.
+
+La carte immersive est implémentée dans **beta.007**, puis Minage et le vein
+mining dans **beta.008** ([contrat et vérifications](mining-beta008.md)).
+Construction et le vein building suivent dans **beta.009**
+([contrat et vérifications](building-beta009.md)).
+Les [prochains tickets détaillés](prochains-tickets.md) poursuivent avec
+la validation complète de la progression après [Inventaire beta.010](inventory-beta010.md).
+**Le New Game+ exige les six compétences au maximum ; toutes les aptitudes ne
+sont pas requises.** Les aptitudes acquises sont conservées.
+
+| Ordre | Ticket préparé | Résultat attendu |
+| --- | --- | --- |
+| Cadrage réalisé | CYCLE-01 | [Contrat beta.011](cycle-factions-beta011.md), conservation et charges |
+| 1, réalisé | MAP-03 | [Carte immersive beta.007](atlas-immersive-beta007.md) |
+| 2, réalisé | PROG-02A | [Minage et vein mining beta.008](mining-beta008.md), aperçu et rangs |
+| 3, réalisé | PROG-02B | [Construction et vein building beta.009](building-beta009.md), surfaces et consommation réelle |
+| 4, réalisé | PROG-02C | [Inventaire beta.010](inventory-beta010.md), jusqu’à six rangées, objets conservés |
+| 5, réalisé en beta.011 | PROG-03 | Six compétences complètes, parcours d'XP et condition de fin vérifiée |
+| 6, réalisé en beta.011 | FACTION-01 | Factions persistantes et capacités liées au prestige |
+| 7, réalisé en beta.011 | NGP-01 | Recommencement complet, aptitudes conservées et effets de faction |
+
+Les branches prévues, critères d'acceptation, dépendances et questions restantes
+sont dans [le cahier des prochains tickets](prochains-tickets.md). Claims et
+protections suivent leur propre contrat après ce jalon ; la grille et Demeure
+ne réservent pas encore de terrain. Ce découpage est préparé localement et
+complète les priorités historiques ci-dessous.
+
+Le correctif [beta.012](inventory-flow-beta012.md) traite l'alignement des coffres,
+les transferts de toutes les rangées, la rotation des barres et le remboursement
+opérateur des compétences. Les claims restent un ticket suivant.
+
+[beta.013](progression-inline-beta013.md) met chaque compétence sur une ligne,
+abaisse le dernier achat à 48 niveaux et explique dans Factions le chemin
+six compétences → New Game+ → charge de fondation.
+Le même retour d'essai ajoute le prestige romain sur la fiche, le grisage complet
+des cases de récupération, la carte sans effacement cyclique et les indicateurs
+natifs de joueurs superposés à l’XP, puis la molette selon la zone survolée.
+
+[beta.014](inventory-sorting-beta014.md) ajoute l'aptitude Rangement : clic molette
+configurable, stockage et hotbar triés séparément selon la case visée, avec
+conservation au New Game+ et autorité du serveur sur tous les objets. Le même
+ticket corrige la hotbar : le cadre se déplace, les objets restent dans leurs
+cases visibles. Il ajoute le préfixe de faction aux noms et à la fiche Habitant,
+et retire le rappel du New Game+ des descriptions individuelles d’aptitudes.
+
+Le lot [beta.011](cycle-factions-beta011.md) relie les six compétences, le
+New Game+, le journal des charges et les factions. Les montants initiaux et
+la matrice de conservation y sont définis. Les claims restent le ticket suivant.
+
+## Cap bêta — 12 septembre 2026
+
+Le créateur considère le **worldgen terminé sur l’alpha.30.7**. Les tickets
+alpha ci-dessous conservent leurs observations et leurs limites historiques ;
+cette décision n’ajoute aucune preuve de test. Priorité suivante : une passe
+courte sur les **expansions**, puis la **progression en jeu**.
+
+### BETA-01 — Adopter beta.xxx
+
+**Branche :** `codex/versioning-beta`.
+
+**Résultat :** partir de `beta.001`, conserver un compteur commun au
+mod et au pack et utiliser la version exacte pour les futurs tags. Contrôler
+le format et l’alignement des métadonnées à la construction. Préserver le code
+de jeu et l’historique des versions alpha. Voir [la convention](versioning.md)
+pour le périmètre et les vérifications de cette préparation locale.
+
+### EXP-01 — Les quatre anciennes expéditions — beta.001
+
+**Branche :** `codex/ancient-expeditions-beta001`, avec la préparation BETA-01.
+
+**Résultat implémenté, export local d’essai :** quatre régions de 512 blocs déjà ouvertes dans les nouveaux
+mondes : nord glacial/Peaks, est tropical/Volcan, sud aride/Canyon, ouest humide/Océan.
+Rapprocher leur placement sur les axes cardinaux, conserver l’île initiale et
+les bateaux, puis vérifier la génération native et le rechargement. Les noms
+évoquent Noor, Makena, Kai et Efe, Ari. Le [contrat bêta](expeditions-beta001.md)
+décrit la création atomique, l’isolation des anciennes sauvegardes et les essais.
+Contrôles purs et 3 GameTests ciblés réussis, puis 3/3 au rechargement sur
+Moyen/graine `0`. MRpack `Sanctuary-beta.001.mrpack` vérifié ; le créateur confirme
+ensuite en jeu que les îles se génèrent correctement. Le rapprochement conserve les emprises de chaque île,
+avec les distances et les limites mesurées dans le contrat.
+Le [contrat d’expansion](expansion.md) reste la référence pour les demandes
+ultérieures et les contrôles de chunks existants.
+
+### EXP-02 — Boréal, corail et temples — beta.002
+
+**Branche :** `codex/expedition-biomes-temples-beta002`.
+
+**Résultat implémenté après essai de beta.001 :** nord boréal enrichi de glace,
+océan chaud avec corail à l’ouest, pyramide dans le canyon et temple dans le
+volcan tropical. Quatre îles de 512 blocs, sites variables selon la graine,
+structures natives complètes. Les nouveaux mondes prennent une révision
+séparée ; les sauvegardes beta.001 restent intactes.
+Voir [le contrat et les résultats](expeditions-beta002.md).
+Tests ciblés 4/4 sur les graines `0` et `42`, reprise 4/4 sur `0` ; temples,
+coffres, pièges et corail vivant vérifiés. MRpack beta.002 construit et vérifié,
+disponible localement pour le nouvel essai ; pas de publication ou déploiement.
+
+### PROG-01 — HELLO_WORLD, Blocodex et premier achat — livré localement en beta.003
+
+**Branche :** `codex/progression-beta003`.
+
+**Premier lot implémenté et testé :** création avant apparition,
+couleurs stables, Blocodex commun, 3/3/3, achats conservés après mort et aptitude
+Noms avec configuration serveur. S'asseoir est disponible dès le début.
+Compétences à gauche, aptitudes défilantes à droite ; boutons natifs de couleur
+avec glyphes SGA teintés. Tests de progression serveur 4/4 et parcours client
+natif réussis, export MRpack vérifié. La suite générale reste à 37/39 à cause
+des deux assertions worldgen historiques, détaillées dans les notes.
+Voir le [cahier](progression-beta003.md), le [contrat de données](progression-beta003-contract.md)
+et les [résultats de livraison](progression-beta003-release.md).
+
+L'audit Modrinth du 12 septembre ne trouve aucun fichier Fabric déclaré pour
+`26.3-pre-2` pour JEI, Xaero's Minimap ou Xaero's World Map. Le socle natif peut
+avancer indépendamment de ces intégrations. L'overlay de vie et les entrées
+de mobs du catalogue restent à développer. L'export beta.003 est local ;
+aucune publication ni synchronisation d'installation personnelle effectuée.
+
+### UI-01 — Cadre Blocodex, icônes et sprint — livré localement en beta.004
+
+**Branche :** `codex/blocodex-layout-beta004`.
+
+Titre et onglets Discovery, Progression, Story, Inhabitant en haut, bouton
+Done / Terminé en bas, corps adapté au GUI et textes défilants. Les six
+compétences portent les icônes 9×9 fournies par le créateur. Le sprint exige
+plus de trois icônes de nourriture, avec contrôle client et serveur.
+Profils beta.003 conservés, sans changement de génération ou de sauvegarde.
+Contrôles purs, serveur ciblé **5/5**, parcours client natif dans huit
+configurations GUI et export MRpack vérifié. Voir
+[le ticket et ses limites](blocodex-beta004.md) et
+[Sanctuary-beta.004.mrpack](../build/Sanctuary-beta.004.mrpack).
+
+### UI-02 — Blocodex joueur/opérateur et accès vanilla — conception validée
+
+Retour du créateur après beta.004 : retirer S'asseoir des aptitudes et du
+passeport, masquer les inconnus aux joueurs, permettre leur inspection aux
+opérateurs, réunir Statistiques et Advancements dans la navigation. La
+[note de conception](blocodex-navigation.md) propose quatre rubriques, les
+contrôles de permissions et les lots suivants (vitrine, favoris, carte, plans).
+Le créateur valide la maquette, la vue opérateur explicite, les découvertes
+filtrées et la vitrine de l'habitant ; il confirme **24 heures réelles** entre
+changements de favori. La référence est conservée dans les
+[archives pour le site](../archives/web/README.md).
+Le retrait de S’asseoir et l’entrée carte sont inclus dans MAP-01. Le filtrage
+du catalogue, les accès vanilla et la vitrine restent à livrer ; coefficients
+d’affinité et portée des bonus de production restent ouverts.
+
+### PROG-02A — Minage et vein mining — beta.008
+
+**Branche :** `codex/progression-vein-mining`.
+
+Sept rangs achetables ; vitesse de 100 à 200 %, sélection par faces de 4 à
+256 blocs après le premier achat. Aperçu, taille au choix, commandes natives
+configurables et destruction par le serveur. Achats, mort et reprise conservent
+la progression. Le [contrat beta.008](mining-beta008.md) précise les paliers
+d'essai, la migration et les vérifications. Construction suit en beta.009.
+
+### MAP-03 — Carte immersive — beta.007
+
+**Branche :** `codex/atlas-immersive`.
+
+Carte jusqu'aux bords de l'écran, centre clair, abords sombres et transparence
+de l'inconnu et du vide. Commandes et coordonnées superposées, priorité des
+boutons et navigation dans les marges. Données et génération conservées.
+Résultat, captures et validation : [carte immersive beta.007](atlas-immersive-beta007.md).
+
+### MAP-02 / DEM-01 — Brouillard et empreintes — beta.006 — livrée localement
+
+**Branche :** `codex/demeure-fog-beta006`.
+
+**Périmètre choisi :** grille de chunks et empreintes Demeure, sans réservation
+ni protection. Corriger les glissements répétés, conserver l’exploration,
+porter les premiers gestes confirmés et consulter l’influence depuis l’Atlas.
+8/8 tests serveur et parcours client natif réussis ; MRpack vérifié.
+Contrat, migration et vérifications : [Demeure beta.006](demeure-beta006.md).
+
+### MAP-01 — Carte native beta.005 — livrée localement
+
+Branche `codex/native-map-beta005`. Le créateur choisit une carte native vue du
+dessus inspirée des usages de Xaero. L’exploration commence avant le déblocage
+de l’aptitude Grande carte. Voir [le contrat beta.005](atlas-beta005.md).
+Ordre de cette livraison : carte native → Demeure → factions/claims → New Game Plus
+et prestiges. Le [cadrage du 13 septembre](prochains-tickets.md) intercale désormais
+la fin des six compétences avant le New Game+.
+[Le relevé Xaero](xaero-experimental.md) et [les pistes libres](cartographie-open-source.md)
+restent des références ; aucun mod cartographique communautaire ajouté au pack.
+Carte de surface, aptitude, relevés persistants, inspection opérateur et noms
+de couleurs livrés : tests serveur 7/7, parcours client et MRpack vérifiés.
+Mini-carte, marqueurs et partage restent séparés ; CONNECT-01 poursuit ensuite
+les systèmes sociaux.
+
+### ARCHIVE-01 — Références réutilisables pour le site — conservées localement
+
+Branche `codex/web-archive-xaero-roadmap`. Maquette Blocodex exportée en HTML
+autonome sans dépendance réseau, source conservée ; ZIP léger et collection
+de 782 fichiers avec fonds alpha jusqu'à 30.7, trois atlas originaux,
+documents et aperçus bêta. Catalogue relatif et empreintes SHA-256 pour
+l'ingestion future. Copie, manifestes atlas, CRC ZIP et membres vérifiés.
+Voir [les archives](../archives/web/README.md). Aucun déploiement du site,
+aucune sauvegarde de jeu modifiée et aucune nouvelle version binaire.
+
+### PROG-02 — Minage, Construction et Inventaire — découpé pour la prochaine séance
+
+Les six compétences ont leur place dans le panneau depuis beta.003. Le report
+initial des mécaniques devient trois tickets : **PROG-02A** pour le vein mining,
+**PROG-02B** pour le vein building, puis **PROG-02C** pour l'inventaire.
+Leurs résultats, références 26.2, contrôles serveur et contrats de sauvegarde
+sont détaillés dans [les prochains tickets](prochains-tickets.md).
+**PROG-03** vérifiera ensuite le parcours complet ; **NGP-01** exige les six
+compétences au maximum et s'articule avec **FACTION-01** via **CYCLE-01**.
+
+## Historique alpha
## Premier jalon : un monde Sanctuary jouable
diff --git a/docs/backrooms-implementation.md b/docs/backrooms-implementation.md
new file mode 100644
index 0000000..3414d03
--- /dev/null
+++ b/docs/backrooms-implementation.md
@@ -0,0 +1,378 @@
+# BR-01 — Explorer les Backrooms alimentées par le ballast
+
+**Ticket local préparé le 15 septembre 2026. Statut : à implémenter après la
+livraison et la validation du suivi du ballast.** Cette préparation est
+documentaire ; elle n'active aucune dimension ni aucun transfert en jeu.
+
+- Branche documentaire : `codex/backrooms-ticket`.
+- Branche prévue pour l'implémentation : `codex/backrooms-generation`.
+- Livraison `beta.xxx` : numéro à attribuer au moment de la livraison du code.
+- Cible relevée dans `gradle.properties` : Minecraft **26.3-pre-2**, Java 25.
+ Vérifier les API et dépendances de la version effectivement ciblée au démarrage.
+- Prérequis bloquant : **suivi du ballast livré**, avec contrat de données et
+ preuves de reprise. Référence du ticket amont à ajouter lorsqu'il sera créé ;
+ aucun ticket dédié à ce suivi n'est identifié dans le backlog à cette date.
+
+## 1. Résultat attendu
+
+Un joueur termine son sommeil dans son lit de l'Overworld et rejoint sa salle
+personnelle, un Indoor de protection situé dans les Backrooms communes. Un
+tableau cache une porte ouverte donnant sur le réseau. Le joueur explore des
+couloirs, salles, bassins et infrastructures, peut rencontrer un autre joueur
+ou découvrir sa salle, puis dort dans un lit trouvé sur place pour revenir à
+son propre lit d'origine dans l'Overworld.
+
+Les nouvelles régions portent l'empreinte du ballast réellement suivi dans
+Sanctuary. Les régions déjà planifiées conservent leur génération ; les lieux
+visités, les constructions et les cartes dessinées par les joueurs restent
+utiles après changement de période, reconnexion et redémarrage.
+
+## 2. Décisions du créateur à respecter
+
+Ces règles proviennent de la discussion de conception et priment sur les
+anciennes intentions d'accès encore ouvertes dans la vision et la cosmologie.
+
+- Les Backrooms sont le dysfonctionnement d'Indoors interconnectés. Le ballast
+ du monde y est redirigé et alimente leur croissance procédurale.
+- Steve a aménagé un refuge de protection associé au renoncement à poursuivre
+ ce chemin. Il a caché le passage avec **un tableau devant une porte ouverte**.
+ Ce geste simple ne fait pas de Steve l'ingénieur de toute l'infrastructure.
+- Chaque joueur dispose d'une salle stable, liée à son identité et intégrée
+ physiquement à la génération des Backrooms. Le lit est son accès personnel.
+- Un autre joueur peut découvrir cette salle à pied et y entrer. Son propre lit
+ ne lui permet pas de choisir la salle d'autrui comme destination.
+- Le sommeil complet déclenche le passage ; il ne fait pas passer la nuit.
+- Dans les Backrooms, **n'importe quel lit utilisable permet de revenir à son
+ propre lit d'origine dans l'Overworld**. Son propriétaire ou sa proximité
+ d'une salle personnelle ne change pas la destination.
+- Les Backrooms ne présentent pas de coordonnées aux joueurs. Chacun construit
+ sa connaissance des trajets et sa cartographie.
+- Couloirs et salles doivent former des lieux explorables avec des ambiances
+ dreamcore, poolrooms et des matériaux liés à l'activité des joueurs, dont
+ la cobblestone (« cobble »).
+
+Les modalités des sections suivantes sont des **choix proposés pour réaliser
+ce premier lot**. Elles ne sont pas des fonctionnalités déjà livrées.
+
+## 3. Condition de démarrage : le ballast doit être exploitable
+
+Le suivi amont doit fournir et documenter :
+
+1. Les opérations qui produisent du ballast, leurs unités, matériaux, quantités,
+ dates/périodes et périmètres d'origine. Distinguer événement terminé, tentative
+ refusée et données absentes. Un zéro enregistré n'est pas une panne de collecte.
+2. Une identité stable des événements ou lots et une reprise sans double compte.
+ Une relecture après arrêt ne produit pas un nouvel apport de matière.
+3. Des instantanés immuables et versionnés, identifiables par période et révision,
+ dont la lecture ne modifie pas le registre économique. Leur archivage doit
+ permettre de comprendre l'origine d'un secteur après évolution du suivi.
+4. La nature du ballast : matière comptable, empreinte d'une opération, ou les
+ deux. Fixer les règles de réservation, de consommation et de récupération
+ lorsque des blocs générés représentent une quantité économique extractible.
+5. Le traitement des actions réalisées dans les Backrooms. Générer un bloc,
+ rejouer un événement ou recycler un matériau ne doit pas alimenter une boucle
+ de création non prévue par le contrat amont.
+6. Les contrôles de sauvegarde, de restauration et de conservation des données,
+ ainsi qu'au moins un parcours réel allant d'une opération au lot enregistré.
+
+Le socle [d'activité datée](blocodex.md#relevés-datés--portée-de-lalpha23)
+observe déjà minage, pose, fabrication, ramassage et jet volontaire. Ces agrégats
+ne prouvent ni consommation ni perte ; ils ne distinguent actuellement ni
+joueur ni dimension. **Ils ne remplacent pas le prérequis ballast.**
+
+BR-01 consomme l'interface du suivi livré. Il ne crée pas les producteurs
+économiques futurs et ne reconstitue pas fictivement leurs transactions.
+Les données synthétiques servent aux tests, sans devenir l'historique du serveur.
+
+## 4. Sommeil, salle personnelle et retour
+
+### Parcours nominal
+
+| Situation | Comportement attendu |
+| --- | --- |
+| Sommeil complet dans le lit personnel validé de l'Overworld | Le serveur prépare la destination, mémorise le retour, puis transfère le joueur dans sa salle. |
+| Premier voyage | Une seule salle est réservée pour l'identité du joueur ; son arrivée et ses raccords sont prêts avant le transfert. |
+| Voyages suivants, y compris après déplacement du lit personnel | Le joueur retrouve la même salle. Le retour est ancré au lit d'origine de ce nouveau voyage. |
+| Réveil volontaire avant la fin du sommeil | Aucun transfert, aucune nouvelle salle créée par le simple début de la pose. |
+| Sommeil dans un lit des Backrooms | Retour au lit d'origine enregistré pour ce joueur ; aucune modification de l'ancrage vers le lit utilisé sur place. |
+| Deux joueurs utilisent successivement le même lit des Backrooms | Chacun revient dans son Overworld à son propre point de retour. |
+| Découverte de la salle d'autrui à pied | Entrée possible ; la salle conserve son identité et son lien au propriétaire. |
+
+Proposition d'interaction : une durée de sommeil individuelle de **100 ticks**
+(environ cinq secondes à 20 ticks/s), à éprouver en jeu. Elle ne dépend pas du
+nombre de joueurs couchés. Permettre le parcours de jour comme de nuit dans les
+mondes Sanctuary où la fonction est activée, sans saut d'heure ni de météo.
+Les lits des Backrooms doivent fonctionner sans explosion.
+
+Réutiliser la règle du lit personnel unique et vérifier son implémentation au
+démarrage : un point de réapparition ne prouve pas à lui seul la propriété d'un
+lit. Si cette association manque, la liaison minimale joueur/lit fait partie
+du parcours BR-01 ; la politique de remplacement doit préserver les lits déjà
+posés. Les lits portés comme accessoires décoratifs ne sont pas des accès.
+
+### Conservation et incidents
+
+- Le lien à la salle repose sur l'identité persistante, jamais sur le pseudo,
+ le nom d'un lit, un prestige ou les coordonnées du lit extérieur.
+- Enregistrer le retour avant le départ. Un transfert a une identité et un état
+ persistants ; interruption, nouvelle requête ou reprise ne créent pas une
+ seconde salle et ne dupliquent pas l'inventaire.
+- Si la destination n'est pas prête, maintenir le joueur en sécurité à la source
+ et permettre d'annuler. Ne pas transférer dans un chunk incomplet.
+- Déconnexion avant transfert : annuler le sommeil. Déconnexion après transfert :
+ reprendre à la dernière position sûre sauvegardée dans les Backrooms, avec
+ le même retour. Un redémarrage n'impose pas un réveil dans l'Overworld.
+- Lit d'origine détruit ou arrivée obstruée : proposer un retour à une position
+ sûre proche de l'ancrage, recherchée dans un rayon borné ; à défaut, utiliser
+ l'arrivée sûre de l'Overworld. Signaler ce secours en FR/EN. Ne pas recréer le
+ lit, modifier le terrain ni rediriger vers la salle d'un autre joueur.
+- Mort : retour proposé dans l'Overworld selon cet ancrage et ce secours ;
+ conserver les règles Sanctuary existantes de mort, d'XP, d'inventaire et de
+ tombe. Une tombe éventuelle reste à son emplacement et ne révèle pas ses
+ coordonnées dans les interfaces joueur. Aucun système de restitution nouvelle
+ des objets perdus n'est ajouté par BR-01.
+- Pour le premier lot, la protection de la salle signifie une arrivée sûre et
+ l'absence d'apparition naturelle de monstres à l'intérieur. Elle ne crée pas
+ implicitement de claim, de verrou de visite ou de règle d'invulnérabilité.
+ Les droits existants s'appliquent aux modifications ; les changements permis
+ aux joueurs persistent, y compris si le tableau ou la porte est retiré.
+
+## 5. Monde partagé et génération progressive
+
+### Hébergement proposé
+
+Utiliser une dimension technique commune, avec l'identifiant proposé
+`sanctuary:backrooms`, à vérifier libre avant enregistrement puis à conserver.
+Les salles personnelles sont des emplacements dans ce monde partagé. Les trajets
+du premier lot sont physiques, continus et stables ; le lit assure les transferts
+vers et depuis l'Overworld.
+
+Un **secteur** est un ensemble de salles et de circulations planifié comme une
+unité. Son plan contient les emprises, niveaux, raccords aux secteurs voisins,
+réservations de salles personnelles et données de génération.
+
+### Ordre de génération
+
+1. Réserver un secteur libre et ses raccords. Associer un instantané de ballast,
+ une graine dérivée, une version de générateur et une version de recettes.
+2. Produire le réseau des pièces : couloirs, embranchements, boucles, impasses
+ lisibles, différences de hauteur et espaces que l'on aperçoit avant d'y accéder.
+3. Attribuer fonctions et ambiances aux ensembles de pièces. Prévoir les
+ transitions et les emplacements stables des refuges personnels.
+4. Construire sols, enveloppes, plafonds, supports, portes, escaliers et bassins.
+ Valider passage à hauteur de joueur, raccords et confinement de l'eau.
+5. Appliquer les matériaux par rôle, puis le mobilier, les lumières et les repères.
+6. Rendre les portions prêtes accessibles à mesure de l'exploration, en respectant
+ un budget borné de travail et de génération anticipée.
+
+Les portes de liaison aux secteurs futurs sont prévues dès le plan initial.
+Une nouvelle salle personnelle occupe une réservation libre ou un nouveau
+secteur raccordé ; elle n'écrase pas une pièce ni une construction existante.
+Le plan garantit un chemin vers le réseau commun et évite les poches isolées.
+
+Les raccords partagés utilisent une convention déterministe conservée côté
+serveur, indépendante de l'ordre de chargement des chunks. Deux joueurs qui
+approchent par des côtés différents réutilisent la même réservation. Le ballast
+ne doit jamais être relu en direct pour changer un raccord pendant sa construction.
+
+La planification réserve une géométrie ; elle ne garde pas tout le monde chargé.
+Bornes de secteur, anticipation, tâches par tick et file d'attente seront
+configurables. Une file saturée ralentit la préparation et maintient les accès
+non prêts fermés de manière sûre. Mesurer les coûts sur le serveur de test.
+
+## 6. Architecture, ambiances et matière
+
+Les plans varient leurs proportions et leurs connexions. Des éléments composés
+à la main peuvent fournir portes, éclairages ou mobilier ; ils ne doivent pas
+imposer une unique salle répétée à chaque tirage.
+
+| Famille du premier lot | Formes et usages | Repères et transitions |
+| --- | --- | --- |
+| Habitation | Chambre, dortoir, salon, couloir étroit | Mobilier, changements de hauteur, ouvertures vers d'autres pièces. |
+| Poolrooms | Bassins, arches, passerelles, vestiaires, douches | Rigoles et zones humides annoncent les bassins ; un chemin praticable permet de progresser. |
+| Infrastructure minérale | Réserves, fondations, soutènements, galeries de maintenance | Cobble, pierre et réparations visibles derrière les enveloppes aménagées. |
+| Traitement dreamcore transversal | Jardin enfermé, lumière évoquant le jour, fenêtre intérieure, volume disproportionné | Étrangeté obtenue d'abord par proportions, répétitions et vues ; aucun portail à rendu spécial requis. |
+
+La relation au ballast agit sur les familles de volumes, les proportions de
+matériaux et leurs rôles. Exemples de recettes proposées :
+
+- Pierre/cobble : épaissir les fondations et favoriser les galeries techniques.
+- Bois travaillé : favoriser cloisons, chambres, réserves et passerelles.
+- Verre : favoriser serres, espaces d'observation et séparations transparentes.
+- Cuivre : favoriser des équipements et architectures techniques ou hydrauliques.
+
+Conserver des palettes par fonction : une poolroom garde son identité même si
+le ballast minéral domine. Les pondérations plafonnées ou à croissance ralentie
+évitent que les matériaux les plus abondants effacent toutes les autres familles.
+Ces transformations de poids n'altèrent jamais les quantités du registre source.
+
+La relation à l'action n'est utilisée que si le suivi la fournit explicitement :
+du cuivre observé ne prouve pas à lui seul qu'une usine a été construite.
+
+### Périodes et ressources
+
+- Associer chaque secteur à une période/révision au moment de sa réservation et
+ conserver les données suffisantes pour reproduire ce plan. Le changement de
+ période influence uniquement de nouvelles réservations.
+- Fournir une base architecturale ancienne pour un serveur au ballast enregistré
+ nul. L'identifier comme héritage fictionnel ; ne pas inventer d'activité passée.
+- En cas d'indisponibilité du suivi, continuer à charger les secteurs connus et
+ les retours sûrs ; suspendre la réservation de nouveaux secteurs. Une panne
+ ne doit pas devenir silencieusement une période de ballast nul.
+- Distinguer l'influence visuelle de la matière comptable récupérable. Avant de
+ rendre des blocs issus du ballast extractibles, appliquer le contrat amont de
+ débit/réservation et de reprise, sans double attribution entre secteurs.
+ La minabilité et les drops du décor doivent être fixés et testés avant livraison.
+ Aucun coffre ne copie automatiquement les objets des joueurs.
+
+## 7. Orientation et intégration au client
+
+- Masquer les positions numériques des surfaces fournies par Sanctuary et du
+ client pris en charge dans cette dimension : F3, HUD, Atlas, marqueurs,
+ Demeure, fiches de lieux, notifications, tombe et messages de transfert.
+- Suspendre le relevé et l'affichage automatiques de la carte/mini-carte pour les
+ Backrooms. Préserver les cartes déjà enregistrées pour les autres dimensions.
+ Vérifier aussi les fonctions natives de carte qui pourraient contourner ce
+ parcours dans le client distribué.
+- Préserver les moyens manuels existants : noms, panneaux, livres, croquis et
+ repères construits. Un éditeur de carte manuelle est hors de ce ticket.
+- Ne pas diffuser un annuaire des salles personnelles ni leurs positions.
+ La découverte physique reste possible, sans autorisation du propriétaire.
+- Les coordonnées restent utilisables dans des diagnostics opérateur explicites
+ et les preuves de test. L'absence de coordonnées est une règle d'interface et
+ de jeu, pas une promesse de cacher la position à un client modifié.
+- Tous les nouveaux messages et libellés sont fournis en français et en anglais.
+
+## 8. Persistance, migration et version de génération
+
+Avant le code qui écrit des données, documenter les schémas et transitions :
+
+- par joueur : identité, identifiant stable de salle, ancrage de retour et état
+ d'un éventuel transfert ;
+- par secteur : identifiant/emprise, voisins et raccords, état de préparation,
+ graine, versions, instantané de ballast et réservations de refuges ;
+- pour la reprise : étapes déjà enregistrées, réservations de matière lorsque
+ nécessaires, traitement d'une interruption entre planification et disponibilité.
+
+Conserver les chunks Minecraft comme état construit et modifiable. Le plan
+sert à produire les portions neuves ; il ne repeint pas les chunks sauvegardés.
+Une donnée invalide est signalée et conservée, sans recréation silencieuse de
+salle ou de registre vide. Une mise à jour ne réinterprète pas les secteurs
+anciens avec de nouvelles recettes.
+
+Développer d'abord sur des mondes neufs dans les dossiers ignorés. Avant toute
+activation sur une sauvegarde existante, livrer un contrat d'adoption explicite :
+ajout de la dimension et des registres, initialisation des liaisons aux lits,
+conservation de l'Overworld, retour des joueurs présents dans les Backrooms lors
+d'une désactivation et limites d'un retour de version. Une désactivation ne
+supprime jamais les secteurs ni le ballast. Aucun déploiement personnel n'est
+inclus dans la rédaction ou l'implémentation de ce ticket.
+
+## 9. Étapes de réalisation du premier lot
+
+Les étapes composent un seul parcours livrable. Les prototypes intermédiaires
+restent dans les mondes de développement jusqu'à satisfaction des critères.
+
+1. **Contrats et intégration** : vérifier la livraison ballast, fixer son
+ adaptateur, les schémas, la minabilité du décor et la règle du lit unique.
+2. **Aller-retour** : dimension commune, deux salles distinctes, tableau et porte,
+ sommeil serveur, persistance et retours ordinaires/de secours.
+3. **Réseau procédural** : secteur d'essai d'environ 12 à 20 pièces, au moins
+ une boucle et deux altitudes, habitation → poolrooms → infrastructure ;
+ une scène dreamcore et des repères reconnaissables. Les nombres sont des
+ objectifs de prototype, pas des tailles universelles du monde.
+4. **Ballast et extension** : relier les recettes aux instantanés réels, comparer
+ plusieurs profils, prolonger vers un autre secteur et éprouver les reprises.
+5. **Parcours client et livraison** : orientation manuelle, FR/EN, multijoueur,
+ performances, sauvegardes et preuves des critères ci-dessous.
+
+## 10. Critères d'acceptation
+
+Toutes les cases décrivent des vérifications futures ; aucune n'est validée par
+la préparation de ce document.
+
+- [ ] **Prérequis** : contrat/version du suivi ballast référencés ; une opération
+ réelle alimente un lot puis un nouveau secteur sans transaction inventée.
+- [ ] **Accès** : sommeil complet individuel, de jour et de nuit, déclenchant
+ exactement un départ ; réveil anticipé sans départ ; temps et météo conservés.
+- [ ] **Identité et visite** : A et B ont deux salles persistantes dans le même
+ réseau ; A rejoint celle de B à pied et y entre ; chaque lit extérieur mène
+ exclusivement à la salle de son joueur.
+- [ ] **Retour** : A et B utilisent successivement un même lit des Backrooms,
+ dont celui d'une salle tierce, et rentrent chacun à leur origine. Tester lit
+ d'origine détruit, obstruction, remplacement et lit des Backrooms retiré
+ pendant le sommeil. Aucun transfert ne place le joueur dans le vide ou un mur.
+- [ ] **Interruption et mort** : reconnexion, redémarrage et arrêt à chaque étape
+ du transfert conservent une salle et un inventaire uniques ; mort/tombe suivent
+ le contrat existant et le secours reste praticable.
+- [ ] **Géométrie** : sur les graines `0`, `42` et `20260915`, démontrer deux
+ refuges raccordés, une boucle, deux altitudes, des passages praticables,
+ des bassins contenus et aucun raccord involontaire vers le vide.
+- [ ] **Identité des lieux** : parcours client montrant habitation, poolrooms,
+ infrastructure en cobble et au moins une scène dreamcore ; repères suffisants
+ pour refaire un trajet sans coordonnées. Une validation visuelle accompagne
+ les contrôles géométriques.
+- [ ] **Empreinte matérielle** : à graine et recettes égales, trois instantanés
+ contrôlés pierre/cobble, bois et verre/cuivre produisent des différences
+ vérifiables de matériaux/rôles tout en préservant le parcours et les ambiances.
+- [ ] **Histoire conservée** : un nouvel apport affecte un nouveau secteur ;
+ plan, constructions, contenus et blocs déjà générés du premier restent
+ inchangés. Une période nulle et des données manquantes suivent deux traitements
+ distincts. Les éventuelles quantités extractibles ne sont attribuées qu'une fois.
+- [ ] **Concurrence et reprise** : charger un plan par plusieurs ordres de chunks,
+ puis par deux explorateurs simultanés ; obtenir les mêmes raccords. Interrompre
+ la création d'un secteur et reprendre sans double réservation ni réécriture
+ de portions déjà disponibles aux joueurs.
+- [ ] **Orientation** : aucun affichage de coordonnées ni cartographie automatique
+ des Backrooms dans les surfaces prises en charge ; cartes de l'Overworld
+ conservées, diagnostics opérateur explicites, messages et parcours FR/EN testés.
+- [ ] **Charge et adoption** : file de génération bornée, arrivée sûre même en
+ retard de préparation, absence de blocage serveur ; fournir budget retenu,
+ mesures de temps de génération/tick/mémoire et configuration de la machine.
+ Vérifier le contrat d'adoption et le retour avant désactivation sur une copie
+ de développement, sans régénérer l'Overworld.
+
+### Vérifications et preuves à produire lors de l'implémentation
+
+- Tests purs du plan : raccords, accessibilité, recettes et reproductibilité.
+- GameTests serveur ciblés : sommeil, identité, retours, persistance, fluides,
+ ballast et absence de doubles effets. Essais à deux clients pour le parcours
+ complet et ses rendus ; un test serveur seul ne valide pas le masquage client.
+- `./gradlew check build`, puis `./gradlew assemblePack` si la distribution change.
+- Rapports avec version Minecraft/mod/générateur, graines, instantanés de ballast,
+ coordonnées opérateur, configuration et captures. Conserver mondes et artefacts
+ dans les dossiers de développement ignorés ; résumer les preuves dans le ticket.
+- À la livraison du mod, synchroniser `mod_version`, `pack_version` et
+ `packwiz/pack.toml` avec le prochain `beta.xxx`, conformément au
+ [versionnement](versioning.md). Ce document seul n'incrémente aucun binaire.
+
+## 11. Hors périmètre et décisions de démarrage
+
+Hors lot : système d'affinités/némésis, nouveaux pouvoirs, générateur général
+d'Indoors achetables, mailbox, shop, objets perdus restitués, quêtes/boss de
+Backrooms, portails à rendu transparent, géométrie qui se reboucle par
+téléportation, évolution des pièces déjà construites et éditeur de cartes.
+
+À fixer dans la première étape à partir du suivi livré : référence du prérequis,
+unités et périodes du ballast, droits d'extraction/drops, dimensions des secteurs,
+budgets de génération et détails de migration. Les réglages proposés de sommeil
+et de secours sont à éprouver. Ces décisions ne rouvrent pas les règles d'accès,
+de visite et de retour confirmées par le créateur.
+
+## 12. Références
+
+- [ANO-01 — doubles de Steve](steve-anomalies.md) prépare la suite : Herobrine
+ dans les Backrooms, mineur fantôme dans l'Overworld et les Backrooms, Steve
+ bugué persistant et transportable. Ces anomalies ont leurs lots propres et
+ ne bloquent pas la livraison du réseau BR-01. Leurs futures modifications
+ de blocs seront des actions de gameplay sauvegardées, jamais une régénération
+ implicite des secteurs existants.
+- [Cosmologie : matière, espaces et mémoire](cosmologie.md).
+- [Vision : dimensions et espaces](vision.md#dimensions-et-espaces).
+- [Blocodex : portée des relevés disponibles](blocodex.md).
+- [Temps réel : sommeil et horloges](realtime-beta037.md).
+- [Règles de contribution](../CONTRIBUTING.md) et
+ [modèle Fonctionnalité](../.gitea/ISSUE_TEMPLATE/feature.md).
+
+Le document conceptuel « Système des Habitants, Affinités et Anomalies » a nourri
+la discussion ; ses autres mécanismes ne sont pas des dépendances de BR-01.
diff --git a/docs/blocodex-beta004.md b/docs/blocodex-beta004.md
new file mode 100644
index 0000000..fbf280c
--- /dev/null
+++ b/docs/blocodex-beta004.md
@@ -0,0 +1,95 @@
+# UI-01 — cadre natif du Blocodex et seuil de sprint
+
+Demande du 13 septembre 2026, branche `codex/blocodex-layout-beta004`.
+Livraison locale d'essai : `beta.004`, après l'export immuable de beta.003.
+
+## Résultat attendu
+
+Le titre Blocodex et les onglets restent en haut de l'écran, comme les menus
+Minecraft. Le bouton natif **Done / Terminé** reste centré en bas. Le corps
+utilise la largeur et la hauteur disponibles à l'échelle de GUI choisie.
+
+Ordre : **Discovery, Progression, Story, Inhabitant** ; traductions françaises
+**Découvertes, Progression, Histoire, Habitant**. Inhabitant conserve la fiche
+comme quatrième onglet. Discovery ouvre les connaissances de blocs existantes.
+La navigation garde le même cadre et Done revient à l'écran qui a ouvert le
+Blocodex, sans empiler les onglets comme des sous-menus.
+
+Compétences à gauche, aptitudes défilantes à droite. Les six compétences
+restent visibles ; Minage, Construction et Inventaire restent « À venir ».
+La fiche et l'histoire doivent rester entièrement lisibles par défilement,
+sans couper la biographie ni les libellés des événements.
+Les six PNG 9×9 fournis le 13 septembre sont utilisés sans modification :
+heart → Vie, hunger → Faim, mining → Minage, building → Construction,
+breathing → Souffle, inventory → Inventaire. La taille suit l'échelle entière
+du GUI ; une infobulle donne le nom de chaque compétence. À l'échelle minimale,
+la colonne gauche dispose de plus de place pour conserver icône et libellé.
+
+## Périmètre et validation
+
+Le retour du créateur ajoute la correction de la faim : beta.003 autorisait
+le sprint avec trois icônes pleines à cause d'un seuil relatif à la capacité.
+beta.004 restaure le seuil vanilla **strictement supérieur à 6 points**.
+Acheter le premier rang de Faim augmente la capacité mais ne remplit pas la
+barre ; manger au-delà de trois icônes permet ensuite de courir. Le serveur
+refuse un démarrage invalide et arrête un sprint quand la faim redescend à 6.
+Les exceptions vanilla du vol créatif et des montures sont conservées.
+
+Aucun prix, identifiant de sauvegarde ou terrain ne change. Les mondes et
+profils beta.003 restent lisibles sans migration. Aucun déploiement personnel.
+
+Vérifier en client natif : les quatre onglets, titre et Done ancrés, navigation
+par clavier et souris, fermeture vers le jeu et vers Échap, défilement,
+recherche et pagination de Discovery, tailles et échelles GUI natives.
+Vérifier le seuil de sprint avant/après le premier achat de Faim, la baisse
+de nourriture, la prédiction d'alimentation et l'exception créative.
+Exécuter `check build` avec les GameTests de progression ciblés, puis assembler
+et vérifier le MRpack beta.004.
+
+## Vérifications effectuées
+
+Sur macOS / Java 25, Minecraft `26.3-pre-2`, Fabric Loader `0.19.5`, Fabric API
+`0.160.0+26.3`, exclusivement dans les dossiers de développement ignorés :
+
+- `./gradlew check build -PsanctuaryFocusedTests=progression` réussit en
+ **4 min 8 s**. Les contrôles purs passent ainsi que **5/5 tests natifs**
+ exécutés par le serveur ciblé. Le scénario des capacités vérifie les seuils
+ de faim 6/7, l'achat qui ne nourrit pas, l'arrêt d'un sprint après baisse de
+ faim et l'exception du vol créatif. Les achats, respawn, données, noms et
+ position assise restent vérifiés par les scénarios de progression.
+- Le parcours client final passe les quatre onglets dans huit
+ configurations de fenêtre/échelle : fenêtres 854×480 et 960×720, options
+ GUI 1, 2, 3 et automatique. Les dimensions logiques obtenues vont de
+ **320×240 à 960×720**. Titre, onglets ordonnés et Done ancrés sont contrôlés,
+ ainsi que recherche et pagination sur le vrai catalogue serveur,
+ navigation clavier et retour vers le jeu ou le menu Échap. Les six icônes
+ sont présentes en 9×9 dans chaque configuration.
+- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true
+ -PsanctuaryProgressionClientTests=true` réussit en **1 min 37 s** sur le code
+ final : seuil client et prédiction d'alimentation, achats, mort et
+ redémarrage, navigation des quatre onglets. Les captures françaises au GUI
+ minimal et la progression à grande taille ont été inspectées : les icônes
+ sont nettes, associées aux bonnes compétences, sans chevauchement des libellés.
+- `./gradlew assemblePack -x check` réussit après les contrôles précédents,
+ sans les relancer pendant l'assemblage. L'export packwiz est vérifié : CRC
+ ZIP, versions interne/externe, JAR embarqué identique au build, cadre et
+ mixin de sprint présents, ressources JSON et clés FR/EN, empreinte Fabric
+ API, index packwiz, six PNG identiques aux originaux et empreinte beta.003
+ antérieure conservée.
+
+## Export local
+
+**[Sanctuary-beta.004.mrpack](../build/Sanctuary-beta.004.mrpack)**,
+2 214 350 octets. Minecraft et les dépendances restent inchangés.
+La sauvegarde beta.003 peut être conservée ; un nouveau monde n'est pas requis.
+
+SHA-256 MRpack : `aee4fdebd48d6719f85d2477fbefe90e12ae38313cc107615fa6105c0c57d475`.
+SHA-256 JAR : `3a8c26105121340a41644c817533bff59f519d7f02837efe570511568eba98a6`.
+Reçu : `build/blocodex004-artifact.json`. Aucun canal public ou installation
+Prism personnelle n'est modifié.
+
+Logs : `build/beta004-check-build.log`, `build/beta004-assemble-pack.log` et
+`build/beta004-client-final.log`. Captures finales : `build/beta004-preview/`.
+Les deux assertions worldgen historiques
+signalées en beta.003 ne font pas partie de la sélection native de ce ticket
+et ne sont pas présentées comme corrigées. Aucun essai Windows n'est revendiqué.
diff --git a/docs/blocodex-navigation.md b/docs/blocodex-navigation.md
new file mode 100644
index 0000000..200e851
--- /dev/null
+++ b/docs/blocodex-navigation.md
@@ -0,0 +1,175 @@
+# Blocodex — navigation, découvertes et vitrine de l'habitant
+
+Discussion du 13 septembre 2026, après l'essai de beta.004.
+Branche de conception : `codex/blocodex-navigation`.
+**Cadrage uniquement : les comportements ci-dessous ne sont pas encore livrés.**
+Le pack beta.004 reste inchangé ; cette note ne modifie aucune sauvegarde.
+
+## Décisions exprimées par le créateur
+
+- Conserver le rendu Minecraft, le titre en haut et le bouton Terminé en bas.
+- Réunir statistiques, advancements, carte, descriptions et constructions
+ dans la navigation du Blocodex.
+- Cacher les entrées non découvertes aux joueurs ; permettre leur inspection
+ aux opérateurs. Distinguer explicitement les essais joueur et opérateur.
+- Retirer S'asseoir des aptitudes et du passeport. La faculté de s'asseoir
+ reste disponible dès le début ; seul son emplacement dans ces menus change.
+- Faire du passeport une fiche dont on peut être fier : exploits accomplis,
+ constellation favorite, type de créature favori.
+- Un favori doit être découvert. Le type de créature favori peut changer une
+ fois toutes les **24 heures réelles** et donne une influence légère, notamment sur l'apprivoisement
+ et le butin. Le choix quotidien de différents types est volontairement permis.
+- Intégrer les plans de construction avec Litematica.
+- Le créateur valide la maquette comme référence à conserver et réutiliser
+ pour le site, avec le fonds d'images et de documents jusqu'à alpha.30.7.
+- Priorité suivante : carte native Sanctuary inspirée de Xaero, puis Demeure,
+ factions et claims, New Game Plus et prestiges. L’exploration précède le
+ déblocage de Grande carte. Voir [MAP-01](atlas-beta005.md).
+
+## Organisation proposée
+
+Quatre rubriques principales stables, puis une navigation locale. L'accès à
+une fonction est retrouvé par sa place et son nom, quelle que soit la touche
+qui a ouvert le Blocodex. À petite échelle, les sous-rubriques passent sur
+plusieurs lignes ; le corps défile sans déplacer l'en-tête ni le pied de page.
+
+| Rubrique FR / EN | Sous-rubriques proposées | Rôle |
+| --- | --- | --- |
+| Découvertes / Discovery | Blocs, Objets, Créatures, Recettes | Ce que je connais, avec description et actions contextuelles. |
+| Progression / Progression | Capacités, Exploits, Statistiques | Compétences à gauche et aptitudes à droite ; advancements Minecraft en priorité, accomplissements Sanctuary séparés ; compteurs vanilla. |
+| Monde / World | Atlas, Constructions, Histoire | Cartes et marqueurs ; plans et matériaux ; événements, Gazette, archives et liens avec le Galactium lorsqu'ils seront disponibles. |
+| Habitant / Inhabitant | Passeport et vitrine sur la même page | Identité, exploits épinglés, constellation favorite et créature favorite. |
+
+La maquette approuvée utilise **Monde** à la place de Story pour accueillir
+la carte et les constructions en plus de l'histoire. La référence archivée
+est dans [archives/web](../archives/web/README.md) ; son statut reste une
+conception validée, sans implémentation supplémentaire dans beta.004.
+
+Les boutons Statistiques et Advancements peuvent d'abord ouvrir les écrans
+natifs depuis Progression et revenir au Blocodex. Il n'est pas nécessaire de
+réécrire leurs données ou leur rendu pour unifier l'accès. Le cadre reste
+identique pour les pages Sanctuary ; les écrans communautaires peuvent garder
+leur présentation avec un retour au bon écran parent.
+
+L'item description devient le contenu de la fiche d'un objet découvert,
+pas une rubrique de plus. Depuis une recette : ouvrir l'objet connu ; depuis
+un exploit accompli : l'épingler dans le passeport ; depuis un favori : ouvrir
+sa fiche ; depuis un plan : consulter les matériaux connus.
+
+## Découvertes et droit de fabrication
+
+Le socle actuel ne suit que les blocs. Il transmet leur catalogue complet,
+et affiche aussi les noms inconnus : voir `BlockKnowledgeNetworking` et
+`SanctuaryBlocodexScreen`. Les objets indépendants et créatures exigent un
+contrat de découverte supplémentaire avant leur intégration réelle.
+
+Pour la nouvelle vue joueur, une entrée inconnue ne produit aucune ligne,
+silhouette, nom, description, résultat de recherche ou liste de favoris.
+Afficher le nombre découvert, sans total de registre révélant les manques.
+Les éventuelles pistes viendront de quêtes ou conversations, sans exposer
+automatiquement le catalogue secret.
+
+Filtrer la réponse Sanctuary côté serveur et les vues côté client. Une
+navigation depuis une recette, un plan ou une recherche ne doit pas révéler
+indirectement les noms et descriptions masqués. Ne pas considérer le registre
+Minecraft, nécessaire au client, comme un secret cryptographiquement caché.
+
+Conserver trois décisions distinctes : découverte de l'objet, accès à
+l'outil de consultation des recettes, révélation d'une recette particulière.
+**La fabrication manuelle reste libre**, même si la recette n'est pas révélée.
+Les écrans vanilla conservent leurs règles de visibilité d'advancements ; on
+ne cache pas un objectif Minecraft normal simplement parce qu'il est inachevé.
+
+## Opérateur et essais
+
+En solo, Autoriser les commandes permet d'accéder aux outils opérateur selon
+les permissions réellement accordées. En multijoueur, le serveur décide des
+droits du joueur ; le mode créatif seul n'est pas une preuve d'autorisation.
+Utiliser le niveau des commandes Sanctuary existantes comme référence.
+
+Un opérateur peut ouvrir une **Vue opérateur** explicitement marquée, avec
+catalogue complet et états de découverte. La vue joueur reste disponible
+pour tester le parcours ordinaire. Inspecter un contenu ne le marque pas
+découvert et n'accorde aucun exploit, XP, favori ou aptitude.
+
+La vue joueur d'un opérateur sert à vérifier l'affichage. Elle ne transforme
+pas ses permissions : les tests d'autorité exigent aussi un vrai joueur sans
+droits. Séparer les actions de diagnostic des actions qui modifient l'état.
+Toute révocation de droits invalide la vue opérateur et son cache.
+
+Matrice minimale du futur ticket :
+
+- Solo sans commandes et client non-opérateur : inconnus absents, recherches
+ et chemins indirects filtrés, aucun accès aux outils opérateur.
+- Solo avec commandes et opérateur serveur : inspection complète possible,
+ marquée comme telle ; aucune découverte enregistrée par cette inspection.
+- Opérateur en vue joueur : mêmes lignes visibles que selon ses découvertes.
+- Retrait des droits avec une page ouverte : retour à la vue autorisée.
+- Découverte réelle, mort et reconnexion : mise à jour puis conservation.
+- Carte verrouillée : raccourcis, menus et autres points d'entrée respectent
+ le même droit ; minimap, grande carte et marqueurs restent trois aptitudes.
+
+## Passeport et affinité
+
+Proposition : trois emplacements d'exploits Minecraft déjà obtenus, une
+constellation connue et un type de créature découvert. Le nombre de trophées
+et le moyen de partager le passeport restent à confirmer. Les identifiants
+référencent les sources de vérité ; épingler n'accorde pas l'accomplissement.
+La constellation favorite nécessite le futur système du ciel : aucune liste
+fictive ne doit devenir un catalogue réel dans le mod.
+
+Pour la créature, le créateur confirme un délai de **24 heures réelles depuis le dernier
+changement**, calculé côté serveur et conservé après mort, déconnexion et
+redémarrage. Il ne s'agit pas d'une remise à zéro à minuit.
+
+Seul le favori courant donne un léger bonus, sans cumul permanent des anciens
+choix. Les animaux déjà apprivoisés restent apprivoisés. Ne pas inventer
+l'apprivoisement d'une espèce qui n'a pas cette mécanique : chaque type doit
+avoir un effet adapté ou ne recevoir aucun effet mécanique.
+
+Restent à régler avant le code : coefficient, types éligibles, variante ou
+type entier, attribution du butin en coopération, et portée du bonus de butin
+(mort de l'animal, productions comme laine/œufs, ou les deux). Un bonus à la
+mort encourage à chasser son favori ; un bonus aux productions encourage à
+s'en occuper. Le profil peut rester discret sans afficher un pourcentage.
+
+Le contrat de persistance des favoris doit être écrit avant d'ajouter des
+données aux habitants. Préférer un ajout versionné avec absence interprétée
+comme aucun favori, sans réécriture silencieuse des profils beta.003/004.
+
+## Carte et constructions
+
+Sanctuary développe désormais sa cartographie native ; le partage futur
+des découvertes reste de joueur à joueur. Litematica conserve la gestion de schémas, les projections et les
+listes de matériaux : [documentation de l'auteur](https://github.com/maruohon/litematica/wiki/Frequently-Asked-Questions).
+Sanctuary fournit le point d'entrée, les droits et les liens vers ses données.
+Un plan reçu ne constitue pas automatiquement une découverte de ses matériaux.
+Préciser le filtrage de la projection et de la liste avant l'intégration pour
+éviter de révéler des matériaux inconnus par ces écrans.
+
+Aucune compatibilité Minecraft 26.3-pre-2 de Xaero, JEI ou Litematica n'est
+établie par cette note, et aucun de ces mods n'est ajouté au pack. Vérifier
+version exacte, dépendances, API et distribution au ticket de chaque adaptateur.
+Le collage créatif d'un schéma ne doit pas devenir une fonction de construction
+accessible par la seule découverte d'un plan.
+
+## Découpage proposé
+
+L'ordre historique était carte native, puis Demeure, factions/claims,
+New Game Plus et prestiges. Après beta.006, le créateur demande la carte
+immersive et l'achèvement des six compétences avant le New Game+ ; les factions
+sont conçues avec le cycle. Les [prochains tickets](prochains-tickets.md) font
+foi pour cet ordre actualisé. Les lots d'interface ci-dessous restent identifiés.
+
+1. **UI-02** : retirer S'asseoir de ces deux pages, filtrer le catalogue actuel
+ selon les droits et découvertes, ajouter les accès natifs Statistiques et
+ Advancements. Tests joueur/opérateur et retour des écrans. Aucun favori,
+ nouveau format de sauvegarde ou mod communautaire nécessaire.
+2. **KNOW-02 / PROFILE-02** : découverte des objets et créatures, épinglage
+ des exploits et contrat des favoris. Effets d'affinité dans un lot séparé
+ une fois les règles chiffrées arrêtées ; le délai de 24 heures est confirmé.
+3. **MAP-01 / CONNECT-01** : carte native, puis adaptateurs recettes et plans sur des versions
+ vérifiées ; réutiliser les mêmes contrôles de découverte et d'aptitudes.
+
+La maquette de discussion illustre la navigation avec des données fictives.
+Elle ne démontre ni l'intégration des mods ni l'application des permissions.
diff --git a/docs/boats-head-beta049.md b/docs/boats-head-beta049.md
new file mode 100644
index 0000000..a6ac5dc
--- /dev/null
+++ b/docs/boats-head-beta049.md
@@ -0,0 +1,82 @@
+# beta.049 — bateaux rectangulaires et blocs portés
+
+Branche `codex/rectangular-boats-beta049`.
+
+## Contrat
+
+Quatre formats supplémentaires, largeur × longueur : 3 × 1, 1 × 3,
+2 × 3 et 3 × 2. Les recettes reprennent exactement cette disposition dans
+l'établi, avec uniquement des bateaux ordinaires identiques du même bois.
+Les onze bois natifs sont couverts : 44 variantes ajoutées, 66 au total.
+Les noms reprennent le bois suivi des dimensions, en français et en anglais.
+Les plans rejoignent aussi une collection Bateaux et radeaux déjà acquise.
+
+Chaque module accueille deux passagers, joueur ou animal. On remplit d'abord
+les places principales de tous les modules, puis leurs places secondaires.
+Cela donne 6 places en 3 × 1 et 1 × 3, 8 en 2 × 2, 12 en 2 × 3 et 3 × 2,
+et 18 en 3 × 3. Les deux sièges sont décalés de 0,8 bloc dans le même module.
+Les fonds natifs sont répétés, avec rebords uniquement
+à l'extérieur et deux rames. Les commandes, moteurs et piles de beta.048 sont
+conservés. Le supplément de masse est `(largeur + longueur − 2) × 0,25` :
+les carrés conservent exactement leur équilibre, et tourner une recette
+rectangulaire ne change pas sa puissance. Les limites de collision des nouveaux
+rectangles suivent leur orientation.
+
+## Blocs sur la tête
+
+- Les sons natifs incluent le joueur qui actionne le bloc, le porteur et les
+ joueurs proches. Une seule diffusion serveur ; les blocs posés conservent
+ leur mécanisme de prédiction habituel.
+- L'apparence reçoit l'état natif du bloc et les informations visibles du feu
+ de camp, du pupitre et du pot décoré. Les aliments en cuisson deviennent
+ visibles. Les événements d'ouverture et de fermeture des coffres sont transmis.
+- Les modèles natifs des blocs avec rendu spécial sont utilisés. Les bateaux
+ ordinaires et collectifs disposent aussi de leur modèle 3D comme chapeau.
+- Les façades des fours et les têtes sont tournées vers l'avant du personnage.
+ La transformation commune conserve le haut des modèles vers le haut.
+- Un coup infligeant des dégâts produit dix ticks de signal redstone local.
+ Distributeurs, droppers, lampes et autres récepteurs natifs réagissent à ce
+ signal. Une trémie respecte sa règle native : un signal la verrouille
+ temporairement. Le piston s'étend et se rétracte localement, sans pousser le
+ terrain. La TNT garde son amorçage réel déjà existant.
+
+Les cosmétiques bateaux restent des modèles portés ; ce ticket ne crée pas un
+second véhicule pilotable sur la tête. Les conditions des blocs fonctionnels
+et les règles de restitution de beta.043 restent applicables.
+
+## Données et limites
+
+Les anciens identifiants, bateaux et formats de sauvegarde restent stables.
+La forme des nouveaux bateaux est portée par leur identifiant enregistré.
+Les informations de rendu passent dans la synchronisation d'apparence existante,
+pas dans une copie récupérable de l'inventaire. Les coffres et les fours ne
+transmettent pas leur stockage à tous les observateurs. Aucun monde personnel
+ni chunk existant n'est modifié par cette livraison.
+
+Les animations de rendu sont limitées aux fonctions de présentation natives ;
+un modèle client ne simule pas un second bloc fonctionnel. Les sources d'énergie
+et les circuits environnants ne deviennent pas un réseau mobile de redstone.
+
+## Vérification
+
+`./gradlew check build :sanctuary:runGameTest assemblePack assembleTestPack`
+réussit avec les groupes `boatshead,poultry,headblocks,cosmetics` : **35 tests
+serveur**. Les 66 recettes sont fabriquées, leurs ingrédients mixtes refusés,
+les capacités complètes et les passagers sauvegardés/rechargés. Les sièges
+principaux précèdent les secondaires. Les paquets natifs de son contiennent
+une diffusion sans exclusion pour la trappe portée, tout en gardant l'exclusion
+vanilla sur un bloc normal. Les aliments, états, retrait des inventaires,
+impulsions, extinction de lampe et amorçage unique de TNT sont contrôlés.
+
+Le parcours client natif FR/EN passe : 66 modèles par tuiles, vol des quatre
+rectangles via les touches réelles, aliments du feu de camp, ouverture/fermeture
+de coffre et Shulker, animation de cloche avec fin, et bateaux comme chapeaux.
+Les captures des têtes et fours ont été relues : façades vers l'avant, haut des
+modèles conservé. Preuves dans `build/boats049-evidence/` ; logs
+`build/boats049-delivery.log` et `build/boats049-client-final.log`.
+
+Le reçu `build/boats049-artifact.json` atteste 1 529 classes et leurs ressources
+conformes au build, les deux MRpacks, JEI et les textures conservées. Les archives
+beta.048 sont inchangées. Aucun déploiement personnel ni publication du canal.
+Une session avec deux clients distants n'a pas été relancée : le son a été
+vérifié au niveau de sa diffusion serveur native, puis le rendu en client local.
diff --git a/docs/building-beta009.md b/docs/building-beta009.md
new file mode 100644
index 0000000..563c60e
--- /dev/null
+++ b/docs/building-beta009.md
@@ -0,0 +1,108 @@
+# PROG-02B — Construction et vein building — beta.009
+
+Branche : `codex/progression-vein-building`. Implémentation et export local
+vérifiés le 13 septembre 2026 ; parcours graphique encore non validé, voir
+ci-dessous. Cible : Minecraft 26.3-pre-2, Fabric et Java 25.
+
+## Contrat préalable de migration
+
+La progression personnelle passe du schéma 3 au schéma 4, en ajoutant
+`building: 0`. Les schémas 1 et 2 passent d'abord par leurs migrations strictes
+existantes. Les rangs, achats, dates, coûts, aptitudes et révisions sont
+conservés sans achat fictif. L'historique accepte sept achats Construction
+supplémentaires, soit 37 événements maximum ; chaque rang doit correspondre
+exactement à ses achats. Les données invalides sont refusées sans remise à zéro.
+La migration est idempotente et conserve l'identifiant `sanctuary:progression`.
+La restauration réattache la représentation canonique, ensuite enregistrée par
+la sauvegarde Minecraft normale. Mort, reconnexion et redémarrage conservent
+les rangs. Aucun New Game+ n'est activé.
+
+Les fichiers de configuration restent au schéma 2 : Construction utilise les
+sept `statCosts`, par défaut 1, 2, 4, 8, 16, 32 et 64 niveaux. Aucun changement
+des profils, couleurs, Atlas, Demeure, inventaire, génération ou chunks existants.
+Client et serveur doivent utiliser beta.009 ensemble. Les versions antérieures
+ne lisent pas le schéma 4 ; revenir en arrière exige une sauvegarde antérieure
+des données de joueur. Aucun monde personnel n'est ouvert ou modifié ici.
+
+## Contrat de jeu et valeurs d'essai
+
+- Rang 0 : pose Minecraft individuelle. Sept achats débloquent les diamètres
+ 3, 5, 7, 9, 11, 13 et 15 (jusqu'à 225 emplacements dans un plan).
+- La portée de visée et d'attaque reste celle de Minecraft. Seul le plan autour
+ du point de départ s'agrandit ; aucune portée d'attaque supplémentaire.
+- Une pose tous les deux ticks en survie ; lots bornés en créatif. Le statut
+ opérateur en survie n'accorde ni matériaux gratuits ni cadence créative.
+- R est la touche commune configurable : avec un bloc en main, aperçu de
+ construction ; R + clic droit maintenus posent, R + molette règle le diamètre.
+ R + clic gauche reste le minage. B reste le Blocodex.
+- Plan déterminé par la face visée et l'orientation du joueur, ordre du centre
+ vers l'extérieur. Le serveur recalcule et revalide les cibles, le stock et les
+ permissions. Relâcher, changer d'objet, ouvrir un menu, mourir, s'éloigner ou
+ perdre la connexion arrête le geste ; les poses déjà effectuées restent.
+- Les poses passent par le placement natif pour les orientations, supports,
+ collisions, contenus d'objets, consommation, statistiques et traces Demeure.
+ Aucune récompense pour l'aperçu ou une pose refusée. Les surfaces ne remplacent
+ pas les blocs occupés et n'ouvrent pas les contenants servant de support.
+ Une dalle peut être fusionnée par son contexte Minecraft natif.
+- Le plan couvre les emplacements dont le support existe déjà, comme le Builder
+ historique. L'aperçu ne dessine que les poses valides ; il ne coûte aucun objet.
+ La pile tenue fournit les matériaux, sans réapprovisionnement automatique.
+- Portes, lits et plantes doubles gardent la pose individuelle, pour ne pas
+ modifier une seconde case hors du plan validé. Échafaudages et nénuphars aussi :
+ leurs placements natifs recherchent une autre cible. Les contextes déplacés
+ et chunks non chargés sont refusés avant tout placement groupé.
+
+## Vérifications
+
+- `./gradlew :sanctuary:compileGametestJava check build assemblePack -PsanctuaryFocusedTests=progression,map,demeure,mining,building`
+ **réussi en 3 min 55 s**, avec **28 tests serveur natifs obligatoires réussis**,
+ dont dix scénarios Construction. Journal : `build/beta009-delivery-complete.log`.
+- Les contrôles purs relisent les schémas 1, 2 et 3 et vérifient l'idempotence,
+ les dates/coûts/rangs conservés, sept achats Construction, plafond et refus
+ d'un rang sans historique. La configuration reste au schéma 2.
+- Les tests serveur vérifient le passage par le placement natif, les six faces,
+ les quatre orientations, les plans de 225 cases sans doublon, les coûts,
+ l'absence d'extension de portée, les stocks courts, le créatif, les dalles
+ simples/doubles/hautes et les escaliers. Les collisions avec un animal,
+ contenants occupés, droits Fabric, mutation pendant un callback, concurrence,
+ interruption et expiration du maintien sont couverts. Les statistiques,
+ Blocodex et contributions Demeure proviennent des poses confirmées.
+- Le test de progression sauvegarde/recharge le joueur et copie ses achats,
+ dont Construction, lors d'une mort ordinaire. Le contrat de reprise est
+ contrôlé sans ouvrir un monde personnel.
+- Le parcours client `Building009ClientChecks` est écrit et compilé : achats
+ par clic, aperçus 25/9, molette, arrêt, consommation, une seule couche par
+ geste, reconfiguration et interruption par le Blocodex. **Son exécution
+ reste non validée** : les deux lancements se sont bloqués avant le monde
+ dans `Minecraft.` / `SDL_GL_SwapWindow`. Le contrôle de bureau a
+ confirmé que le Mac était verrouillé. Aucun résultat graphique beta.009
+ ni capture en jeu n'est revendiqué. Les processus de test ont été arrêtés ;
+ la valeur VSync du dossier de développement a été remise à sa valeur initiale.
+ Journaux : `build/beta009-client.log`, `build/beta009-client-retry.log` ;
+ piles : `build/beta009-client-threads.txt`, `build/beta009-client-retry-threads.txt`.
+
+La sélection des tests serveur est limitée à
+`progression,map,demeure,mining,building` ; ce ticket ne revendique pas la
+correction des deux anciennes assertions de génération générales.
+
+Référence historique en lecture seule : sélection, plan et session de
+construction dans `../26.2/sanctuary`, GPL-3.0-or-later. Les appels natifs sont
+vérifiés dans les classes de Minecraft 26.3-pre-2, dont `BlockItem.place`,
+`ItemStack.useOn` et `ServerPlayerGameMode.useItemOn`. Le mixin commun contourne
+seulement l'interaction du support comme le fait la pose accroupie ; les hooks
+Fabric, le placement, les contenus d'objets, coûts et critères Minecraft restent
+actifs. La revalidation a lieu après les callbacks et avant l'usage de l'objet.
+
+## Artefact local
+
+- [Sanctuary-beta.009.mrpack](../build/Sanctuary-beta.009.mrpack), **2 360 956 octets**.
+- SHA-256 : `efa1ac7974b16e2f6169149518e08ed2bca4371b386b3413850b08618392da21`.
+- Reçu : `build/building009-artifact.json` ; somme séparée :
+ `build/Sanctuary-beta.009.mrpack.sha256`.
+- ZIP et JSON vérifiés, identité du JAR construit et du JAR Demeure embarqué,
+ versions internes, classes/mixins Construction, traductions FR/EN, six icônes
+ originales, hash Fabric API et index packwiz. Aucune classe de test embarquée.
+ Les exports beta.003 à beta.008 sont restés strictement identiques.
+
+L'artefact est disponible pour essai local. Le canal packwiz n'a pas été publié
+et aucune instance personnelle n'a été synchronisée.
diff --git a/docs/carry-babies-beta041.md b/docs/carry-babies-beta041.md
new file mode 100644
index 0000000..d4150a0
--- /dev/null
+++ b/docs/carry-babies-beta041.md
@@ -0,0 +1,42 @@
+# beta.041 — bébés portés sans ralentissement
+
+Branche `codex/carry-babies-beta041`. Règle retenue : porter un bébé mob ne
+ralentit pas le porteur. La précision demandée à l'utilisateur reste ouverte ;
+cette interprétation est annoncée pendant le travail.
+
+Les bébés natifs ne contribuent plus au poids de la pile. Les autres passagers
+gardent un facteur de 0,75 chacun, avec le plancher global existant de 25 %.
+Un joueur portant un bébé compte toujours lui-même lorsqu'il monte sur un
+autre joueur. Le bébé ne rajoute pas une seconde pénalité au joueur du bas.
+
+Les familiers utilisent l'âge de leur modèle natif : si celui-ci peut être
+affiché en bébé, le familier bénéficie de la même exemption. Une petite taille
+aléatoire ou un modèle adulte réduit ne suffit pas à devenir un bébé.
+La vérification réutilise le modèle natif déjà mis en cache pour les sons ;
+aucune entité supplémentaire n'est ajoutée au monde ou animée côté serveur.
+
+Le calcul reste côté serveur et se remet à jour au prochain tick lorsqu'un
+animal grandit sur la tête. Prendre et déposer applique le changement
+immédiatement. Le portage, les collisions, le plané et les interactions gardent
+leurs règles existantes. Aucun identifiant, attachement ou format de sauvegarde
+n'est modifié.
+
+## Validation
+
+`./gradlew check build assemblePack assembleTestPack
+-PsanctuaryFocusedTests=carry,familiar,movement,corrections,familiarhit` réussit,
+avec **43 tests serveur natifs**. Les scénarios couvrent le bébé dans une pile
+de joueurs, la croissance sans dépose, le retour au statut bébé, les familiers
+vache/zombie et le dragon adulte réduit ; les anciens tests de poids, cumul de
+modificateurs, démontage, plané et réaction aux coups passent également.
+
+Les deux MRpacks correspondent aux 1 441 classes compilées. Seules les classes
+de CarryService et FamiliarEntity diffèrent de beta.040 ; les 173 PNG et les
+ressources client sont identiques. Aucun nouveau parcours graphique ou test de
+connexion réseau dédié : les résultats client de beta.040 restent consignés
+dans son reçu. Aucune acceptation d'EULA, installation personnelle ou
+publication du canal. Les exports beta.039 et beta.040 restent immuables.
+
+Exports : [pack normal](../build/Sanctuary-beta.041.mrpack) ·
+[profil plat rapide](../build/Sanctuary-Test-beta.041.mrpack).
+Preuves locales : `build/carry041-check.log`, `build/carry041-artifact.json`.
diff --git a/docs/cartographie-open-source.md b/docs/cartographie-open-source.md
new file mode 100644
index 0000000..0832d6f
--- /dev/null
+++ b/docs/cartographie-open-source.md
@@ -0,0 +1,73 @@
+# Cartographie — pistes MIT et GPLv3
+
+Relevé du 13 septembre 2026, demandé après la décision d'attendre Xaero.
+Sources : métadonnées et versions officielles Modrinth, README et fichiers
+LICENSE des dépôts liés par les auteurs. **Aucun mod installé, forké, compilé
+ou testé en jeu pendant cette étude.**
+
+## Pistes retenues pour comparaison
+
+| Projet | Licence du projet vérifiée dans les sources | Version Fabric 26.2 identifiée | Intérêt pour Sanctuary |
+| --- | --- | --- | --- |
+| [Improved Maps](https://modrinth.com/mod/improved-maps) | [MIT](https://github.com/craftycorvid/ImprovedMaps/blob/main/LICENSE) | `1.0`, ID `kS9puVhR`, 21 août 2026 | Atlas géré côté serveur, copie des cartes pour les transmettre ; minimap et grand atlas avec le complément client. |
+| [Simple Atlas](https://modrinth.com/mod/simple-atlas) | [MIT](https://github.com/RubberToe-06/simple_atlas/blob/master/LICENSE) | `1.2.0`, ID `6Hy4rXil`, 18 juin 2026 | Grande carte interactive, marqueurs, dimensions et fusion/copie d'atlas ; pas de minimap HUD annoncée dans les fonctions consultées. |
+| [Ultimate Map Atlases](https://modrinth.com/mod/ultimate_map_atlases) | [GPLv3](https://github.com/nanakytim/Ultimate_Map_Atlases/blob/main/LICENSE), Modrinth : `GPL-3.0-only` | `26.2`, ID `bgd39YOI`, 17 juin 2026 | Minimap et carte fondées sur les cartes vanilla, atlas ouvrable/fermable et boussole influençant son affichage. |
+| [BlueMap](https://modrinth.com/plugin/bluemap) | [MIT](https://github.com/BlueMap-Minecraft/BlueMap/blob/master/LICENSE) | `5.24-fabric`, ID `xvccCRD9`, 10 septembre 2026 | Carte 3D pour navigateur et site ; composant complémentaire, pas une minimap de jeu. |
+
+Pour **chacun de ces quatre projets**, le filtrage des versions publiées par
+Fabric + Minecraft `26.3-pre-2` renvoie **zéro version déclarée** au moment du
+relevé. Une build 26.2 ne constitue pas une preuve de fonctionnement sur la
+cible actuelle. Les pages générales peuvent décrire des fonctionnalités plus
+récentes que certains binaires : vérifier le tag et le JAR retenus avant code.
+
+## Lecture pour Sanctuary
+
+**Improved Maps est la première piste à étudier si nous revenons à un fork
+libre** : il réunit déjà minimap, grand atlas et cartes transmissibles. C'est
+une appréciation de conception fondée sur les fonctions annoncées, pas un
+résultat de test. Ses états sont liés à un objet atlas et aux cartes vanilla,
+pas encore à l'identité et aux aptitudes Sanctuary. Les marqueurs personnels,
+la séparation des trois aptitudes et l'ouverture sans objet depuis le Blocodex
+restent à vérifier ou développer.
+
+Simple Atlas est intéressant pour les marqueurs et la circulation de cartes.
+Ultimate Map Atlases est une autre base possible pour une approche diégétique
+où l'atlas s'ouvre et se ferme. Dans les deux cas, distinguer les découvertes
+du joueur des cartes contenues dans un item partagé.
+
+Ces trois solutions reposent sur les cartes vanilla : vérifier le rendu des
+îles flottantes, surfaces superposées, dimensions, performances et niveaux de
+zoom avant de les choisir comme remplaçant de Xaero. Leur licence principale
+ne suffit pas à valider toute la pile : Improved Maps annonce Polymer (LGPLv3)
+et Fabric API, Simple Atlas déclare Cloth Config (LGPLv3) dans sa version
+26.2 ; Ultimate Map Atlases ne déclare pas de dépendance dans cette fiche
+de version, ce qui ne prouve pas qu'il n'en a aucune à l'exécution.
+
+BlueMap peut alimenter le futur site/Galactium avec des vues 3D. Il lit les
+données du monde ; ses cartes ne sont donc pas automatiquement limitées aux
+découvertes individuelles. Définir les zones et informations publiables avant
+tout raccordement aux secrets et à la progression. Aucun rendu de monde ni
+serveur web BlueMap n'a été lancé ici.
+
+## Faux équivalents écartés du filtre strict
+
+- **XaeroPlus** est MIT, mais requiert Minimap et World Map, qui restent
+ All Rights Reserved. Il ne remplace pas leurs moteurs.
+- **VoxelMap-Updated** expose un dépôt source, mais le projet Modrinth
+ déclare All Rights Reserved : il ne répond pas au filtre MIT/GPLv3.
+- **Antique Atlas 4** et **Surveyor** sont annoncés sous LGPL-3.0-or-later.
+ Ce sont des pistes libres, mais distinctes de GPLv3 et hors du filtre exact
+ demandé. La branche récente d'Antique Atlas 4 ne doit pas être confondue
+ avec l'ancien **Antique Atlas**, GPLv3, dont la dernière build Fabric
+ identifiée est `7.1.1-fabric-mc1.18.2`.
+- **Map Atlases [Forge]** est GPLv3 mais le relevé ne contient pas de build
+ Fabric ; ne pas le confondre avec les ports ou variantes portant un nom proche.
+
+## Preuves conservées
+
+`archives/web/references/open-maps-intake-2026-09-13.json` contient les projets,
+leurs licences déclarées, URLs des sources, IDs et dépendances des versions,
+résultats du filtre exact et empreintes des fichiers LICENSE vérifiés dans
+les quatre dépôts retenus. Le créateur a ensuite choisi une carte native Sanctuary :
+voir [MAP-01](atlas-beta005.md). Ce relevé reste une référence historique,
+sans substitution automatique dans Sanctuary.
diff --git a/docs/collections-beta035.md b/docs/collections-beta035.md
new file mode 100644
index 0000000..d3da4c2
--- /dev/null
+++ b/docs/collections-beta035.md
@@ -0,0 +1,115 @@
+# RECIPES-035 — collections Minecraft jouables
+
+Branche `codex/collections-beta035`, base beta.034. Contrat tiré des
+[67 collections](collections-minecraft.md) et de leur
+[inventaire exhaustif](collections-minecraft-inventaire.md).
+
+## Contrat et reprise des habitants
+
+Les **2 042 recettes natives de Minecraft 26.3-pre-2** ont chacune leur collection
+principale. Les **126 advancements** ouvrent les familles du tableau : chaque
+advancement configuré suffit, sans achat du catalogue ni possession des ingrédients.
+Les collections de survie s’ouvrent également à la découverte d’un membre admissible
+ou d’une amorce. Comme depuis beta.024, « découverte » utilise les connaissances
+existantes : observation réelle d’un bloc, objet connu, inventaire ou statistiques
+natives. La lecture d’un plan ne crée jamais une découverte de son résultat ou de
+ses ingrédients. La fabrication manuelle reste libre.
+
+66 familles de survie et une référence technique sont livrées. La référence
+`sanctuary:technical_catalogue` n’a aucun déclencheur de survie et ne s’ajoute pas
+au carnet personnel, même avec un œuf de familier. Les blocs sans objet et les
+blocs non récupérables (bedrock, générateurs, cadres de portail, etc.) ne sont pas
+transformés en amorces d’inventaire. Les familles sans recette peuvent être
+connues sans produire un faux gain de plans.
+
+**Migration additive sans changement de format :** la pièce jointe
+`sanctuary:recipe_knowledge` reste en schéma 1 ; aucun champ de sauvegarde n’est
+ajouté, supprimé ou réinterprété. Au prochain chargement de leur carnet, les
+habitants reçoivent les compléments correspondant à leurs collections déjà
+ouvertes, objets connus et advancements déjà accomplis. Tous les identifiants et
+recettes déjà appris restent conservés, même absents des nouvelles données.
+Aucun monde existant n’est ouvert ou réécrit par la livraison.
+
+`sanctuary:basic_wood` et `sanctuary:brewing` restent stables. Le bois conserve son
+ancien tag `minecraft:planks` et **40 recettes complémentaires** de bateaux et
+menuiseries du Nether ; les 170 recettes primaires restent classées séparément.
+L’alchimie conserve le sélecteur `minecraft:brewing`, y compris les recettes de ce
+type ajoutées par un datapack ou un mod, en plus de ses 286 recettes primaires.
+Cette compatibilité ne révèle pas les pistons ou les boucliers avec une planche.
+L’achat unique du catalogue reste à **4 niveaux**.
+
+## Données et consultation
+
+Les 67 JSON sont dans `data/sanctuary/sanctuary_recipe_collections/`. `recipes`
+contient le classement principal ; `additional_recipes` préserve les accès
+historiques. `items`, `item_tags` et `advancements` sont des portes alternatives.
+`members.blocks` / `members.items` conservent le classement exact des identifiants
+pour les futures fiches ; ces listes ne constituent pas des possessions requises.
+`operator_only` empêche une définition technique de devenir un déblocage personnel.
+
+Le chargement indexe objets, tags et advancements une fois par rechargement de
+ressources. Une collection déjà enseignée ne reparcourt pas ses recettes à chaque
+actualisation d’habitant. `/reload` renouvelle cet index et réconcilie une fois
+les carnets ; aucune recette déjà apprise n’est retirée.
+
+**Discovery → Recettes** affiche les noms FR/EN des collections connues. L’accès
+au catalogue reste en haut, avant la liste, afin que les nouvelles collections
+ne repoussent pas le bouton d’ouverture. Les notifications beta.034 signalent
+les collections nouvellement ouvertes et regroupent les lots ; les reconnexions
+ne rejouent pas l’historique.
+
+Les fiches détaillées d’obtention, le musée visuel de tous les blocs, les gestes
+sans recette (commerce, oxydation, croissance…), quêtes et bounties restent à
+concevoir. Aucune recette artificielle ni expansion de dimension n’est ajoutée.
+
+## Compléter le catalogue
+
+Modifier les deux documents de référence, puis exécuter :
+
+```sh
+python3 scripts/compile_collections.py --write
+python3 scripts/compile_collections.py
+```
+
+Le contrôle compare les deux directions advancement/collection, les 2 042 recettes
+uniques, les 1 815 identifiants distincts, les amorces, les définitions serveur et
+les noms FR/EN. Il est intégré à `check` ; les tests Minecraft confrontent ensuite
+ces identifiants aux registres et recettes réellement chargés. Le générateur ne
+télécharge rien et ne modifie ni les recettes natives ni les documents sources.
+
+## Vérifications et livraison
+
+`./gradlew check build assemblePack assembleTestPack` avec les suites
+`collections,recipes,notifications,progression,cycle` réussit : **28 tests serveur**.
+La matrice exerce les **126 advancements**, les 66 voies de découverte des familles,
+les identifiants des registres réellement chargés, les limites bois/machines,
+les amorces du lit, de l’alambic, du fabricateur et de la brosse, l’isolation des
+habitants et la reprise des carnets beta.034. Les 2 042 recettes primaires sont
+comparées à l’ensemble des recettes vanilla chargées, sans identifiant manquant.
+
+Le parcours client natif final réussit sur le profil plat : achat réel du
+catalogue, familles enseignées par advancement sans ingrédients, consultation
+effective des plans de Shulker, transfert depuis la sixième rangée, vue opérateur
+et révocation, notifications, rendu FR/EN après rechargement terminé, puis
+véritables New Game+, mort/respawn et redémarrage du serveur. Les nouvelles
+collections et le reçu du catalogue sont conservés dans ces trois passages.
+Les captures sont dans `build/collections035-evidence`.
+
+L’assemblage final après les vérifications client utilise `assemblePack
+assembleTestPack -x check`, sans répéter les tests serveur déjà passés. Les classes
+du moteur serveur correspondent à celles vérifiées ; ZIP, 1 322 classes Sanctuary,
+67 définitions, traductions, dépendances imbriquées et profil facultatif ont été
+comparés aux builds. JEI conserve exactement son binaire `30.32.0-sanctuary.2`.
+
+- [Sanctuary-beta.035.mrpack](../build/Sanctuary-beta.035.mrpack), pack normal.
+- [Sanctuary-Test-beta.035.mrpack](../build/Sanctuary-Test-beta.035.mrpack), profil plat rapide.
+
+SHA-256 normal : `dfcf5927eaef183fe582b95454480849a08353561cb66d83a514d8a0274b5c60`.
+SHA-256 test : `f94f216b5b09c7ebeafea45a8d735978a4efc354aa245ffbfd993ee7e6db2b24`.
+Reçu : `build/collections035-artifact.json`. Journaux :
+`build/collections035-check.log`, `build/collections035-client-final.log` et
+`build/collections035-assemble-final.log`.
+
+Client et serveur beta.035 ensemble, sur Minecraft 26.3-pre-2 / Loader 0.19.5 /
+Fabric API 0.160.0+26.3. Les exports antérieurs sont inchangés. Aucun canal de
+distribution, instance Prism personnelle ou monde existant n’a été modifié.
diff --git a/docs/collections-minecraft-inventaire.md b/docs/collections-minecraft-inventaire.md
new file mode 100644
index 0000000..079c7a5
--- /dev/null
+++ b/docs/collections-minecraft-inventaire.md
@@ -0,0 +1,4886 @@
+# Collections Minecraft — inventaire exhaustif
+
+**Minecraft Java 26.3-pre-2 · Proposition du 14 septembre 2026**
+
+[Lire le document de conception et les règles](collections-minecraft.md). Cet inventaire fixe le classement principal ; les [définitions beta.035](collections-beta035.md) le matérialisent dans le pack.
+
+**2 042 recettes JSON**, **1 658 identifiants d’objets**, **1 286 identifiants de blocs**, soit **1 815 identifiants distincts** blocs/objets. Toutes les lignes sont issues de la version exacte ; leur attribution aux 67 collections est éditoriale.
+
+Les recettes et identifiants utilisent tous le namespace `minecraft:`. Chaque recette figure une seule fois. Un identifiant peut être à la fois un bloc et un objet ; « bloc sans objet homonyme » peut désigner un bloc représenté par un objet portant un autre identifiant, comme une culture.
+
+Le nombre de sorties explicites compte seulement les JSON ayant ce résultat nommé. **Zéro sortie explicite ne prouve pas l’absence de toute opération** : les réparations, ornements, copies ou résultats hérités de l’entrée sont indiqués à part. Cette colonne ne prouve pas non plus qu’un objet soit accessible en survie. Pour les méthodes d’obtention et objets techniques, lire la fiche de collection du document principal.
+
+Les résultats de potions incluent leur composant de potion. Les autres composants sont précisés lorsqu’ils figurent dans le résultat ; les résultats dynamiques dépendent de l’objet ou des ingrédients fournis. Les coûts et grilles complets restent ceux du JSON vanilla, non recopiés dans cette annexe.
+
+| Collection | Recettes | Identifiants blocs/objets |
+| --- | ---: | ---: |
+| [C01 — Bois et menuiserie](#c01) | 170 | 214 |
+| [C02 — Pierre et premiers outils](#c02) | 57 | 37 |
+| [C03 — Granite, diorite et andésite](#c03) | 45 | 21 |
+| [C04 — Ardoise des profondeurs](#c04) | 73 | 22 |
+| [C05 — Tuf](#c05) | 38 | 14 |
+| [C06 — Argile et briques](#c06) | 10 | 8 |
+| [C07 — Sables et grès](#c07) | 38 | 25 |
+| [C08 — Verre et vitrages](#c08) | 50 | 34 |
+| [C09 — Teintures](#c09) | 50 | 22 |
+| [C10 — Laine, literie et coussins](#c10) | 178 | 97 |
+| [C11 — Terres cuites](#c11) | 33 | 33 |
+| [C12 — Bétons](#c12) | 80 | 64 |
+| [C13 — Sols et mousses](#c13) | 13 | 22 |
+| [C14 — Neige et glace](#c14) | 4 | 9 |
+| [C15 — Améthyste et optique](#c15) | 3 | 10 |
+| [C16 — Spéléothèmes](#c16) | 1 | 2 |
+| [C17 — Soufre](#c17) | 36 | 16 |
+| [C18 — Cinabre](#c18) | 34 | 13 |
+| [C19 — Cuivre et chaudronnerie](#c19) | 89 | 78 |
+| [C20 — Cuivre ciré](#c20) | 116 | 60 |
+| [C21 — Fer et forge de base](#c21) | 34 | 35 |
+| [C22 — Or et dorure](#c22) | 26 | 17 |
+| [C23 — Diamants](#c23) | 16 | 14 |
+| [C24 — Netherite](#c24) | 18 | 17 |
+| [C25 — Charbon, feu et éclairage](#c25) | 28 | 45 |
+| [C26 — Redstone et mesure](#c26) | 20 | 21 |
+| [C27 — Machines et automatisation](#c27) | 11 | 13 |
+| [C28 — Rails et wagonnets](#c28) | 9 | 10 |
+| [C29 — Bateaux et radeaux](#c29) | 22 | 22 |
+| [C30 — Cartographie et orientation](#c30) | 7 | 22 |
+| [C31 — Papier, livres et rangement](#c31) | 28 | 28 |
+| [C32 — Enchantement et entretien](#c32) | 3 | 4 |
+| [C33 — Cultures et compost](#c33) | 9 | 22 |
+| [C34 — Cuisine](#c34) | 59 | 42 |
+| [C35 — Élevage, cuir et montures](#c35) | 15 | 15 |
+| [C36 — Animaux apprivoisés](#c36) | 2 | 2 |
+| [C37 — Abeilles et miel](#c37) | 5 | 6 |
+| [C38 — Pêche et seaux vivants](#c38) | 1 | 11 |
+| [C39 — Océan et monuments](#c39) | 21 | 27 |
+| [C40 — Coraux et récifs](#c40) | 0 | 40 |
+| [C41 — Botanique et renifleurs](#c41) | 1 | 121 |
+| [C42 — Champignons](#c42) | 0 | 6 |
+| [C43 — Archéologie et poterie](#c43) | 3 | 25 |
+| [C44 — Ornements d’armure](#c44) | 37 | 19 |
+| [C45 — Bannières et héraldique](#c45) | 40 | 43 |
+| [C46 — Arc et arbalète](#c46) | 4 | 8 |
+| [C47 — Explosifs et pyrotechnie](#c47) | 6 | 5 |
+| [C48 — Chambres des épreuves](#c48) | 2 | 9 |
+| [C49 — Musique et Allays](#c49) | 3 | 26 |
+| [C50 — Sculk et mémoire](#c50) | 2 | 8 |
+| [C51 — Jardin pâle et résine](#c51) | 13 | 9 |
+| [C52 — Architecture du Nether](#c52) | 82 | 56 |
+| [C53 — Bois carmin et biscornu](#c53) | 28 | 48 |
+| [C54 — Blazes et préparation](#c54) | 3 | 5 |
+| [C55 — Alchimie](#c55) | 286 | 16 |
+| [C56 — Wither et balises](#c56) | 1 | 5 |
+| [C57 — Obsidiennes et réapparition](#c57) | 1 | 3 |
+| [C58 — Arpenteurs](#c58) | 1 | 1 |
+| [C59 — Ghasts joyeux](#c59) | 33 | 18 |
+| [C60 — Architecture de l’End](#c60) | 20 | 14 |
+| [C61 — Boîtes de Shulker](#c61) | 17 | 18 |
+| [C62 — Dragon et cristaux](#c62) | 1 | 4 |
+| [C63 — Élytres et grand voyage](#c63) | 0 | 2 |
+| [C64 — Trophées et butins](#c64) | 0 | 11 |
+| [C65 — Blocs et objets techniques](#c65) | 0 | 112 |
+| [C66 — Villages et échanges](#c66) | 6 | 5 |
+| [C67 — Grenouilles et grelampes](#c67) | 0 | 4 |
+
+## C01
+
+**Bois et menuiserie · Woodworking** — [déclencheurs et règles](collections-minecraft.md#c01).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:acacia_button` | `minecraft:crafting_shapeless` | Bouton en acacia (`minecraft:acacia_button`) |
+| `minecraft:acacia_door` | `minecraft:crafting_shaped` | Porte en acacia (`minecraft:acacia_door`) × 3 |
+| `minecraft:acacia_fence` | `minecraft:crafting_shaped` | Barrière en acacia (`minecraft:acacia_fence`) × 3 |
+| `minecraft:acacia_fence_gate` | `minecraft:crafting_shaped` | Portillon en acacia (`minecraft:acacia_fence_gate`) |
+| `minecraft:acacia_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en acacia (`minecraft:acacia_hanging_sign`) × 6 |
+| `minecraft:acacia_planks` | `minecraft:crafting_shapeless` | Planches d'acacia (`minecraft:acacia_planks`) × 4 |
+| `minecraft:acacia_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en acacia (`minecraft:acacia_pressure_plate`) |
+| `minecraft:acacia_shelf` | `minecraft:crafting_shaped` | Étagère en acacia (`minecraft:acacia_shelf`) × 6 |
+| `minecraft:acacia_sign` | `minecraft:crafting_shaped` | Pancarte en acacia (`minecraft:acacia_sign`) × 3 |
+| `minecraft:acacia_slab` | `minecraft:crafting_shaped` | Dalle en acacia (`minecraft:acacia_slab`) × 6 |
+| `minecraft:acacia_stairs` | `minecraft:crafting_shaped` | Escalier en acacia (`minecraft:acacia_stairs`) × 4 |
+| `minecraft:acacia_trapdoor` | `minecraft:crafting_shaped` | Trappe en acacia (`minecraft:acacia_trapdoor`) × 2 |
+| `minecraft:acacia_wood` | `minecraft:crafting_shaped` | Bois d'acacia (`minecraft:acacia_wood`) × 3 |
+| `minecraft:bamboo_block` | `minecraft:crafting_shapeless` | Bloc de bambou (`minecraft:bamboo_block`) |
+| `minecraft:bamboo_button` | `minecraft:crafting_shapeless` | Bouton en bambou (`minecraft:bamboo_button`) |
+| `minecraft:bamboo_door` | `minecraft:crafting_shaped` | Porte en bambou (`minecraft:bamboo_door`) × 3 |
+| `minecraft:bamboo_fence` | `minecraft:crafting_shaped` | Barrière en bambou (`minecraft:bamboo_fence`) × 3 |
+| `minecraft:bamboo_fence_gate` | `minecraft:crafting_shaped` | Portillon en bambou (`minecraft:bamboo_fence_gate`) |
+| `minecraft:bamboo_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en bambou (`minecraft:bamboo_hanging_sign`) × 6 |
+| `minecraft:bamboo_mosaic` | `minecraft:crafting_shaped` | Mosaïque de bambou (`minecraft:bamboo_mosaic`) |
+| `minecraft:bamboo_mosaic_slab` | `minecraft:crafting_shaped` | Dalle de mosaïque de bambou (`minecraft:bamboo_mosaic_slab`) × 6 |
+| `minecraft:bamboo_mosaic_stairs` | `minecraft:crafting_shaped` | Escalier en mosaïque de bambou (`minecraft:bamboo_mosaic_stairs`) × 4 |
+| `minecraft:bamboo_planks` | `minecraft:crafting_shapeless` | Planches de bambou (`minecraft:bamboo_planks`) × 2 |
+| `minecraft:bamboo_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en bambou (`minecraft:bamboo_pressure_plate`) |
+| `minecraft:bamboo_shelf` | `minecraft:crafting_shaped` | Étagère en bambou (`minecraft:bamboo_shelf`) × 6 |
+| `minecraft:bamboo_sign` | `minecraft:crafting_shaped` | Pancarte en bambou (`minecraft:bamboo_sign`) × 3 |
+| `minecraft:bamboo_slab` | `minecraft:crafting_shaped` | Dalle en bambou (`minecraft:bamboo_slab`) × 6 |
+| `minecraft:bamboo_stairs` | `minecraft:crafting_shaped` | Escalier en bambou (`minecraft:bamboo_stairs`) × 4 |
+| `minecraft:bamboo_trapdoor` | `minecraft:crafting_shaped` | Trappe en bambou (`minecraft:bamboo_trapdoor`) × 2 |
+| `minecraft:barrel` | `minecraft:crafting_shaped` | Tonneau (`minecraft:barrel`) |
+| `minecraft:birch_button` | `minecraft:crafting_shapeless` | Bouton en bouleau (`minecraft:birch_button`) |
+| `minecraft:birch_door` | `minecraft:crafting_shaped` | Porte en bouleau (`minecraft:birch_door`) × 3 |
+| `minecraft:birch_fence` | `minecraft:crafting_shaped` | Barrière en bouleau (`minecraft:birch_fence`) × 3 |
+| `minecraft:birch_fence_gate` | `minecraft:crafting_shaped` | Portillon en bouleau (`minecraft:birch_fence_gate`) |
+| `minecraft:birch_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en bouleau (`minecraft:birch_hanging_sign`) × 6 |
+| `minecraft:birch_planks` | `minecraft:crafting_shapeless` | Planches de bouleau (`minecraft:birch_planks`) × 4 |
+| `minecraft:birch_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en bouleau (`minecraft:birch_pressure_plate`) |
+| `minecraft:birch_shelf` | `minecraft:crafting_shaped` | Étagère en bouleau (`minecraft:birch_shelf`) × 6 |
+| `minecraft:birch_sign` | `minecraft:crafting_shaped` | Pancarte en bouleau (`minecraft:birch_sign`) × 3 |
+| `minecraft:birch_slab` | `minecraft:crafting_shaped` | Dalle en bouleau (`minecraft:birch_slab`) × 6 |
+| `minecraft:birch_stairs` | `minecraft:crafting_shaped` | Escalier en bouleau (`minecraft:birch_stairs`) × 4 |
+| `minecraft:birch_trapdoor` | `minecraft:crafting_shaped` | Trappe en bouleau (`minecraft:birch_trapdoor`) × 2 |
+| `minecraft:birch_wood` | `minecraft:crafting_shaped` | Bois de bouleau (`minecraft:birch_wood`) × 3 |
+| `minecraft:bowl` | `minecraft:crafting_shaped` | Bol (`minecraft:bowl`) × 4 |
+| `minecraft:cherry_button` | `minecraft:crafting_shapeless` | Bouton en cerisier (`minecraft:cherry_button`) |
+| `minecraft:cherry_door` | `minecraft:crafting_shaped` | Porte en cerisier (`minecraft:cherry_door`) × 3 |
+| `minecraft:cherry_fence` | `minecraft:crafting_shaped` | Barrière en cerisier (`minecraft:cherry_fence`) × 3 |
+| `minecraft:cherry_fence_gate` | `minecraft:crafting_shaped` | Portillon en cerisier (`minecraft:cherry_fence_gate`) |
+| `minecraft:cherry_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en cerisier (`minecraft:cherry_hanging_sign`) × 6 |
+| `minecraft:cherry_planks` | `minecraft:crafting_shapeless` | Planches de cerisier (`minecraft:cherry_planks`) × 4 |
+| `minecraft:cherry_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en cerisier (`minecraft:cherry_pressure_plate`) |
+| `minecraft:cherry_shelf` | `minecraft:crafting_shaped` | Étagère en cerisier (`minecraft:cherry_shelf`) × 6 |
+| `minecraft:cherry_sign` | `minecraft:crafting_shaped` | Pancarte en cerisier (`minecraft:cherry_sign`) × 3 |
+| `minecraft:cherry_slab` | `minecraft:crafting_shaped` | Dalle en cerisier (`minecraft:cherry_slab`) × 6 |
+| `minecraft:cherry_stairs` | `minecraft:crafting_shaped` | Escalier en cerisier (`minecraft:cherry_stairs`) × 4 |
+| `minecraft:cherry_trapdoor` | `minecraft:crafting_shaped` | Trappe en cerisier (`minecraft:cherry_trapdoor`) × 2 |
+| `minecraft:cherry_wood` | `minecraft:crafting_shaped` | Bois de cerisier (`minecraft:cherry_wood`) × 3 |
+| `minecraft:chest` | `minecraft:crafting_shaped` | Coffre (`minecraft:chest`) |
+| `minecraft:crafting_table` | `minecraft:crafting_shaped` | Établi (`minecraft:crafting_table`) |
+| `minecraft:dark_oak_button` | `minecraft:crafting_shapeless` | Bouton en chêne noir (`minecraft:dark_oak_button`) |
+| `minecraft:dark_oak_door` | `minecraft:crafting_shaped` | Porte en chêne noir (`minecraft:dark_oak_door`) × 3 |
+| `minecraft:dark_oak_fence` | `minecraft:crafting_shaped` | Barrière en chêne noir (`minecraft:dark_oak_fence`) × 3 |
+| `minecraft:dark_oak_fence_gate` | `minecraft:crafting_shaped` | Portillon en chêne noir (`minecraft:dark_oak_fence_gate`) |
+| `minecraft:dark_oak_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en chêne noir (`minecraft:dark_oak_hanging_sign`) × 6 |
+| `minecraft:dark_oak_planks` | `minecraft:crafting_shapeless` | Planches de chêne noir (`minecraft:dark_oak_planks`) × 4 |
+| `minecraft:dark_oak_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en chêne noir (`minecraft:dark_oak_pressure_plate`) |
+| `minecraft:dark_oak_shelf` | `minecraft:crafting_shaped` | Étagère en chêne noir (`minecraft:dark_oak_shelf`) × 6 |
+| `minecraft:dark_oak_sign` | `minecraft:crafting_shaped` | Pancarte en chêne noir (`minecraft:dark_oak_sign`) × 3 |
+| `minecraft:dark_oak_slab` | `minecraft:crafting_shaped` | Dalle en chêne noir (`minecraft:dark_oak_slab`) × 6 |
+| `minecraft:dark_oak_stairs` | `minecraft:crafting_shaped` | Escalier en chêne noir (`minecraft:dark_oak_stairs`) × 4 |
+| `minecraft:dark_oak_trapdoor` | `minecraft:crafting_shaped` | Trappe en chêne noir (`minecraft:dark_oak_trapdoor`) × 2 |
+| `minecraft:dark_oak_wood` | `minecraft:crafting_shaped` | Bois de chêne noir (`minecraft:dark_oak_wood`) × 3 |
+| `minecraft:jungle_button` | `minecraft:crafting_shapeless` | Bouton en acajou (`minecraft:jungle_button`) |
+| `minecraft:jungle_door` | `minecraft:crafting_shaped` | Porte en acajou (`minecraft:jungle_door`) × 3 |
+| `minecraft:jungle_fence` | `minecraft:crafting_shaped` | Barrière en acajou (`minecraft:jungle_fence`) × 3 |
+| `minecraft:jungle_fence_gate` | `minecraft:crafting_shaped` | Portillon en acajou (`minecraft:jungle_fence_gate`) |
+| `minecraft:jungle_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en acajou (`minecraft:jungle_hanging_sign`) × 6 |
+| `minecraft:jungle_planks` | `minecraft:crafting_shapeless` | Planches d'acajou (`minecraft:jungle_planks`) × 4 |
+| `minecraft:jungle_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en acajou (`minecraft:jungle_pressure_plate`) |
+| `minecraft:jungle_shelf` | `minecraft:crafting_shaped` | Étagère en acajou (`minecraft:jungle_shelf`) × 6 |
+| `minecraft:jungle_sign` | `minecraft:crafting_shaped` | Pancarte en acajou (`minecraft:jungle_sign`) × 3 |
+| `minecraft:jungle_slab` | `minecraft:crafting_shaped` | Dalle en acajou (`minecraft:jungle_slab`) × 6 |
+| `minecraft:jungle_stairs` | `minecraft:crafting_shaped` | Escalier en acajou (`minecraft:jungle_stairs`) × 4 |
+| `minecraft:jungle_trapdoor` | `minecraft:crafting_shaped` | Trappe en acajou (`minecraft:jungle_trapdoor`) × 2 |
+| `minecraft:jungle_wood` | `minecraft:crafting_shaped` | Bois d'acajou (`minecraft:jungle_wood`) × 3 |
+| `minecraft:ladder` | `minecraft:crafting_shaped` | Échelle (`minecraft:ladder`) × 3 |
+| `minecraft:mangrove_button` | `minecraft:crafting_shapeless` | Bouton en palétuvier (`minecraft:mangrove_button`) |
+| `minecraft:mangrove_door` | `minecraft:crafting_shaped` | Porte en palétuvier (`minecraft:mangrove_door`) × 3 |
+| `minecraft:mangrove_fence` | `minecraft:crafting_shaped` | Barrière en palétuvier (`minecraft:mangrove_fence`) × 3 |
+| `minecraft:mangrove_fence_gate` | `minecraft:crafting_shaped` | Portillon en palétuvier (`minecraft:mangrove_fence_gate`) |
+| `minecraft:mangrove_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en palétuvier (`minecraft:mangrove_hanging_sign`) × 6 |
+| `minecraft:mangrove_planks` | `minecraft:crafting_shapeless` | Planches de palétuvier (`minecraft:mangrove_planks`) × 4 |
+| `minecraft:mangrove_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en palétuvier (`minecraft:mangrove_pressure_plate`) |
+| `minecraft:mangrove_shelf` | `minecraft:crafting_shaped` | Étagère en palétuvier (`minecraft:mangrove_shelf`) × 6 |
+| `minecraft:mangrove_sign` | `minecraft:crafting_shaped` | Pancarte en palétuvier (`minecraft:mangrove_sign`) × 3 |
+| `minecraft:mangrove_slab` | `minecraft:crafting_shaped` | Dalle en palétuvier (`minecraft:mangrove_slab`) × 6 |
+| `minecraft:mangrove_stairs` | `minecraft:crafting_shaped` | Escalier en palétuvier (`minecraft:mangrove_stairs`) × 4 |
+| `minecraft:mangrove_trapdoor` | `minecraft:crafting_shaped` | Trappe en palétuvier (`minecraft:mangrove_trapdoor`) × 2 |
+| `minecraft:mangrove_wood` | `minecraft:crafting_shaped` | Bois de palétuvier (`minecraft:mangrove_wood`) × 3 |
+| `minecraft:oak_button` | `minecraft:crafting_shapeless` | Bouton en chêne (`minecraft:oak_button`) |
+| `minecraft:oak_door` | `minecraft:crafting_shaped` | Porte en chêne (`minecraft:oak_door`) × 3 |
+| `minecraft:oak_fence` | `minecraft:crafting_shaped` | Barrière en chêne (`minecraft:oak_fence`) × 3 |
+| `minecraft:oak_fence_gate` | `minecraft:crafting_shaped` | Portillon en chêne (`minecraft:oak_fence_gate`) |
+| `minecraft:oak_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en chêne (`minecraft:oak_hanging_sign`) × 6 |
+| `minecraft:oak_planks` | `minecraft:crafting_shapeless` | Planches de chêne (`minecraft:oak_planks`) × 4 |
+| `minecraft:oak_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en chêne (`minecraft:oak_pressure_plate`) |
+| `minecraft:oak_shelf` | `minecraft:crafting_shaped` | Étagère en chêne (`minecraft:oak_shelf`) × 6 |
+| `minecraft:oak_sign` | `minecraft:crafting_shaped` | Pancarte en chêne (`minecraft:oak_sign`) × 3 |
+| `minecraft:oak_slab` | `minecraft:crafting_shaped` | Dalle en chêne (`minecraft:oak_slab`) × 6 |
+| `minecraft:oak_stairs` | `minecraft:crafting_shaped` | Escalier en chêne (`minecraft:oak_stairs`) × 4 |
+| `minecraft:oak_trapdoor` | `minecraft:crafting_shaped` | Trappe en chêne (`minecraft:oak_trapdoor`) × 2 |
+| `minecraft:oak_wood` | `minecraft:crafting_shaped` | Bois de chêne (`minecraft:oak_wood`) × 3 |
+| `minecraft:pale_oak_button` | `minecraft:crafting_shapeless` | Bouton en chêne pâle (`minecraft:pale_oak_button`) |
+| `minecraft:pale_oak_door` | `minecraft:crafting_shaped` | Porte en chêne pâle (`minecraft:pale_oak_door`) × 3 |
+| `minecraft:pale_oak_fence` | `minecraft:crafting_shaped` | Barrière en chêne pâle (`minecraft:pale_oak_fence`) × 3 |
+| `minecraft:pale_oak_fence_gate` | `minecraft:crafting_shaped` | Portillon en chêne pâle (`minecraft:pale_oak_fence_gate`) |
+| `minecraft:pale_oak_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en chêne pâle (`minecraft:pale_oak_hanging_sign`) × 6 |
+| `minecraft:pale_oak_planks` | `minecraft:crafting_shapeless` | Planches de chêne pâle (`minecraft:pale_oak_planks`) × 4 |
+| `minecraft:pale_oak_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en chêne pâle (`minecraft:pale_oak_pressure_plate`) |
+| `minecraft:pale_oak_shelf` | `minecraft:crafting_shaped` | Étagère en chêne pâle (`minecraft:pale_oak_shelf`) × 6 |
+| `minecraft:pale_oak_sign` | `minecraft:crafting_shaped` | Pancarte en chêne pâle (`minecraft:pale_oak_sign`) × 3 |
+| `minecraft:pale_oak_slab` | `minecraft:crafting_shaped` | Dalle en chêne pâle (`minecraft:pale_oak_slab`) × 6 |
+| `minecraft:pale_oak_stairs` | `minecraft:crafting_shaped` | Escalier en chêne pâle (`minecraft:pale_oak_stairs`) × 4 |
+| `minecraft:pale_oak_trapdoor` | `minecraft:crafting_shaped` | Trappe en chêne pâle (`minecraft:pale_oak_trapdoor`) × 2 |
+| `minecraft:pale_oak_wood` | `minecraft:crafting_shaped` | Bois de chêne pâle (`minecraft:pale_oak_wood`) × 3 |
+| `minecraft:poplar_button` | `minecraft:crafting_shapeless` | Bouton en peuplier (`minecraft:poplar_button`) |
+| `minecraft:poplar_door` | `minecraft:crafting_shaped` | Porte en peuplier (`minecraft:poplar_door`) × 3 |
+| `minecraft:poplar_fence` | `minecraft:crafting_shaped` | Barrière en peuplier (`minecraft:poplar_fence`) × 3 |
+| `minecraft:poplar_fence_gate` | `minecraft:crafting_shaped` | Portillon en peuplier (`minecraft:poplar_fence_gate`) |
+| `minecraft:poplar_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en peuplier (`minecraft:poplar_hanging_sign`) × 6 |
+| `minecraft:poplar_planks` | `minecraft:crafting_shapeless` | Planches de peuplier (`minecraft:poplar_planks`) × 4 |
+| `minecraft:poplar_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en peuplier (`minecraft:poplar_pressure_plate`) |
+| `minecraft:poplar_shelf` | `minecraft:crafting_shaped` | Étagère en peuplier (`minecraft:poplar_shelf`) × 6 |
+| `minecraft:poplar_sign` | `minecraft:crafting_shaped` | Pancarte en peuplier (`minecraft:poplar_sign`) × 3 |
+| `minecraft:poplar_slab` | `minecraft:crafting_shaped` | Dalle en peuplier (`minecraft:poplar_slab`) × 6 |
+| `minecraft:poplar_stairs` | `minecraft:crafting_shaped` | Escalier en peuplier (`minecraft:poplar_stairs`) × 4 |
+| `minecraft:poplar_trapdoor` | `minecraft:crafting_shaped` | Trappe en peuplier (`minecraft:poplar_trapdoor`) × 2 |
+| `minecraft:poplar_wood` | `minecraft:crafting_shaped` | Bois de peuplier (`minecraft:poplar_wood`) × 3 |
+| `minecraft:scaffolding` | `minecraft:crafting_shaped` | Échafaudage (`minecraft:scaffolding`) × 6 |
+| `minecraft:spruce_button` | `minecraft:crafting_shapeless` | Bouton en sapin (`minecraft:spruce_button`) |
+| `minecraft:spruce_door` | `minecraft:crafting_shaped` | Porte en sapin (`minecraft:spruce_door`) × 3 |
+| `minecraft:spruce_fence` | `minecraft:crafting_shaped` | Barrière en sapin (`minecraft:spruce_fence`) × 3 |
+| `minecraft:spruce_fence_gate` | `minecraft:crafting_shaped` | Portillon en sapin (`minecraft:spruce_fence_gate`) |
+| `minecraft:spruce_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue en sapin (`minecraft:spruce_hanging_sign`) × 6 |
+| `minecraft:spruce_planks` | `minecraft:crafting_shapeless` | Planches de sapin (`minecraft:spruce_planks`) × 4 |
+| `minecraft:spruce_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en sapin (`minecraft:spruce_pressure_plate`) |
+| `minecraft:spruce_shelf` | `minecraft:crafting_shaped` | Étagère en sapin (`minecraft:spruce_shelf`) × 6 |
+| `minecraft:spruce_sign` | `minecraft:crafting_shaped` | Pancarte en sapin (`minecraft:spruce_sign`) × 3 |
+| `minecraft:spruce_slab` | `minecraft:crafting_shaped` | Dalle en sapin (`minecraft:spruce_slab`) × 6 |
+| `minecraft:spruce_stairs` | `minecraft:crafting_shaped` | Escalier en sapin (`minecraft:spruce_stairs`) × 4 |
+| `minecraft:spruce_trapdoor` | `minecraft:crafting_shaped` | Trappe en sapin (`minecraft:spruce_trapdoor`) × 2 |
+| `minecraft:spruce_wood` | `minecraft:crafting_shaped` | Bois de sapin (`minecraft:spruce_wood`) × 3 |
+| `minecraft:stick` | `minecraft:crafting_shaped` | Bâton (`minecraft:stick`) × 4 |
+| `minecraft:stick_from_bamboo_item` | `minecraft:crafting_shaped` | Bâton (`minecraft:stick`) |
+| `minecraft:stripped_acacia_wood` | `minecraft:crafting_shaped` | Bois d'acacia écorcé (`minecraft:stripped_acacia_wood`) × 3 |
+| `minecraft:stripped_birch_wood` | `minecraft:crafting_shaped` | Bois de bouleau écorcé (`minecraft:stripped_birch_wood`) × 3 |
+| `minecraft:stripped_cherry_wood` | `minecraft:crafting_shaped` | Bois de cerisier écorcé (`minecraft:stripped_cherry_wood`) × 3 |
+| `minecraft:stripped_dark_oak_wood` | `minecraft:crafting_shaped` | Bois de chêne noir écorcé (`minecraft:stripped_dark_oak_wood`) × 3 |
+| `minecraft:stripped_jungle_wood` | `minecraft:crafting_shaped` | Bois d'acajou écorcé (`minecraft:stripped_jungle_wood`) × 3 |
+| `minecraft:stripped_mangrove_wood` | `minecraft:crafting_shaped` | Bois de palétuvier écorcé (`minecraft:stripped_mangrove_wood`) × 3 |
+| `minecraft:stripped_oak_wood` | `minecraft:crafting_shaped` | Bois de chêne écorcé (`minecraft:stripped_oak_wood`) × 3 |
+| `minecraft:stripped_pale_oak_wood` | `minecraft:crafting_shaped` | Bois de chêne pâle écorcé (`minecraft:stripped_pale_oak_wood`) × 3 |
+| `minecraft:stripped_poplar_wood` | `minecraft:crafting_shaped` | Bois de peuplier écorcé (`minecraft:stripped_poplar_wood`) × 3 |
+| `minecraft:stripped_spruce_wood` | `minecraft:crafting_shaped` | Bois de sapin écorcé (`minecraft:stripped_spruce_wood`) × 3 |
+| `minecraft:wooden_axe` | `minecraft:crafting_shaped` | Hache en bois (`minecraft:wooden_axe`) |
+| `minecraft:wooden_hoe` | `minecraft:crafting_shaped` | Houe en bois (`minecraft:wooden_hoe`) |
+| `minecraft:wooden_pickaxe` | `minecraft:crafting_shaped` | Pioche en bois (`minecraft:wooden_pickaxe`) |
+| `minecraft:wooden_shovel` | `minecraft:crafting_shaped` | Pelle en bois (`minecraft:wooden_shovel`) |
+| `minecraft:wooden_spear` | `minecraft:crafting_shaped` | Lance en bois (`minecraft:wooden_spear`) |
+| `minecraft:wooden_sword` | `minecraft:crafting_shaped` | Épée en bois (`minecraft:wooden_sword`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:acacia_button` | Bouton en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_door` | Porte en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_fence` | Barrière en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_fence_gate` | Portillon en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_hanging_sign` | Pancarte suspendue en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_log` | Bûche d'acacia | Bloc et objet | 0 |
+| `minecraft:acacia_planks` | Planches d'acacia | Bloc et objet | 1 |
+| `minecraft:acacia_pressure_plate` | Plaque de pression en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_shelf` | Étagère en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_sign` | Pancarte en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_slab` | Dalle en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_stairs` | Escalier en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_trapdoor` | Trappe en acacia | Bloc et objet | 1 |
+| `minecraft:acacia_wall_hanging_sign` | Pancarte suspendue en acacia murale | Bloc sans objet homonyme | 0 |
+| `minecraft:acacia_wall_sign` | Pancarte en acacia murale | Bloc sans objet homonyme | 0 |
+| `minecraft:acacia_wood` | Bois d'acacia | Bloc et objet | 1 |
+| `minecraft:bamboo` | Bambou | Bloc et objet | 0 |
+| `minecraft:bamboo_block` | Bloc de bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_button` | Bouton en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_door` | Porte en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_fence` | Barrière en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_fence_gate` | Portillon en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_hanging_sign` | Pancarte suspendue en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_mosaic` | Mosaïque de bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_mosaic_slab` | Dalle de mosaïque de bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_mosaic_stairs` | Escalier en mosaïque de bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_planks` | Planches de bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_pressure_plate` | Plaque de pression en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_shelf` | Étagère en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_sign` | Pancarte en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_slab` | Dalle en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_stairs` | Escalier en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_trapdoor` | Trappe en bambou | Bloc et objet | 1 |
+| `minecraft:bamboo_wall_hanging_sign` | Pancarte suspendue en bambou murale | Bloc sans objet homonyme | 0 |
+| `minecraft:bamboo_wall_sign` | Pancarte en bambou murale | Bloc sans objet homonyme | 0 |
+| `minecraft:barrel` | Tonneau | Bloc et objet | 1 |
+| `minecraft:birch_button` | Bouton en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_door` | Porte en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_fence` | Barrière en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_fence_gate` | Portillon en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_hanging_sign` | Pancarte suspendue en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_log` | Bûche de bouleau | Bloc et objet | 0 |
+| `minecraft:birch_planks` | Planches de bouleau | Bloc et objet | 1 |
+| `minecraft:birch_pressure_plate` | Plaque de pression en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_shelf` | Étagère en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_sign` | Pancarte en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_slab` | Dalle en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_stairs` | Escalier en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_trapdoor` | Trappe en bouleau | Bloc et objet | 1 |
+| `minecraft:birch_wall_hanging_sign` | Pancarte suspendue en bouleau murale | Bloc sans objet homonyme | 0 |
+| `minecraft:birch_wall_sign` | Pancarte en bouleau murale | Bloc sans objet homonyme | 0 |
+| `minecraft:birch_wood` | Bois de bouleau | Bloc et objet | 1 |
+| `minecraft:bowl` | Bol | Objet | 1 |
+| `minecraft:cherry_button` | Bouton en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_door` | Porte en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_fence` | Barrière en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_fence_gate` | Portillon en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_hanging_sign` | Pancarte suspendue en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_log` | Bûche de cerisier | Bloc et objet | 0 |
+| `minecraft:cherry_planks` | Planches de cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_pressure_plate` | Plaque de pression en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_shelf` | Étagère en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_sign` | Pancarte en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_slab` | Dalle en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_stairs` | Escalier en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_trapdoor` | Trappe en cerisier | Bloc et objet | 1 |
+| `minecraft:cherry_wall_hanging_sign` | Pancarte suspendue en cerisier murale | Bloc sans objet homonyme | 0 |
+| `minecraft:cherry_wall_sign` | Pancarte en cerisier murale | Bloc sans objet homonyme | 0 |
+| `minecraft:cherry_wood` | Bois de cerisier | Bloc et objet | 1 |
+| `minecraft:chest` | Coffre | Bloc et objet | 1 |
+| `minecraft:crafting_table` | Établi | Bloc et objet | 1 |
+| `minecraft:dark_oak_button` | Bouton en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_door` | Porte en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_fence` | Barrière en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_fence_gate` | Portillon en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_hanging_sign` | Pancarte suspendue en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_log` | Bûche de chêne noir | Bloc et objet | 0 |
+| `minecraft:dark_oak_planks` | Planches de chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_pressure_plate` | Plaque de pression en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_shelf` | Étagère en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_sign` | Pancarte en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_slab` | Dalle en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_stairs` | Escalier en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_trapdoor` | Trappe en chêne noir | Bloc et objet | 1 |
+| `minecraft:dark_oak_wall_hanging_sign` | Pancarte suspendue en chêne noir murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dark_oak_wall_sign` | Pancarte en chêne noir murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dark_oak_wood` | Bois de chêne noir | Bloc et objet | 1 |
+| `minecraft:jungle_button` | Bouton en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_door` | Porte en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_fence` | Barrière en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_fence_gate` | Portillon en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_hanging_sign` | Pancarte suspendue en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_log` | Bûche d'acajou | Bloc et objet | 0 |
+| `minecraft:jungle_planks` | Planches d'acajou | Bloc et objet | 1 |
+| `minecraft:jungle_pressure_plate` | Plaque de pression en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_shelf` | Étagère en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_sign` | Pancarte en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_slab` | Dalle en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_stairs` | Escalier en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_trapdoor` | Trappe en acajou | Bloc et objet | 1 |
+| `minecraft:jungle_wall_hanging_sign` | Pancarte suspendue en acajou murale | Bloc sans objet homonyme | 0 |
+| `minecraft:jungle_wall_sign` | Pancarte en acajou murale | Bloc sans objet homonyme | 0 |
+| `minecraft:jungle_wood` | Bois d'acajou | Bloc et objet | 1 |
+| `minecraft:ladder` | Échelle | Bloc et objet | 1 |
+| `minecraft:mangrove_button` | Bouton en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_door` | Porte en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_fence` | Barrière en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_fence_gate` | Portillon en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_hanging_sign` | Pancarte suspendue en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_log` | Bûche de palétuvier | Bloc et objet | 0 |
+| `minecraft:mangrove_planks` | Planches de palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_pressure_plate` | Plaque de pression en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_shelf` | Étagère en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_sign` | Pancarte en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_slab` | Dalle en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_stairs` | Escalier en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_trapdoor` | Trappe en palétuvier | Bloc et objet | 1 |
+| `minecraft:mangrove_wall_hanging_sign` | Pancarte suspendue en palétuvier murale | Bloc sans objet homonyme | 0 |
+| `minecraft:mangrove_wall_sign` | Pancarte en palétuvier murale | Bloc sans objet homonyme | 0 |
+| `minecraft:mangrove_wood` | Bois de palétuvier | Bloc et objet | 1 |
+| `minecraft:oak_button` | Bouton en chêne | Bloc et objet | 1 |
+| `minecraft:oak_door` | Porte en chêne | Bloc et objet | 1 |
+| `minecraft:oak_fence` | Barrière en chêne | Bloc et objet | 1 |
+| `minecraft:oak_fence_gate` | Portillon en chêne | Bloc et objet | 1 |
+| `minecraft:oak_hanging_sign` | Pancarte suspendue en chêne | Bloc et objet | 1 |
+| `minecraft:oak_log` | Bûche de chêne | Bloc et objet | 0 |
+| `minecraft:oak_planks` | Planches de chêne | Bloc et objet | 1 |
+| `minecraft:oak_pressure_plate` | Plaque de pression en chêne | Bloc et objet | 1 |
+| `minecraft:oak_shelf` | Étagère en chêne | Bloc et objet | 1 |
+| `minecraft:oak_sign` | Pancarte en chêne | Bloc et objet | 1 |
+| `minecraft:oak_slab` | Dalle en chêne | Bloc et objet | 1 |
+| `minecraft:oak_stairs` | Escalier en chêne | Bloc et objet | 1 |
+| `minecraft:oak_trapdoor` | Trappe en chêne | Bloc et objet | 1 |
+| `minecraft:oak_wall_hanging_sign` | Pancarte suspendue en chêne murale | Bloc sans objet homonyme | 0 |
+| `minecraft:oak_wall_sign` | Pancarte en chêne murale | Bloc sans objet homonyme | 0 |
+| `minecraft:oak_wood` | Bois de chêne | Bloc et objet | 1 |
+| `minecraft:pale_oak_button` | Bouton en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_door` | Porte en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_fence` | Barrière en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_fence_gate` | Portillon en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_hanging_sign` | Pancarte suspendue en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_log` | Bûche de chêne pâle | Bloc et objet | 0 |
+| `minecraft:pale_oak_planks` | Planches de chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_pressure_plate` | Plaque de pression en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_shelf` | Étagère en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_sign` | Pancarte en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_slab` | Dalle en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_stairs` | Escalier en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_trapdoor` | Trappe en chêne pâle | Bloc et objet | 1 |
+| `minecraft:pale_oak_wall_hanging_sign` | Pancarte suspendue en chêne pâle murale | Bloc sans objet homonyme | 0 |
+| `minecraft:pale_oak_wall_sign` | Pancarte en chêne pâle murale | Bloc sans objet homonyme | 0 |
+| `minecraft:pale_oak_wood` | Bois de chêne pâle | Bloc et objet | 1 |
+| `minecraft:petrified_oak_slab` | Dalle en chêne pétrifiée | Bloc et objet | 0 |
+| `minecraft:poplar_button` | Bouton en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_door` | Porte en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_fence` | Barrière en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_fence_gate` | Portillon en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_hanging_sign` | Pancarte suspendue en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_log` | Bûche de peuplier | Bloc et objet | 0 |
+| `minecraft:poplar_planks` | Planches de peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_pressure_plate` | Plaque de pression en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_shelf` | Étagère en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_sign` | Pancarte en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_slab` | Dalle en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_stairs` | Escalier en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_trapdoor` | Trappe en peuplier | Bloc et objet | 1 |
+| `minecraft:poplar_wall_hanging_sign` | Pancarte suspendue en peuplier murale | Bloc sans objet homonyme | 0 |
+| `minecraft:poplar_wall_sign` | Pancarte en peuplier murale | Bloc sans objet homonyme | 0 |
+| `minecraft:poplar_wood` | Bois de peuplier | Bloc et objet | 1 |
+| `minecraft:scaffolding` | Échafaudage | Bloc et objet | 1 |
+| `minecraft:spruce_button` | Bouton en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_door` | Porte en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_fence` | Barrière en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_fence_gate` | Portillon en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_hanging_sign` | Pancarte suspendue en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_log` | Bûche de sapin | Bloc et objet | 0 |
+| `minecraft:spruce_planks` | Planches de sapin | Bloc et objet | 1 |
+| `minecraft:spruce_pressure_plate` | Plaque de pression en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_shelf` | Étagère en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_sign` | Pancarte en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_slab` | Dalle en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_stairs` | Escalier en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_trapdoor` | Trappe en sapin | Bloc et objet | 1 |
+| `minecraft:spruce_wall_hanging_sign` | Pancarte suspendue en sapin murale | Bloc sans objet homonyme | 0 |
+| `minecraft:spruce_wall_sign` | Pancarte en sapin murale | Bloc sans objet homonyme | 0 |
+| `minecraft:spruce_wood` | Bois de sapin | Bloc et objet | 1 |
+| `minecraft:stick` | Bâton | Objet | 2 |
+| `minecraft:stripped_acacia_log` | Bûche d'acacia écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_acacia_wood` | Bois d'acacia écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_bamboo_block` | Bloc de bambou écorcé | Bloc et objet | 0 |
+| `minecraft:stripped_birch_log` | Bûche de bouleau écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_birch_wood` | Bois de bouleau écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_cherry_log` | Bûche de cerisier écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_cherry_wood` | Bois de cerisier écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_dark_oak_log` | Bûche de chêne noir écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_dark_oak_wood` | Bois de chêne noir écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_jungle_log` | Bûche d'acajou écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_jungle_wood` | Bois d'acajou écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_mangrove_log` | Bûche de palétuvier écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_mangrove_wood` | Bois de palétuvier écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_oak_log` | Bûche de chêne écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_oak_wood` | Bois de chêne écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_pale_oak_log` | Bûche de chêne pâle écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_pale_oak_wood` | Bois de chêne pâle écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_poplar_log` | Bûche de peuplier écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_poplar_wood` | Bois de peuplier écorcé | Bloc et objet | 1 |
+| `minecraft:stripped_spruce_log` | Bûche de sapin écorcée | Bloc et objet | 0 |
+| `minecraft:stripped_spruce_wood` | Bois de sapin écorcé | Bloc et objet | 1 |
+| `minecraft:wooden_axe` | Hache en bois | Objet | 1 |
+| `minecraft:wooden_hoe` | Houe en bois | Objet | 1 |
+| `minecraft:wooden_pickaxe` | Pioche en bois | Objet | 1 |
+| `minecraft:wooden_shovel` | Pelle en bois | Objet | 1 |
+| `minecraft:wooden_spear` | Lance en bois | Objet | 1 |
+| `minecraft:wooden_sword` | Épée en bois | Objet | 1 |
+
+## C02
+
+**Pierre et premiers outils · Stoneworking** — [déclencheurs et règles](collections-minecraft.md#c02).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_stone_bricks` | `minecraft:crafting_shaped` | Pierre sculptée (`minecraft:chiseled_stone_bricks`) |
+| `minecraft:chiseled_stone_bricks_from_stone_bricks_stonecutting` | `minecraft:stonecutting` | Pierre sculptée (`minecraft:chiseled_stone_bricks`) |
+| `minecraft:chiseled_stone_bricks_from_stone_stonecutting` | `minecraft:stonecutting` | Pierre sculptée (`minecraft:chiseled_stone_bricks`) |
+| `minecraft:cobblestone_from_stone_stonecutting` | `minecraft:stonecutting` | Pierres (`minecraft:cobblestone`) |
+| `minecraft:cobblestone_slab` | `minecraft:crafting_shaped` | Dalle de pierres (`minecraft:cobblestone_slab`) × 6 |
+| `minecraft:cobblestone_slab_from_cobblestone_stonecutting` | `minecraft:stonecutting` | Dalle de pierres (`minecraft:cobblestone_slab`) × 2 |
+| `minecraft:cobblestone_slab_from_stone_stonecutting` | `minecraft:stonecutting` | Dalle de pierres (`minecraft:cobblestone_slab`) × 2 |
+| `minecraft:cobblestone_stairs` | `minecraft:crafting_shaped` | Escalier en pierre (`minecraft:cobblestone_stairs`) × 4 |
+| `minecraft:cobblestone_stairs_from_cobblestone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre (`minecraft:cobblestone_stairs`) |
+| `minecraft:cobblestone_stairs_from_stone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre (`minecraft:cobblestone_stairs`) |
+| `minecraft:cobblestone_wall` | `minecraft:crafting_shaped` | Muret de pierres (`minecraft:cobblestone_wall`) × 6 |
+| `minecraft:cobblestone_wall_from_cobblestone_stonecutting` | `minecraft:stonecutting` | Muret de pierres (`minecraft:cobblestone_wall`) |
+| `minecraft:cobblestone_wall_from_stone_stonecutting` | `minecraft:stonecutting` | Muret de pierres (`minecraft:cobblestone_wall`) |
+| `minecraft:cracked_stone_bricks` | `minecraft:smelting` | Pierre taillée craquelée (`minecraft:cracked_stone_bricks`) |
+| `minecraft:furnace` | `minecraft:crafting_shaped` | Fourneau (`minecraft:furnace`) |
+| `minecraft:mossy_cobblestone_from_moss_block` | `minecraft:crafting_shapeless` | Pierres moussues (`minecraft:mossy_cobblestone`) |
+| `minecraft:mossy_cobblestone_from_vine` | `minecraft:crafting_shapeless` | Pierres moussues (`minecraft:mossy_cobblestone`) |
+| `minecraft:mossy_cobblestone_slab` | `minecraft:crafting_shaped` | Dalle de pierres moussues (`minecraft:mossy_cobblestone_slab`) × 6 |
+| `minecraft:mossy_cobblestone_slab_from_mossy_cobblestone_stonecutting` | `minecraft:stonecutting` | Dalle de pierres moussues (`minecraft:mossy_cobblestone_slab`) × 2 |
+| `minecraft:mossy_cobblestone_stairs` | `minecraft:crafting_shaped` | Escalier en pierres moussues (`minecraft:mossy_cobblestone_stairs`) × 4 |
+| `minecraft:mossy_cobblestone_stairs_from_mossy_cobblestone_stonecutting` | `minecraft:stonecutting` | Escalier en pierres moussues (`minecraft:mossy_cobblestone_stairs`) |
+| `minecraft:mossy_cobblestone_wall` | `minecraft:crafting_shaped` | Muret de pierres moussues (`minecraft:mossy_cobblestone_wall`) × 6 |
+| `minecraft:mossy_cobblestone_wall_from_mossy_cobblestone_stonecutting` | `minecraft:stonecutting` | Muret de pierres moussues (`minecraft:mossy_cobblestone_wall`) |
+| `minecraft:mossy_stone_brick_slab` | `minecraft:crafting_shaped` | Dalle de pierre taillée moussue (`minecraft:mossy_stone_brick_slab`) × 6 |
+| `minecraft:mossy_stone_brick_slab_from_mossy_stone_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de pierre taillée moussue (`minecraft:mossy_stone_brick_slab`) × 2 |
+| `minecraft:mossy_stone_brick_stairs` | `minecraft:crafting_shaped` | Escalier en pierre taillée moussue (`minecraft:mossy_stone_brick_stairs`) × 4 |
+| `minecraft:mossy_stone_brick_stairs_from_mossy_stone_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en pierre taillée moussue (`minecraft:mossy_stone_brick_stairs`) |
+| `minecraft:mossy_stone_brick_wall` | `minecraft:crafting_shaped` | Muret de pierre taillée moussue (`minecraft:mossy_stone_brick_wall`) × 6 |
+| `minecraft:mossy_stone_brick_wall_from_mossy_stone_bricks_stonecutting` | `minecraft:stonecutting` | Muret de pierre taillée moussue (`minecraft:mossy_stone_brick_wall`) |
+| `minecraft:mossy_stone_bricks_from_moss_block` | `minecraft:crafting_shapeless` | Pierre taillée moussue (`minecraft:mossy_stone_bricks`) |
+| `minecraft:mossy_stone_bricks_from_vine` | `minecraft:crafting_shapeless` | Pierre taillée moussue (`minecraft:mossy_stone_bricks`) |
+| `minecraft:smooth_stone` | `minecraft:smelting` | Roche lisse (`minecraft:smooth_stone`) |
+| `minecraft:smooth_stone_slab` | `minecraft:crafting_shaped` | Dalle de roche lisse (`minecraft:smooth_stone_slab`) × 6 |
+| `minecraft:smooth_stone_slab_from_smooth_stone_stonecutting` | `minecraft:stonecutting` | Dalle de roche lisse (`minecraft:smooth_stone_slab`) × 2 |
+| `minecraft:stone` | `minecraft:smelting` | Roche (`minecraft:stone`) |
+| `minecraft:stone_axe` | `minecraft:crafting_shaped` | Hache en pierre (`minecraft:stone_axe`) |
+| `minecraft:stone_brick_slab` | `minecraft:crafting_shaped` | Dalle de pierre taillée (`minecraft:stone_brick_slab`) × 6 |
+| `minecraft:stone_brick_slab_from_stone_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de pierre taillée (`minecraft:stone_brick_slab`) × 2 |
+| `minecraft:stone_brick_slab_from_stone_stonecutting` | `minecraft:stonecutting` | Dalle de pierre taillée (`minecraft:stone_brick_slab`) × 2 |
+| `minecraft:stone_brick_stairs` | `minecraft:crafting_shaped` | Escalier en pierre taillée (`minecraft:stone_brick_stairs`) × 4 |
+| `minecraft:stone_brick_stairs_from_stone_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en pierre taillée (`minecraft:stone_brick_stairs`) |
+| `minecraft:stone_brick_stairs_from_stone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre taillée (`minecraft:stone_brick_stairs`) |
+| `minecraft:stone_brick_wall` | `minecraft:crafting_shaped` | Muret de pierre taillée (`minecraft:stone_brick_wall`) × 6 |
+| `minecraft:stone_brick_wall_from_stone_bricks_stonecutting` | `minecraft:stonecutting` | Muret de pierre taillée (`minecraft:stone_brick_wall`) |
+| `minecraft:stone_brick_wall_from_stone_stonecutting` | `minecraft:stonecutting` | Muret de pierre taillée (`minecraft:stone_brick_wall`) |
+| `minecraft:stone_bricks` | `minecraft:crafting_shaped` | Pierre taillée (`minecraft:stone_bricks`) × 4 |
+| `minecraft:stone_bricks_from_stone_stonecutting` | `minecraft:stonecutting` | Pierre taillée (`minecraft:stone_bricks`) |
+| `minecraft:stone_hoe` | `minecraft:crafting_shaped` | Houe en pierre (`minecraft:stone_hoe`) |
+| `minecraft:stone_pickaxe` | `minecraft:crafting_shaped` | Pioche en pierre (`minecraft:stone_pickaxe`) |
+| `minecraft:stone_shovel` | `minecraft:crafting_shaped` | Pelle en pierre (`minecraft:stone_shovel`) |
+| `minecraft:stone_slab` | `minecraft:crafting_shaped` | Dalle de roche (`minecraft:stone_slab`) × 6 |
+| `minecraft:stone_slab_from_stone_stonecutting` | `minecraft:stonecutting` | Dalle de roche (`minecraft:stone_slab`) × 2 |
+| `minecraft:stone_spear` | `minecraft:crafting_shaped` | Lance en pierre (`minecraft:stone_spear`) |
+| `minecraft:stone_stairs` | `minecraft:crafting_shaped` | Escalier en roche (`minecraft:stone_stairs`) × 4 |
+| `minecraft:stone_stairs_from_stone_stonecutting` | `minecraft:stonecutting` | Escalier en roche (`minecraft:stone_stairs`) |
+| `minecraft:stone_sword` | `minecraft:crafting_shaped` | Épée en pierre (`minecraft:stone_sword`) |
+| `minecraft:stonecutter` | `minecraft:crafting_shaped` | Tailleur de pierre (`minecraft:stonecutter`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_stone_bricks` | Pierre sculptée | Bloc et objet | 3 |
+| `minecraft:cobblestone` | Pierres | Bloc et objet | 1 |
+| `minecraft:cobblestone_slab` | Dalle de pierres | Bloc et objet | 3 |
+| `minecraft:cobblestone_stairs` | Escalier en pierre | Bloc et objet | 3 |
+| `minecraft:cobblestone_wall` | Muret de pierres | Bloc et objet | 3 |
+| `minecraft:cracked_stone_bricks` | Pierre taillée craquelée | Bloc et objet | 1 |
+| `minecraft:furnace` | Fourneau | Bloc et objet | 1 |
+| `minecraft:infested_chiseled_stone_bricks` | Pierre sculptée infestée | Bloc et objet | 0 |
+| `minecraft:infested_cobblestone` | Pierres infestées | Bloc et objet | 0 |
+| `minecraft:infested_cracked_stone_bricks` | Pierre taillée craquelée infestée | Bloc et objet | 0 |
+| `minecraft:infested_mossy_stone_bricks` | Pierre taillée moussue infestée | Bloc et objet | 0 |
+| `minecraft:infested_stone` | Roche infestée | Bloc et objet | 0 |
+| `minecraft:infested_stone_bricks` | Pierre taillée infestée | Bloc et objet | 0 |
+| `minecraft:mossy_cobblestone` | Pierres moussues | Bloc et objet | 2 |
+| `minecraft:mossy_cobblestone_slab` | Dalle de pierres moussues | Bloc et objet | 2 |
+| `minecraft:mossy_cobblestone_stairs` | Escalier en pierres moussues | Bloc et objet | 2 |
+| `minecraft:mossy_cobblestone_wall` | Muret de pierres moussues | Bloc et objet | 2 |
+| `minecraft:mossy_stone_brick_slab` | Dalle de pierre taillée moussue | Bloc et objet | 2 |
+| `minecraft:mossy_stone_brick_stairs` | Escalier en pierre taillée moussue | Bloc et objet | 2 |
+| `minecraft:mossy_stone_brick_wall` | Muret de pierre taillée moussue | Bloc et objet | 2 |
+| `minecraft:mossy_stone_bricks` | Pierre taillée moussue | Bloc et objet | 2 |
+| `minecraft:smooth_stone` | Roche lisse | Bloc et objet | 1 |
+| `minecraft:smooth_stone_slab` | Dalle de roche lisse | Bloc et objet | 2 |
+| `minecraft:stone` | Roche | Bloc et objet | 1 |
+| `minecraft:stone_axe` | Hache en pierre | Objet | 1 |
+| `minecraft:stone_brick_slab` | Dalle de pierre taillée | Bloc et objet | 3 |
+| `minecraft:stone_brick_stairs` | Escalier en pierre taillée | Bloc et objet | 3 |
+| `minecraft:stone_brick_wall` | Muret de pierre taillée | Bloc et objet | 3 |
+| `minecraft:stone_bricks` | Pierre taillée | Bloc et objet | 2 |
+| `minecraft:stone_hoe` | Houe en pierre | Objet | 1 |
+| `minecraft:stone_pickaxe` | Pioche en pierre | Objet | 1 |
+| `minecraft:stone_shovel` | Pelle en pierre | Objet | 1 |
+| `minecraft:stone_slab` | Dalle de roche | Bloc et objet | 2 |
+| `minecraft:stone_spear` | Lance en pierre | Objet | 1 |
+| `minecraft:stone_stairs` | Escalier en roche | Bloc et objet | 2 |
+| `minecraft:stone_sword` | Épée en pierre | Objet | 1 |
+| `minecraft:stonecutter` | Tailleur de pierre | Bloc et objet | 1 |
+
+## C03
+
+**Granite, diorite et andésite · Decorative Stone** — [déclencheurs et règles](collections-minecraft.md#c03).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:andesite` | `minecraft:crafting_shapeless` | Andésite (`minecraft:andesite`) × 2 |
+| `minecraft:andesite_slab` | `minecraft:crafting_shaped` | Dalle d'andésite (`minecraft:andesite_slab`) × 6 |
+| `minecraft:andesite_slab_from_andesite_stonecutting` | `minecraft:stonecutting` | Dalle d'andésite (`minecraft:andesite_slab`) × 2 |
+| `minecraft:andesite_stairs` | `minecraft:crafting_shaped` | Escalier en andésite (`minecraft:andesite_stairs`) × 4 |
+| `minecraft:andesite_stairs_from_andesite_stonecutting` | `minecraft:stonecutting` | Escalier en andésite (`minecraft:andesite_stairs`) |
+| `minecraft:andesite_wall` | `minecraft:crafting_shaped` | Muret d'andésite (`minecraft:andesite_wall`) × 6 |
+| `minecraft:andesite_wall_from_andesite_stonecutting` | `minecraft:stonecutting` | Muret d'andésite (`minecraft:andesite_wall`) |
+| `minecraft:diorite` | `minecraft:crafting_shaped` | Diorite (`minecraft:diorite`) × 2 |
+| `minecraft:diorite_slab` | `minecraft:crafting_shaped` | Dalle de diorite (`minecraft:diorite_slab`) × 6 |
+| `minecraft:diorite_slab_from_diorite_stonecutting` | `minecraft:stonecutting` | Dalle de diorite (`minecraft:diorite_slab`) × 2 |
+| `minecraft:diorite_stairs` | `minecraft:crafting_shaped` | Escalier en diorite (`minecraft:diorite_stairs`) × 4 |
+| `minecraft:diorite_stairs_from_diorite_stonecutting` | `minecraft:stonecutting` | Escalier en diorite (`minecraft:diorite_stairs`) |
+| `minecraft:diorite_wall` | `minecraft:crafting_shaped` | Muret de diorite (`minecraft:diorite_wall`) × 6 |
+| `minecraft:diorite_wall_from_diorite_stonecutting` | `minecraft:stonecutting` | Muret de diorite (`minecraft:diorite_wall`) |
+| `minecraft:granite` | `minecraft:crafting_shapeless` | Granite (`minecraft:granite`) |
+| `minecraft:granite_slab` | `minecraft:crafting_shaped` | Dalle de granite (`minecraft:granite_slab`) × 6 |
+| `minecraft:granite_slab_from_granite_stonecutting` | `minecraft:stonecutting` | Dalle de granite (`minecraft:granite_slab`) × 2 |
+| `minecraft:granite_stairs` | `minecraft:crafting_shaped` | Escalier en granite (`minecraft:granite_stairs`) × 4 |
+| `minecraft:granite_stairs_from_granite_stonecutting` | `minecraft:stonecutting` | Escalier en granite (`minecraft:granite_stairs`) |
+| `minecraft:granite_wall` | `minecraft:crafting_shaped` | Muret de granite (`minecraft:granite_wall`) × 6 |
+| `minecraft:granite_wall_from_granite_stonecutting` | `minecraft:stonecutting` | Muret de granite (`minecraft:granite_wall`) |
+| `minecraft:polished_andesite` | `minecraft:crafting_shaped` | Andésite polie (`minecraft:polished_andesite`) × 4 |
+| `minecraft:polished_andesite_from_andesite_stonecutting` | `minecraft:stonecutting` | Andésite polie (`minecraft:polished_andesite`) |
+| `minecraft:polished_andesite_slab` | `minecraft:crafting_shaped` | Dalle d'andésite polie (`minecraft:polished_andesite_slab`) × 6 |
+| `minecraft:polished_andesite_slab_from_andesite_stonecutting` | `minecraft:stonecutting` | Dalle d'andésite polie (`minecraft:polished_andesite_slab`) × 2 |
+| `minecraft:polished_andesite_slab_from_polished_andesite_stonecutting` | `minecraft:stonecutting` | Dalle d'andésite polie (`minecraft:polished_andesite_slab`) × 2 |
+| `minecraft:polished_andesite_stairs` | `minecraft:crafting_shaped` | Escalier en andésite polie (`minecraft:polished_andesite_stairs`) × 4 |
+| `minecraft:polished_andesite_stairs_from_andesite_stonecutting` | `minecraft:stonecutting` | Escalier en andésite polie (`minecraft:polished_andesite_stairs`) |
+| `minecraft:polished_andesite_stairs_from_polished_andesite_stonecutting` | `minecraft:stonecutting` | Escalier en andésite polie (`minecraft:polished_andesite_stairs`) |
+| `minecraft:polished_diorite` | `minecraft:crafting_shaped` | Diorite polie (`minecraft:polished_diorite`) × 4 |
+| `minecraft:polished_diorite_from_diorite_stonecutting` | `minecraft:stonecutting` | Diorite polie (`minecraft:polished_diorite`) |
+| `minecraft:polished_diorite_slab` | `minecraft:crafting_shaped` | Dalle de diorite polie (`minecraft:polished_diorite_slab`) × 6 |
+| `minecraft:polished_diorite_slab_from_diorite_stonecutting` | `minecraft:stonecutting` | Dalle de diorite polie (`minecraft:polished_diorite_slab`) × 2 |
+| `minecraft:polished_diorite_slab_from_polished_diorite_stonecutting` | `minecraft:stonecutting` | Dalle de diorite polie (`minecraft:polished_diorite_slab`) × 2 |
+| `minecraft:polished_diorite_stairs` | `minecraft:crafting_shaped` | Escalier en diorite polie (`minecraft:polished_diorite_stairs`) × 4 |
+| `minecraft:polished_diorite_stairs_from_diorite_stonecutting` | `minecraft:stonecutting` | Escalier en diorite polie (`minecraft:polished_diorite_stairs`) |
+| `minecraft:polished_diorite_stairs_from_polished_diorite_stonecutting` | `minecraft:stonecutting` | Escalier en diorite polie (`minecraft:polished_diorite_stairs`) |
+| `minecraft:polished_granite` | `minecraft:crafting_shaped` | Granite poli (`minecraft:polished_granite`) × 4 |
+| `minecraft:polished_granite_from_granite_stonecutting` | `minecraft:stonecutting` | Granite poli (`minecraft:polished_granite`) |
+| `minecraft:polished_granite_slab` | `minecraft:crafting_shaped` | Dalle de granite poli (`minecraft:polished_granite_slab`) × 6 |
+| `minecraft:polished_granite_slab_from_granite_stonecutting` | `minecraft:stonecutting` | Dalle de granite poli (`minecraft:polished_granite_slab`) × 2 |
+| `minecraft:polished_granite_slab_from_polished_granite_stonecutting` | `minecraft:stonecutting` | Dalle de granite poli (`minecraft:polished_granite_slab`) × 2 |
+| `minecraft:polished_granite_stairs` | `minecraft:crafting_shaped` | Escalier en granite poli (`minecraft:polished_granite_stairs`) × 4 |
+| `minecraft:polished_granite_stairs_from_granite_stonecutting` | `minecraft:stonecutting` | Escalier en granite poli (`minecraft:polished_granite_stairs`) |
+| `minecraft:polished_granite_stairs_from_polished_granite_stonecutting` | `minecraft:stonecutting` | Escalier en granite poli (`minecraft:polished_granite_stairs`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:andesite` | Andésite | Bloc et objet | 1 |
+| `minecraft:andesite_slab` | Dalle d'andésite | Bloc et objet | 2 |
+| `minecraft:andesite_stairs` | Escalier en andésite | Bloc et objet | 2 |
+| `minecraft:andesite_wall` | Muret d'andésite | Bloc et objet | 2 |
+| `minecraft:diorite` | Diorite | Bloc et objet | 1 |
+| `minecraft:diorite_slab` | Dalle de diorite | Bloc et objet | 2 |
+| `minecraft:diorite_stairs` | Escalier en diorite | Bloc et objet | 2 |
+| `minecraft:diorite_wall` | Muret de diorite | Bloc et objet | 2 |
+| `minecraft:granite` | Granite | Bloc et objet | 1 |
+| `minecraft:granite_slab` | Dalle de granite | Bloc et objet | 2 |
+| `minecraft:granite_stairs` | Escalier en granite | Bloc et objet | 2 |
+| `minecraft:granite_wall` | Muret de granite | Bloc et objet | 2 |
+| `minecraft:polished_andesite` | Andésite polie | Bloc et objet | 2 |
+| `minecraft:polished_andesite_slab` | Dalle d'andésite polie | Bloc et objet | 3 |
+| `minecraft:polished_andesite_stairs` | Escalier en andésite polie | Bloc et objet | 3 |
+| `minecraft:polished_diorite` | Diorite polie | Bloc et objet | 2 |
+| `minecraft:polished_diorite_slab` | Dalle de diorite polie | Bloc et objet | 3 |
+| `minecraft:polished_diorite_stairs` | Escalier en diorite polie | Bloc et objet | 3 |
+| `minecraft:polished_granite` | Granite poli | Bloc et objet | 2 |
+| `minecraft:polished_granite_slab` | Dalle de granite poli | Bloc et objet | 3 |
+| `minecraft:polished_granite_stairs` | Escalier en granite poli | Bloc et objet | 3 |
+
+## C04
+
+**Ardoise des profondeurs · Deepslate** — [déclencheurs et règles](collections-minecraft.md#c04).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_deepslate` | `minecraft:crafting_shaped` | Ardoise des abîmes sculptée (`minecraft:chiseled_deepslate`) |
+| `minecraft:chiseled_deepslate_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes sculptée (`minecraft:chiseled_deepslate`) |
+| `minecraft:chiseled_deepslate_from_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes sculptée (`minecraft:chiseled_deepslate`) |
+| `minecraft:cobbled_deepslate_from_deepslate_stonecutting` | `minecraft:stonecutting` | Pierres des abîmes (`minecraft:cobbled_deepslate`) |
+| `minecraft:cobbled_deepslate_slab` | `minecraft:crafting_shaped` | Dalle de pierres des abîmes (`minecraft:cobbled_deepslate_slab`) × 6 |
+| `minecraft:cobbled_deepslate_slab_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle de pierres des abîmes (`minecraft:cobbled_deepslate_slab`) × 2 |
+| `minecraft:cobbled_deepslate_slab_from_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle de pierres des abîmes (`minecraft:cobbled_deepslate_slab`) × 2 |
+| `minecraft:cobbled_deepslate_stairs` | `minecraft:crafting_shaped` | Escalier en pierres des abîmes (`minecraft:cobbled_deepslate_stairs`) × 4 |
+| `minecraft:cobbled_deepslate_stairs_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en pierres des abîmes (`minecraft:cobbled_deepslate_stairs`) |
+| `minecraft:cobbled_deepslate_stairs_from_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en pierres des abîmes (`minecraft:cobbled_deepslate_stairs`) |
+| `minecraft:cobbled_deepslate_wall` | `minecraft:crafting_shaped` | Muret de pierres des abîmes (`minecraft:cobbled_deepslate_wall`) × 6 |
+| `minecraft:cobbled_deepslate_wall_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Muret de pierres des abîmes (`minecraft:cobbled_deepslate_wall`) |
+| `minecraft:cobbled_deepslate_wall_from_deepslate_stonecutting` | `minecraft:stonecutting` | Muret de pierres des abîmes (`minecraft:cobbled_deepslate_wall`) |
+| `minecraft:cracked_deepslate_bricks` | `minecraft:smelting` | Ardoise des abîmes taillée craquelée (`minecraft:cracked_deepslate_bricks`) |
+| `minecraft:cracked_deepslate_tiles` | `minecraft:smelting` | Ardoise des abîmes carrelée craquelée (`minecraft:cracked_deepslate_tiles`) |
+| `minecraft:deepslate` | `minecraft:smelting` | Ardoise des abîmes (`minecraft:deepslate`) |
+| `minecraft:deepslate_brick_slab` | `minecraft:crafting_shaped` | Dalle d'ardoise des abîmes taillée (`minecraft:deepslate_brick_slab`) × 6 |
+| `minecraft:deepslate_brick_slab_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes taillée (`minecraft:deepslate_brick_slab`) × 2 |
+| `minecraft:deepslate_brick_slab_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes taillée (`minecraft:deepslate_brick_slab`) × 2 |
+| `minecraft:deepslate_brick_slab_from_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes taillée (`minecraft:deepslate_brick_slab`) × 2 |
+| `minecraft:deepslate_brick_slab_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes taillée (`minecraft:deepslate_brick_slab`) × 2 |
+| `minecraft:deepslate_brick_stairs` | `minecraft:crafting_shaped` | Escalier en ardoise des abîmes taillée (`minecraft:deepslate_brick_stairs`) × 4 |
+| `minecraft:deepslate_brick_stairs_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes taillée (`minecraft:deepslate_brick_stairs`) |
+| `minecraft:deepslate_brick_stairs_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes taillée (`minecraft:deepslate_brick_stairs`) |
+| `minecraft:deepslate_brick_stairs_from_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes taillée (`minecraft:deepslate_brick_stairs`) |
+| `minecraft:deepslate_brick_stairs_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes taillée (`minecraft:deepslate_brick_stairs`) |
+| `minecraft:deepslate_brick_wall` | `minecraft:crafting_shaped` | Muret d'ardoise des abîmes taillée (`minecraft:deepslate_brick_wall`) × 6 |
+| `minecraft:deepslate_brick_wall_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes taillée (`minecraft:deepslate_brick_wall`) |
+| `minecraft:deepslate_brick_wall_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes taillée (`minecraft:deepslate_brick_wall`) |
+| `minecraft:deepslate_brick_wall_from_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes taillée (`minecraft:deepslate_brick_wall`) |
+| `minecraft:deepslate_brick_wall_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes taillée (`minecraft:deepslate_brick_wall`) |
+| `minecraft:deepslate_bricks` | `minecraft:crafting_shaped` | Ardoise des abîmes taillée (`minecraft:deepslate_bricks`) × 4 |
+| `minecraft:deepslate_bricks_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes taillée (`minecraft:deepslate_bricks`) |
+| `minecraft:deepslate_bricks_from_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes taillée (`minecraft:deepslate_bricks`) |
+| `minecraft:deepslate_bricks_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes taillée (`minecraft:deepslate_bricks`) |
+| `minecraft:deepslate_tile_slab` | `minecraft:crafting_shaped` | Dalle d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_slab`) × 6 |
+| `minecraft:deepslate_tile_slab_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_slab`) × 2 |
+| `minecraft:deepslate_tile_slab_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_slab`) × 2 |
+| `minecraft:deepslate_tile_slab_from_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_slab`) × 2 |
+| `minecraft:deepslate_tile_slab_from_deepslate_tiles_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_slab`) × 2 |
+| `minecraft:deepslate_tile_slab_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_slab`) × 2 |
+| `minecraft:deepslate_tile_stairs` | `minecraft:crafting_shaped` | Escalier en ardoise des abîmes carrelée (`minecraft:deepslate_tile_stairs`) × 4 |
+| `minecraft:deepslate_tile_stairs_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes carrelée (`minecraft:deepslate_tile_stairs`) |
+| `minecraft:deepslate_tile_stairs_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes carrelée (`minecraft:deepslate_tile_stairs`) |
+| `minecraft:deepslate_tile_stairs_from_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes carrelée (`minecraft:deepslate_tile_stairs`) |
+| `minecraft:deepslate_tile_stairs_from_deepslate_tiles_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes carrelée (`minecraft:deepslate_tile_stairs`) |
+| `minecraft:deepslate_tile_stairs_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes carrelée (`minecraft:deepslate_tile_stairs`) |
+| `minecraft:deepslate_tile_wall` | `minecraft:crafting_shaped` | Muret d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_wall`) × 6 |
+| `minecraft:deepslate_tile_wall_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_wall`) |
+| `minecraft:deepslate_tile_wall_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_wall`) |
+| `minecraft:deepslate_tile_wall_from_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_wall`) |
+| `minecraft:deepslate_tile_wall_from_deepslate_tiles_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_wall`) |
+| `minecraft:deepslate_tile_wall_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes carrelée (`minecraft:deepslate_tile_wall`) |
+| `minecraft:deepslate_tiles` | `minecraft:crafting_shaped` | Ardoise des abîmes carrelée (`minecraft:deepslate_tiles`) × 4 |
+| `minecraft:deepslate_tiles_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes carrelée (`minecraft:deepslate_tiles`) |
+| `minecraft:deepslate_tiles_from_deepslate_bricks_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes carrelée (`minecraft:deepslate_tiles`) |
+| `minecraft:deepslate_tiles_from_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes carrelée (`minecraft:deepslate_tiles`) |
+| `minecraft:deepslate_tiles_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes carrelée (`minecraft:deepslate_tiles`) |
+| `minecraft:polished_deepslate` | `minecraft:crafting_shaped` | Ardoise des abîmes polie (`minecraft:polished_deepslate`) × 4 |
+| `minecraft:polished_deepslate_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes polie (`minecraft:polished_deepslate`) |
+| `minecraft:polished_deepslate_from_deepslate_stonecutting` | `minecraft:stonecutting` | Ardoise des abîmes polie (`minecraft:polished_deepslate`) |
+| `minecraft:polished_deepslate_slab` | `minecraft:crafting_shaped` | Dalle d'ardoise des abîmes polie (`minecraft:polished_deepslate_slab`) × 6 |
+| `minecraft:polished_deepslate_slab_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes polie (`minecraft:polished_deepslate_slab`) × 2 |
+| `minecraft:polished_deepslate_slab_from_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes polie (`minecraft:polished_deepslate_slab`) × 2 |
+| `minecraft:polished_deepslate_slab_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Dalle d'ardoise des abîmes polie (`minecraft:polished_deepslate_slab`) × 2 |
+| `minecraft:polished_deepslate_stairs` | `minecraft:crafting_shaped` | Escalier en ardoise des abîmes polie (`minecraft:polished_deepslate_stairs`) × 4 |
+| `minecraft:polished_deepslate_stairs_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes polie (`minecraft:polished_deepslate_stairs`) |
+| `minecraft:polished_deepslate_stairs_from_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes polie (`minecraft:polished_deepslate_stairs`) |
+| `minecraft:polished_deepslate_stairs_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Escalier en ardoise des abîmes polie (`minecraft:polished_deepslate_stairs`) |
+| `minecraft:polished_deepslate_wall` | `minecraft:crafting_shaped` | Muret d'ardoise des abîmes polie (`minecraft:polished_deepslate_wall`) × 6 |
+| `minecraft:polished_deepslate_wall_from_cobbled_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes polie (`minecraft:polished_deepslate_wall`) |
+| `minecraft:polished_deepslate_wall_from_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes polie (`minecraft:polished_deepslate_wall`) |
+| `minecraft:polished_deepslate_wall_from_polished_deepslate_stonecutting` | `minecraft:stonecutting` | Muret d'ardoise des abîmes polie (`minecraft:polished_deepslate_wall`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_deepslate` | Ardoise des abîmes sculptée | Bloc et objet | 3 |
+| `minecraft:cobbled_deepslate` | Pierres des abîmes | Bloc et objet | 1 |
+| `minecraft:cobbled_deepslate_slab` | Dalle de pierres des abîmes | Bloc et objet | 3 |
+| `minecraft:cobbled_deepslate_stairs` | Escalier en pierres des abîmes | Bloc et objet | 3 |
+| `minecraft:cobbled_deepslate_wall` | Muret de pierres des abîmes | Bloc et objet | 3 |
+| `minecraft:cracked_deepslate_bricks` | Ardoise des abîmes taillée craquelée | Bloc et objet | 1 |
+| `minecraft:cracked_deepslate_tiles` | Ardoise des abîmes carrelée craquelée | Bloc et objet | 1 |
+| `minecraft:deepslate` | Ardoise des abîmes | Bloc et objet | 1 |
+| `minecraft:deepslate_brick_slab` | Dalle d'ardoise des abîmes taillée | Bloc et objet | 5 |
+| `minecraft:deepslate_brick_stairs` | Escalier en ardoise des abîmes taillée | Bloc et objet | 5 |
+| `minecraft:deepslate_brick_wall` | Muret d'ardoise des abîmes taillée | Bloc et objet | 5 |
+| `minecraft:deepslate_bricks` | Ardoise des abîmes taillée | Bloc et objet | 4 |
+| `minecraft:deepslate_tile_slab` | Dalle d'ardoise des abîmes carrelée | Bloc et objet | 6 |
+| `minecraft:deepslate_tile_stairs` | Escalier en ardoise des abîmes carrelée | Bloc et objet | 6 |
+| `minecraft:deepslate_tile_wall` | Muret d'ardoise des abîmes carrelée | Bloc et objet | 6 |
+| `minecraft:deepslate_tiles` | Ardoise des abîmes carrelée | Bloc et objet | 5 |
+| `minecraft:infested_deepslate` | Ardoise des abîmes infestée | Bloc et objet | 0 |
+| `minecraft:polished_deepslate` | Ardoise des abîmes polie | Bloc et objet | 3 |
+| `minecraft:polished_deepslate_slab` | Dalle d'ardoise des abîmes polie | Bloc et objet | 4 |
+| `minecraft:polished_deepslate_stairs` | Escalier en ardoise des abîmes polie | Bloc et objet | 4 |
+| `minecraft:polished_deepslate_wall` | Muret d'ardoise des abîmes polie | Bloc et objet | 4 |
+| `minecraft:reinforced_deepslate` | Ardoise des abîmes renforcée | Bloc et objet | 0 |
+
+## C05
+
+**Tuf · Tuff** — [déclencheurs et règles](collections-minecraft.md#c05).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_tuff` | `minecraft:crafting_shaped` | Tuf sculpté (`minecraft:chiseled_tuff`) |
+| `minecraft:chiseled_tuff_bricks` | `minecraft:crafting_shaped` | Tuf taillé sculpté (`minecraft:chiseled_tuff_bricks`) |
+| `minecraft:chiseled_tuff_bricks_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Tuf taillé sculpté (`minecraft:chiseled_tuff_bricks`) |
+| `minecraft:chiseled_tuff_bricks_from_tuff_bricks_stonecutting` | `minecraft:stonecutting` | Tuf taillé sculpté (`minecraft:chiseled_tuff_bricks`) |
+| `minecraft:chiseled_tuff_bricks_from_tuff_stonecutting` | `minecraft:stonecutting` | Tuf taillé sculpté (`minecraft:chiseled_tuff_bricks`) |
+| `minecraft:chiseled_tuff_from_tuff_stonecutting` | `minecraft:stonecutting` | Tuf sculpté (`minecraft:chiseled_tuff`) |
+| `minecraft:polished_tuff` | `minecraft:crafting_shaped` | Tuf poli (`minecraft:polished_tuff`) × 4 |
+| `minecraft:polished_tuff_from_tuff_stonecutting` | `minecraft:stonecutting` | Tuf poli (`minecraft:polished_tuff`) |
+| `minecraft:polished_tuff_slab` | `minecraft:crafting_shaped` | Dalle de tuf poli (`minecraft:polished_tuff_slab`) × 6 |
+| `minecraft:polished_tuff_slab_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Dalle de tuf poli (`minecraft:polished_tuff_slab`) × 2 |
+| `minecraft:polished_tuff_slab_from_tuff_stonecutting` | `minecraft:stonecutting` | Dalle de tuf poli (`minecraft:polished_tuff_slab`) × 2 |
+| `minecraft:polished_tuff_stairs` | `minecraft:crafting_shaped` | Escalier en tuf poli (`minecraft:polished_tuff_stairs`) × 4 |
+| `minecraft:polished_tuff_stairs_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Escalier en tuf poli (`minecraft:polished_tuff_stairs`) |
+| `minecraft:polished_tuff_stairs_from_tuff_stonecutting` | `minecraft:stonecutting` | Escalier en tuf poli (`minecraft:polished_tuff_stairs`) |
+| `minecraft:polished_tuff_wall` | `minecraft:crafting_shaped` | Muret de tuf poli (`minecraft:polished_tuff_wall`) × 6 |
+| `minecraft:polished_tuff_wall_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Muret de tuf poli (`minecraft:polished_tuff_wall`) |
+| `minecraft:polished_tuff_wall_from_tuff_stonecutting` | `minecraft:stonecutting` | Muret de tuf poli (`minecraft:polished_tuff_wall`) |
+| `minecraft:tuff_brick_slab` | `minecraft:crafting_shaped` | Dalle de tuf taillé (`minecraft:tuff_brick_slab`) × 6 |
+| `minecraft:tuff_brick_slab_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Dalle de tuf taillé (`minecraft:tuff_brick_slab`) × 2 |
+| `minecraft:tuff_brick_slab_from_tuff_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de tuf taillé (`minecraft:tuff_brick_slab`) × 2 |
+| `minecraft:tuff_brick_slab_from_tuff_stonecutting` | `minecraft:stonecutting` | Dalle de tuf taillé (`minecraft:tuff_brick_slab`) × 2 |
+| `minecraft:tuff_brick_stairs` | `minecraft:crafting_shaped` | Escalier en tuf taillé (`minecraft:tuff_brick_stairs`) × 4 |
+| `minecraft:tuff_brick_stairs_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Escalier en tuf taillé (`minecraft:tuff_brick_stairs`) |
+| `minecraft:tuff_brick_stairs_from_tuff_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en tuf taillé (`minecraft:tuff_brick_stairs`) |
+| `minecraft:tuff_brick_stairs_from_tuff_stonecutting` | `minecraft:stonecutting` | Escalier en tuf taillé (`minecraft:tuff_brick_stairs`) |
+| `minecraft:tuff_brick_wall` | `minecraft:crafting_shaped` | Muret de tuf taillé (`minecraft:tuff_brick_wall`) × 6 |
+| `minecraft:tuff_brick_wall_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Muret de tuf taillé (`minecraft:tuff_brick_wall`) |
+| `minecraft:tuff_brick_wall_from_tuff_bricks_stonecutting` | `minecraft:stonecutting` | Muret de tuf taillé (`minecraft:tuff_brick_wall`) |
+| `minecraft:tuff_brick_wall_from_tuff_stonecutting` | `minecraft:stonecutting` | Muret de tuf taillé (`minecraft:tuff_brick_wall`) |
+| `minecraft:tuff_bricks` | `minecraft:crafting_shaped` | Tuf taillé (`minecraft:tuff_bricks`) × 4 |
+| `minecraft:tuff_bricks_from_polished_tuff_stonecutting` | `minecraft:stonecutting` | Tuf taillé (`minecraft:tuff_bricks`) |
+| `minecraft:tuff_bricks_from_tuff_stonecutting` | `minecraft:stonecutting` | Tuf taillé (`minecraft:tuff_bricks`) |
+| `minecraft:tuff_slab` | `minecraft:crafting_shaped` | Dalle de tuf (`minecraft:tuff_slab`) × 6 |
+| `minecraft:tuff_slab_from_tuff_stonecutting` | `minecraft:stonecutting` | Dalle de tuf (`minecraft:tuff_slab`) × 2 |
+| `minecraft:tuff_stairs` | `minecraft:crafting_shaped` | Escalier en tuf (`minecraft:tuff_stairs`) × 4 |
+| `minecraft:tuff_stairs_from_tuff_stonecutting` | `minecraft:stonecutting` | Escalier en tuf (`minecraft:tuff_stairs`) |
+| `minecraft:tuff_wall` | `minecraft:crafting_shaped` | Muret de tuf (`minecraft:tuff_wall`) × 6 |
+| `minecraft:tuff_wall_from_tuff_stonecutting` | `minecraft:stonecutting` | Muret de tuf (`minecraft:tuff_wall`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_tuff` | Tuf sculpté | Bloc et objet | 2 |
+| `minecraft:chiseled_tuff_bricks` | Tuf taillé sculpté | Bloc et objet | 4 |
+| `minecraft:polished_tuff` | Tuf poli | Bloc et objet | 2 |
+| `minecraft:polished_tuff_slab` | Dalle de tuf poli | Bloc et objet | 3 |
+| `minecraft:polished_tuff_stairs` | Escalier en tuf poli | Bloc et objet | 3 |
+| `minecraft:polished_tuff_wall` | Muret de tuf poli | Bloc et objet | 3 |
+| `minecraft:tuff` | Tuf | Bloc et objet | 0 |
+| `minecraft:tuff_brick_slab` | Dalle de tuf taillé | Bloc et objet | 4 |
+| `minecraft:tuff_brick_stairs` | Escalier en tuf taillé | Bloc et objet | 4 |
+| `minecraft:tuff_brick_wall` | Muret de tuf taillé | Bloc et objet | 4 |
+| `minecraft:tuff_bricks` | Tuf taillé | Bloc et objet | 3 |
+| `minecraft:tuff_slab` | Dalle de tuf | Bloc et objet | 2 |
+| `minecraft:tuff_stairs` | Escalier en tuf | Bloc et objet | 2 |
+| `minecraft:tuff_wall` | Muret de tuf | Bloc et objet | 2 |
+
+## C06
+
+**Argile et briques · Clay and Bricks** — [déclencheurs et règles](collections-minecraft.md#c06).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:brick` | `minecraft:smelting` | Brique (`minecraft:brick`) |
+| `minecraft:brick_slab` | `minecraft:crafting_shaped` | Dalle de briques (`minecraft:brick_slab`) × 6 |
+| `minecraft:brick_slab_from_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de briques (`minecraft:brick_slab`) × 2 |
+| `minecraft:brick_stairs` | `minecraft:crafting_shaped` | Escalier en briques (`minecraft:brick_stairs`) × 4 |
+| `minecraft:brick_stairs_from_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en briques (`minecraft:brick_stairs`) |
+| `minecraft:brick_wall` | `minecraft:crafting_shaped` | Muret de briques (`minecraft:brick_wall`) × 6 |
+| `minecraft:brick_wall_from_bricks_stonecutting` | `minecraft:stonecutting` | Muret de briques (`minecraft:brick_wall`) |
+| `minecraft:bricks` | `minecraft:crafting_shaped` | Briques (`minecraft:bricks`) |
+| `minecraft:clay` | `minecraft:crafting_shaped` | Argile (`minecraft:clay`) |
+| `minecraft:flower_pot` | `minecraft:crafting_shaped` | Pot de fleurs (`minecraft:flower_pot`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:brick` | Brique | Objet | 1 |
+| `minecraft:brick_slab` | Dalle de briques | Bloc et objet | 2 |
+| `minecraft:brick_stairs` | Escalier en briques | Bloc et objet | 2 |
+| `minecraft:brick_wall` | Muret de briques | Bloc et objet | 2 |
+| `minecraft:bricks` | Briques | Bloc et objet | 1 |
+| `minecraft:clay` | Argile | Bloc et objet | 1 |
+| `minecraft:clay_ball` | Motte d'argile | Objet | 0 |
+| `minecraft:flower_pot` | Pot de fleurs | Bloc et objet | 1 |
+
+## C07
+
+**Sables et grès · Sand and Sandstone** — [déclencheurs et règles](collections-minecraft.md#c07).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_red_sandstone` | `minecraft:crafting_shaped` | Grès rouge sculpté (`minecraft:chiseled_red_sandstone`) |
+| `minecraft:chiseled_red_sandstone_from_red_sandstone_stonecutting` | `minecraft:stonecutting` | Grès rouge sculpté (`minecraft:chiseled_red_sandstone`) |
+| `minecraft:chiseled_sandstone` | `minecraft:crafting_shaped` | Grès sculpté (`minecraft:chiseled_sandstone`) |
+| `minecraft:chiseled_sandstone_from_sandstone_stonecutting` | `minecraft:stonecutting` | Grès sculpté (`minecraft:chiseled_sandstone`) |
+| `minecraft:cut_red_sandstone` | `minecraft:crafting_shaped` | Grès rouge taillé (`minecraft:cut_red_sandstone`) × 4 |
+| `minecraft:cut_red_sandstone_from_red_sandstone_stonecutting` | `minecraft:stonecutting` | Grès rouge taillé (`minecraft:cut_red_sandstone`) |
+| `minecraft:cut_red_sandstone_slab` | `minecraft:crafting_shaped` | Dalle de grès rouge taillé (`minecraft:cut_red_sandstone_slab`) × 6 |
+| `minecraft:cut_red_sandstone_slab_from_cut_red_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès rouge taillé (`minecraft:cut_red_sandstone_slab`) × 2 |
+| `minecraft:cut_red_sandstone_slab_from_red_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès rouge taillé (`minecraft:cut_red_sandstone_slab`) × 2 |
+| `minecraft:cut_sandstone` | `minecraft:crafting_shaped` | Grès taillé (`minecraft:cut_sandstone`) × 4 |
+| `minecraft:cut_sandstone_from_sandstone_stonecutting` | `minecraft:stonecutting` | Grès taillé (`minecraft:cut_sandstone`) |
+| `minecraft:cut_sandstone_slab` | `minecraft:crafting_shaped` | Dalle de grès taillé (`minecraft:cut_sandstone_slab`) × 6 |
+| `minecraft:cut_sandstone_slab_from_cut_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès taillé (`minecraft:cut_sandstone_slab`) × 2 |
+| `minecraft:cut_sandstone_slab_from_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès taillé (`minecraft:cut_sandstone_slab`) × 2 |
+| `minecraft:red_sandstone` | `minecraft:crafting_shaped` | Grès rouge (`minecraft:red_sandstone`) |
+| `minecraft:red_sandstone_slab` | `minecraft:crafting_shaped` | Dalle de grès rouge (`minecraft:red_sandstone_slab`) × 6 |
+| `minecraft:red_sandstone_slab_from_red_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès rouge (`minecraft:red_sandstone_slab`) × 2 |
+| `minecraft:red_sandstone_stairs` | `minecraft:crafting_shaped` | Escalier en grès rouge (`minecraft:red_sandstone_stairs`) × 4 |
+| `minecraft:red_sandstone_stairs_from_red_sandstone_stonecutting` | `minecraft:stonecutting` | Escalier en grès rouge (`minecraft:red_sandstone_stairs`) |
+| `minecraft:red_sandstone_wall` | `minecraft:crafting_shaped` | Muret de grès rouge (`minecraft:red_sandstone_wall`) × 6 |
+| `minecraft:red_sandstone_wall_from_red_sandstone_stonecutting` | `minecraft:stonecutting` | Muret de grès rouge (`minecraft:red_sandstone_wall`) |
+| `minecraft:sandstone` | `minecraft:crafting_shaped` | Grès (`minecraft:sandstone`) |
+| `minecraft:sandstone_slab` | `minecraft:crafting_shaped` | Dalle de grès (`minecraft:sandstone_slab`) × 6 |
+| `minecraft:sandstone_slab_from_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès (`minecraft:sandstone_slab`) × 2 |
+| `minecraft:sandstone_stairs` | `minecraft:crafting_shaped` | Escalier en grès (`minecraft:sandstone_stairs`) × 4 |
+| `minecraft:sandstone_stairs_from_sandstone_stonecutting` | `minecraft:stonecutting` | Escalier en grès (`minecraft:sandstone_stairs`) |
+| `minecraft:sandstone_wall` | `minecraft:crafting_shaped` | Muret de grès (`minecraft:sandstone_wall`) × 6 |
+| `minecraft:sandstone_wall_from_sandstone_stonecutting` | `minecraft:stonecutting` | Muret de grès (`minecraft:sandstone_wall`) |
+| `minecraft:smooth_red_sandstone` | `minecraft:smelting` | Grès rouge lisse (`minecraft:smooth_red_sandstone`) |
+| `minecraft:smooth_red_sandstone_slab` | `minecraft:crafting_shaped` | Dalle de grès rouge lisse (`minecraft:smooth_red_sandstone_slab`) × 6 |
+| `minecraft:smooth_red_sandstone_slab_from_smooth_red_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès rouge lisse (`minecraft:smooth_red_sandstone_slab`) × 2 |
+| `minecraft:smooth_red_sandstone_stairs` | `minecraft:crafting_shaped` | Escalier en grès rouge lisse (`minecraft:smooth_red_sandstone_stairs`) × 4 |
+| `minecraft:smooth_red_sandstone_stairs_from_smooth_red_sandstone_stonecutting` | `minecraft:stonecutting` | Escalier en grès rouge lisse (`minecraft:smooth_red_sandstone_stairs`) |
+| `minecraft:smooth_sandstone` | `minecraft:smelting` | Grès lisse (`minecraft:smooth_sandstone`) |
+| `minecraft:smooth_sandstone_slab` | `minecraft:crafting_shaped` | Dalle de grès lisse (`minecraft:smooth_sandstone_slab`) × 6 |
+| `minecraft:smooth_sandstone_slab_from_smooth_sandstone_stonecutting` | `minecraft:stonecutting` | Dalle de grès lisse (`minecraft:smooth_sandstone_slab`) × 2 |
+| `minecraft:smooth_sandstone_stairs` | `minecraft:crafting_shaped` | Escalier en grès lisse (`minecraft:smooth_sandstone_stairs`) × 4 |
+| `minecraft:smooth_sandstone_stairs_from_smooth_sandstone_stonecutting` | `minecraft:stonecutting` | Escalier en grès lisse (`minecraft:smooth_sandstone_stairs`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_red_sandstone` | Grès rouge sculpté | Bloc et objet | 2 |
+| `minecraft:chiseled_sandstone` | Grès sculpté | Bloc et objet | 2 |
+| `minecraft:cut_red_sandstone` | Grès rouge taillé | Bloc et objet | 2 |
+| `minecraft:cut_red_sandstone_slab` | Dalle de grès rouge taillé | Bloc et objet | 3 |
+| `minecraft:cut_sandstone` | Grès taillé | Bloc et objet | 2 |
+| `minecraft:cut_sandstone_slab` | Dalle de grès taillé | Bloc et objet | 3 |
+| `minecraft:gravel` | Gravier | Bloc et objet | 0 |
+| `minecraft:red_sand` | Sable rouge | Bloc et objet | 0 |
+| `minecraft:red_sandstone` | Grès rouge | Bloc et objet | 1 |
+| `minecraft:red_sandstone_slab` | Dalle de grès rouge | Bloc et objet | 2 |
+| `minecraft:red_sandstone_stairs` | Escalier en grès rouge | Bloc et objet | 2 |
+| `minecraft:red_sandstone_wall` | Muret de grès rouge | Bloc et objet | 2 |
+| `minecraft:sand` | Sable | Bloc et objet | 0 |
+| `minecraft:sandstone` | Grès | Bloc et objet | 1 |
+| `minecraft:sandstone_slab` | Dalle de grès | Bloc et objet | 2 |
+| `minecraft:sandstone_stairs` | Escalier en grès | Bloc et objet | 2 |
+| `minecraft:sandstone_wall` | Muret de grès | Bloc et objet | 2 |
+| `minecraft:smooth_red_sandstone` | Grès rouge lisse | Bloc et objet | 1 |
+| `minecraft:smooth_red_sandstone_slab` | Dalle de grès rouge lisse | Bloc et objet | 2 |
+| `minecraft:smooth_red_sandstone_stairs` | Escalier en grès rouge lisse | Bloc et objet | 2 |
+| `minecraft:smooth_sandstone` | Grès lisse | Bloc et objet | 1 |
+| `minecraft:smooth_sandstone_slab` | Dalle de grès lisse | Bloc et objet | 2 |
+| `minecraft:smooth_sandstone_stairs` | Escalier en grès lisse | Bloc et objet | 2 |
+| `minecraft:suspicious_gravel` | Gravier suspect | Bloc et objet | 0 |
+| `minecraft:suspicious_sand` | Sable suspect | Bloc et objet | 0 |
+
+## C08
+
+**Verre et vitrages · Glasswork** — [déclencheurs et règles](collections-minecraft.md#c08).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_stained_glass` | `minecraft:crafting_shaped` | Verre noir (`minecraft:black_stained_glass`) × 8 |
+| `minecraft:black_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre noire (`minecraft:black_stained_glass_pane`) × 16 |
+| `minecraft:black_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre noire (`minecraft:black_stained_glass_pane`) × 8 |
+| `minecraft:blue_stained_glass` | `minecraft:crafting_shaped` | Verre bleu (`minecraft:blue_stained_glass`) × 8 |
+| `minecraft:blue_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre bleue (`minecraft:blue_stained_glass_pane`) × 16 |
+| `minecraft:blue_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre bleue (`minecraft:blue_stained_glass_pane`) × 8 |
+| `minecraft:brown_stained_glass` | `minecraft:crafting_shaped` | Verre marron (`minecraft:brown_stained_glass`) × 8 |
+| `minecraft:brown_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre marron (`minecraft:brown_stained_glass_pane`) × 16 |
+| `minecraft:brown_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre marron (`minecraft:brown_stained_glass_pane`) × 8 |
+| `minecraft:cyan_stained_glass` | `minecraft:crafting_shaped` | Verre cyan (`minecraft:cyan_stained_glass`) × 8 |
+| `minecraft:cyan_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre cyan (`minecraft:cyan_stained_glass_pane`) × 16 |
+| `minecraft:cyan_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre cyan (`minecraft:cyan_stained_glass_pane`) × 8 |
+| `minecraft:glass` | `minecraft:smelting` | Verre (`minecraft:glass`) |
+| `minecraft:glass_pane` | `minecraft:crafting_shaped` | Vitre (`minecraft:glass_pane`) × 16 |
+| `minecraft:gray_stained_glass` | `minecraft:crafting_shaped` | Verre gris (`minecraft:gray_stained_glass`) × 8 |
+| `minecraft:gray_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre grise (`minecraft:gray_stained_glass_pane`) × 16 |
+| `minecraft:gray_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre grise (`minecraft:gray_stained_glass_pane`) × 8 |
+| `minecraft:green_stained_glass` | `minecraft:crafting_shaped` | Verre vert (`minecraft:green_stained_glass`) × 8 |
+| `minecraft:green_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre verte (`minecraft:green_stained_glass_pane`) × 16 |
+| `minecraft:green_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre verte (`minecraft:green_stained_glass_pane`) × 8 |
+| `minecraft:light_blue_stained_glass` | `minecraft:crafting_shaped` | Verre bleu clair (`minecraft:light_blue_stained_glass`) × 8 |
+| `minecraft:light_blue_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre bleu clair (`minecraft:light_blue_stained_glass_pane`) × 16 |
+| `minecraft:light_blue_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre bleu clair (`minecraft:light_blue_stained_glass_pane`) × 8 |
+| `minecraft:light_gray_stained_glass` | `minecraft:crafting_shaped` | Verre gris clair (`minecraft:light_gray_stained_glass`) × 8 |
+| `minecraft:light_gray_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre gris clair (`minecraft:light_gray_stained_glass_pane`) × 16 |
+| `minecraft:light_gray_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre gris clair (`minecraft:light_gray_stained_glass_pane`) × 8 |
+| `minecraft:lime_stained_glass` | `minecraft:crafting_shaped` | Verre vert clair (`minecraft:lime_stained_glass`) × 8 |
+| `minecraft:lime_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre vert clair (`minecraft:lime_stained_glass_pane`) × 16 |
+| `minecraft:lime_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre vert clair (`minecraft:lime_stained_glass_pane`) × 8 |
+| `minecraft:magenta_stained_glass` | `minecraft:crafting_shaped` | Verre magenta (`minecraft:magenta_stained_glass`) × 8 |
+| `minecraft:magenta_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre magenta (`minecraft:magenta_stained_glass_pane`) × 16 |
+| `minecraft:magenta_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre magenta (`minecraft:magenta_stained_glass_pane`) × 8 |
+| `minecraft:orange_stained_glass` | `minecraft:crafting_shaped` | Verre orange (`minecraft:orange_stained_glass`) × 8 |
+| `minecraft:orange_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre orange (`minecraft:orange_stained_glass_pane`) × 16 |
+| `minecraft:orange_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre orange (`minecraft:orange_stained_glass_pane`) × 8 |
+| `minecraft:pink_stained_glass` | `minecraft:crafting_shaped` | Verre rose (`minecraft:pink_stained_glass`) × 8 |
+| `minecraft:pink_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre rose (`minecraft:pink_stained_glass_pane`) × 16 |
+| `minecraft:pink_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre rose (`minecraft:pink_stained_glass_pane`) × 8 |
+| `minecraft:purple_stained_glass` | `minecraft:crafting_shaped` | Verre violet (`minecraft:purple_stained_glass`) × 8 |
+| `minecraft:purple_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre violette (`minecraft:purple_stained_glass_pane`) × 16 |
+| `minecraft:purple_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre violette (`minecraft:purple_stained_glass_pane`) × 8 |
+| `minecraft:red_stained_glass` | `minecraft:crafting_shaped` | Verre rouge (`minecraft:red_stained_glass`) × 8 |
+| `minecraft:red_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre rouge (`minecraft:red_stained_glass_pane`) × 16 |
+| `minecraft:red_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre rouge (`minecraft:red_stained_glass_pane`) × 8 |
+| `minecraft:white_stained_glass` | `minecraft:crafting_shaped` | Verre blanc (`minecraft:white_stained_glass`) × 8 |
+| `minecraft:white_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre blanche (`minecraft:white_stained_glass_pane`) × 16 |
+| `minecraft:white_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre blanche (`minecraft:white_stained_glass_pane`) × 8 |
+| `minecraft:yellow_stained_glass` | `minecraft:crafting_shaped` | Verre jaune (`minecraft:yellow_stained_glass`) × 8 |
+| `minecraft:yellow_stained_glass_pane` | `minecraft:crafting_shaped` | Vitre jaune (`minecraft:yellow_stained_glass_pane`) × 16 |
+| `minecraft:yellow_stained_glass_pane_from_glass_pane` | `minecraft:crafting_shaped` | Vitre jaune (`minecraft:yellow_stained_glass_pane`) × 8 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_stained_glass` | Verre noir | Bloc et objet | 1 |
+| `minecraft:black_stained_glass_pane` | Vitre noire | Bloc et objet | 2 |
+| `minecraft:blue_stained_glass` | Verre bleu | Bloc et objet | 1 |
+| `minecraft:blue_stained_glass_pane` | Vitre bleue | Bloc et objet | 2 |
+| `minecraft:brown_stained_glass` | Verre marron | Bloc et objet | 1 |
+| `minecraft:brown_stained_glass_pane` | Vitre marron | Bloc et objet | 2 |
+| `minecraft:cyan_stained_glass` | Verre cyan | Bloc et objet | 1 |
+| `minecraft:cyan_stained_glass_pane` | Vitre cyan | Bloc et objet | 2 |
+| `minecraft:glass` | Verre | Bloc et objet | 1 |
+| `minecraft:glass_pane` | Vitre | Bloc et objet | 1 |
+| `minecraft:gray_stained_glass` | Verre gris | Bloc et objet | 1 |
+| `minecraft:gray_stained_glass_pane` | Vitre grise | Bloc et objet | 2 |
+| `minecraft:green_stained_glass` | Verre vert | Bloc et objet | 1 |
+| `minecraft:green_stained_glass_pane` | Vitre verte | Bloc et objet | 2 |
+| `minecraft:light_blue_stained_glass` | Verre bleu clair | Bloc et objet | 1 |
+| `minecraft:light_blue_stained_glass_pane` | Vitre bleu clair | Bloc et objet | 2 |
+| `minecraft:light_gray_stained_glass` | Verre gris clair | Bloc et objet | 1 |
+| `minecraft:light_gray_stained_glass_pane` | Vitre gris clair | Bloc et objet | 2 |
+| `minecraft:lime_stained_glass` | Verre vert clair | Bloc et objet | 1 |
+| `minecraft:lime_stained_glass_pane` | Vitre vert clair | Bloc et objet | 2 |
+| `minecraft:magenta_stained_glass` | Verre magenta | Bloc et objet | 1 |
+| `minecraft:magenta_stained_glass_pane` | Vitre magenta | Bloc et objet | 2 |
+| `minecraft:orange_stained_glass` | Verre orange | Bloc et objet | 1 |
+| `minecraft:orange_stained_glass_pane` | Vitre orange | Bloc et objet | 2 |
+| `minecraft:pink_stained_glass` | Verre rose | Bloc et objet | 1 |
+| `minecraft:pink_stained_glass_pane` | Vitre rose | Bloc et objet | 2 |
+| `minecraft:purple_stained_glass` | Verre violet | Bloc et objet | 1 |
+| `minecraft:purple_stained_glass_pane` | Vitre violette | Bloc et objet | 2 |
+| `minecraft:red_stained_glass` | Verre rouge | Bloc et objet | 1 |
+| `minecraft:red_stained_glass_pane` | Vitre rouge | Bloc et objet | 2 |
+| `minecraft:white_stained_glass` | Verre blanc | Bloc et objet | 1 |
+| `minecraft:white_stained_glass_pane` | Vitre blanche | Bloc et objet | 2 |
+| `minecraft:yellow_stained_glass` | Verre jaune | Bloc et objet | 1 |
+| `minecraft:yellow_stained_glass_pane` | Vitre jaune | Bloc et objet | 2 |
+
+## C09
+
+**Teintures · Dyes** — [déclencheurs et règles](collections-minecraft.md#c09).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_dye` | `minecraft:crafting_shapeless` | Teinture noire (`minecraft:black_dye`) |
+| `minecraft:black_dye_from_wither_rose` | `minecraft:crafting_shapeless` | Teinture noire (`minecraft:black_dye`) |
+| `minecraft:blue_dye` | `minecraft:crafting_shapeless` | Teinture bleue (`minecraft:blue_dye`) |
+| `minecraft:blue_dye_from_cornflower` | `minecraft:crafting_shapeless` | Teinture bleue (`minecraft:blue_dye`) |
+| `minecraft:brown_dye` | `minecraft:crafting_shapeless` | Teinture marron (`minecraft:brown_dye`) |
+| `minecraft:cyan_dye` | `minecraft:crafting_shapeless` | Teinture cyan (`minecraft:cyan_dye`) × 2 |
+| `minecraft:cyan_dye_from_pitcher_plant` | `minecraft:crafting_shapeless` | Teinture cyan (`minecraft:cyan_dye`) × 2 |
+| `minecraft:gray_dye` | `minecraft:crafting_shapeless` | Teinture grise (`minecraft:gray_dye`) × 2 |
+| `minecraft:gray_dye_from_closed_eyeblossom` | `minecraft:crafting_shapeless` | Teinture grise (`minecraft:gray_dye`) |
+| `minecraft:green_dye` | `minecraft:smelting` | Teinture verte (`minecraft:green_dye`) |
+| `minecraft:lapis_block` | `minecraft:crafting_shaped` | Bloc de lapis-lazuli (`minecraft:lapis_block`) |
+| `minecraft:lapis_lazuli` | `minecraft:crafting_shapeless` | Lapis-lazuli (`minecraft:lapis_lazuli`) × 9 |
+| `minecraft:lapis_lazuli_from_blasting_deepslate_lapis_ore` | `minecraft:blasting` | Lapis-lazuli (`minecraft:lapis_lazuli`) |
+| `minecraft:lapis_lazuli_from_blasting_lapis_ore` | `minecraft:blasting` | Lapis-lazuli (`minecraft:lapis_lazuli`) |
+| `minecraft:lapis_lazuli_from_smelting_deepslate_lapis_ore` | `minecraft:smelting` | Lapis-lazuli (`minecraft:lapis_lazuli`) |
+| `minecraft:lapis_lazuli_from_smelting_lapis_ore` | `minecraft:smelting` | Lapis-lazuli (`minecraft:lapis_lazuli`) |
+| `minecraft:light_blue_dye_from_blue_orchid` | `minecraft:crafting_shapeless` | Teinture bleu clair (`minecraft:light_blue_dye`) |
+| `minecraft:light_blue_dye_from_blue_white_dye` | `minecraft:crafting_shapeless` | Teinture bleu clair (`minecraft:light_blue_dye`) × 2 |
+| `minecraft:light_gray_dye_from_azure_bluet` | `minecraft:crafting_shapeless` | Teinture gris clair (`minecraft:light_gray_dye`) |
+| `minecraft:light_gray_dye_from_black_white_dye` | `minecraft:crafting_shapeless` | Teinture gris clair (`minecraft:light_gray_dye`) × 3 |
+| `minecraft:light_gray_dye_from_gray_white_dye` | `minecraft:crafting_shapeless` | Teinture gris clair (`minecraft:light_gray_dye`) × 2 |
+| `minecraft:light_gray_dye_from_oxeye_daisy` | `minecraft:crafting_shapeless` | Teinture gris clair (`minecraft:light_gray_dye`) |
+| `minecraft:light_gray_dye_from_white_tulip` | `minecraft:crafting_shapeless` | Teinture gris clair (`minecraft:light_gray_dye`) |
+| `minecraft:lime_dye` | `minecraft:crafting_shapeless` | Teinture vert clair (`minecraft:lime_dye`) × 2 |
+| `minecraft:lime_dye_from_smelting` | `minecraft:smelting` | Teinture vert clair (`minecraft:lime_dye`) |
+| `minecraft:magenta_dye_from_allium` | `minecraft:crafting_shapeless` | Teinture magenta (`minecraft:magenta_dye`) |
+| `minecraft:magenta_dye_from_blue_red_pink` | `minecraft:crafting_shapeless` | Teinture magenta (`minecraft:magenta_dye`) × 3 |
+| `minecraft:magenta_dye_from_blue_red_white_dye` | `minecraft:crafting_shapeless` | Teinture magenta (`minecraft:magenta_dye`) × 4 |
+| `minecraft:magenta_dye_from_lilac` | `minecraft:crafting_shapeless` | Teinture magenta (`minecraft:magenta_dye`) × 2 |
+| `minecraft:magenta_dye_from_purple_and_pink` | `minecraft:crafting_shapeless` | Teinture magenta (`minecraft:magenta_dye`) × 2 |
+| `minecraft:orange_dye_from_open_eyeblossom` | `minecraft:crafting_shapeless` | Teinture orange (`minecraft:orange_dye`) |
+| `minecraft:orange_dye_from_orange_tulip` | `minecraft:crafting_shapeless` | Teinture orange (`minecraft:orange_dye`) |
+| `minecraft:orange_dye_from_red_yellow` | `minecraft:crafting_shapeless` | Teinture orange (`minecraft:orange_dye`) × 2 |
+| `minecraft:orange_dye_from_torchflower` | `minecraft:crafting_shapeless` | Teinture orange (`minecraft:orange_dye`) |
+| `minecraft:pink_dye_from_cactus_flower` | `minecraft:crafting_shapeless` | Teinture rose (`minecraft:pink_dye`) |
+| `minecraft:pink_dye_from_peony` | `minecraft:crafting_shapeless` | Teinture rose (`minecraft:pink_dye`) × 2 |
+| `minecraft:pink_dye_from_pink_petals` | `minecraft:crafting_shapeless` | Teinture rose (`minecraft:pink_dye`) |
+| `minecraft:pink_dye_from_pink_tulip` | `minecraft:crafting_shapeless` | Teinture rose (`minecraft:pink_dye`) |
+| `minecraft:pink_dye_from_red_white_dye` | `minecraft:crafting_shapeless` | Teinture rose (`minecraft:pink_dye`) × 2 |
+| `minecraft:purple_dye` | `minecraft:crafting_shapeless` | Teinture violette (`minecraft:purple_dye`) × 2 |
+| `minecraft:red_dye_from_beetroot` | `minecraft:crafting_shapeless` | Teinture rouge (`minecraft:red_dye`) |
+| `minecraft:red_dye_from_poppy` | `minecraft:crafting_shapeless` | Teinture rouge (`minecraft:red_dye`) |
+| `minecraft:red_dye_from_rose_bush` | `minecraft:crafting_shapeless` | Teinture rouge (`minecraft:red_dye`) × 2 |
+| `minecraft:red_dye_from_tulip` | `minecraft:crafting_shapeless` | Teinture rouge (`minecraft:red_dye`) |
+| `minecraft:white_dye` | `minecraft:crafting_shapeless` | Teinture blanche (`minecraft:white_dye`) |
+| `minecraft:white_dye_from_lily_of_the_valley` | `minecraft:crafting_shapeless` | Teinture blanche (`minecraft:white_dye`) |
+| `minecraft:yellow_dye_from_dandelion` | `minecraft:crafting_shapeless` | Teinture jaune (`minecraft:yellow_dye`) |
+| `minecraft:yellow_dye_from_golden_dandelion` | `minecraft:crafting_shapeless` | Teinture jaune (`minecraft:yellow_dye`) |
+| `minecraft:yellow_dye_from_sunflower` | `minecraft:crafting_shapeless` | Teinture jaune (`minecraft:yellow_dye`) × 2 |
+| `minecraft:yellow_dye_from_wildflowers` | `minecraft:crafting_shapeless` | Teinture jaune (`minecraft:yellow_dye`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_dye` | Teinture noire | Objet | 2 |
+| `minecraft:blue_dye` | Teinture bleue | Objet | 2 |
+| `minecraft:brown_dye` | Teinture marron | Objet | 1 |
+| `minecraft:cyan_dye` | Teinture cyan | Objet | 2 |
+| `minecraft:deepslate_lapis_ore` | Minerai de lapis-lazuli des abîmes | Bloc et objet | 0 |
+| `minecraft:glow_ink_sac` | Poche d'encre luisante | Objet | 0 |
+| `minecraft:gray_dye` | Teinture grise | Objet | 2 |
+| `minecraft:green_dye` | Teinture verte | Objet | 1 |
+| `minecraft:ink_sac` | Poche d'encre | Objet | 0 |
+| `minecraft:lapis_block` | Bloc de lapis-lazuli | Bloc et objet | 1 |
+| `minecraft:lapis_lazuli` | Lapis-lazuli | Objet | 5 |
+| `minecraft:lapis_ore` | Minerai de lapis-lazuli | Bloc et objet | 0 |
+| `minecraft:light_blue_dye` | Teinture bleu clair | Objet | 2 |
+| `minecraft:light_gray_dye` | Teinture gris clair | Objet | 5 |
+| `minecraft:lime_dye` | Teinture vert clair | Objet | 2 |
+| `minecraft:magenta_dye` | Teinture magenta | Objet | 5 |
+| `minecraft:orange_dye` | Teinture orange | Objet | 4 |
+| `minecraft:pink_dye` | Teinture rose | Objet | 5 |
+| `minecraft:purple_dye` | Teinture violette | Objet | 1 |
+| `minecraft:red_dye` | Teinture rouge | Objet | 4 |
+| `minecraft:white_dye` | Teinture blanche | Objet | 2 |
+| `minecraft:yellow_dye` | Teinture jaune | Objet | 4 |
+
+## C10
+
+**Laine, literie et coussins · Textiles and Bedding** — [déclencheurs et règles](collections-minecraft.md#c10).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_bed` | `minecraft:crafting_shaped` | Lit noir (`minecraft:black_bed`) |
+| `minecraft:black_carpet` | `minecraft:crafting_shaped` | Tapis noir (`minecraft:black_carpet`) × 3 |
+| `minecraft:black_cushion` | `minecraft:crafting_shaped` | Coussin noir (`minecraft:black_cushion`) |
+| `minecraft:black_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine noire (`minecraft:black_wool_slab`) × 6 |
+| `minecraft:black_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine noire (`minecraft:black_wool_stairs`) × 4 |
+| `minecraft:blue_bed` | `minecraft:crafting_shaped` | Lit bleu (`minecraft:blue_bed`) |
+| `minecraft:blue_carpet` | `minecraft:crafting_shaped` | Tapis bleu (`minecraft:blue_carpet`) × 3 |
+| `minecraft:blue_cushion` | `minecraft:crafting_shaped` | Coussin bleu (`minecraft:blue_cushion`) |
+| `minecraft:blue_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine bleue (`minecraft:blue_wool_slab`) × 6 |
+| `minecraft:blue_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine bleue (`minecraft:blue_wool_stairs`) × 4 |
+| `minecraft:brown_bed` | `minecraft:crafting_shaped` | Lit marron (`minecraft:brown_bed`) |
+| `minecraft:brown_carpet` | `minecraft:crafting_shaped` | Tapis marron (`minecraft:brown_carpet`) × 3 |
+| `minecraft:brown_cushion` | `minecraft:crafting_shaped` | Coussin marron (`minecraft:brown_cushion`) |
+| `minecraft:brown_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine marron (`minecraft:brown_wool_slab`) × 6 |
+| `minecraft:brown_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine marron (`minecraft:brown_wool_stairs`) × 4 |
+| `minecraft:cyan_bed` | `minecraft:crafting_shaped` | Lit cyan (`minecraft:cyan_bed`) |
+| `minecraft:cyan_carpet` | `minecraft:crafting_shaped` | Tapis cyan (`minecraft:cyan_carpet`) × 3 |
+| `minecraft:cyan_cushion` | `minecraft:crafting_shaped` | Coussin cyan (`minecraft:cyan_cushion`) |
+| `minecraft:cyan_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine cyan (`minecraft:cyan_wool_slab`) × 6 |
+| `minecraft:cyan_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine cyan (`minecraft:cyan_wool_stairs`) × 4 |
+| `minecraft:dye_black_bed` | `minecraft:crafting_shapeless` | Lit noir (`minecraft:black_bed`) |
+| `minecraft:dye_black_carpet` | `minecraft:crafting_shapeless` | Tapis noir (`minecraft:black_carpet`) |
+| `minecraft:dye_black_cushion` | `minecraft:crafting_shapeless` | Coussin noir (`minecraft:black_cushion`) |
+| `minecraft:dye_black_wool` | `minecraft:crafting_shapeless` | Laine noire (`minecraft:black_wool`) |
+| `minecraft:dye_black_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine noire (`minecraft:black_wool_slab`) |
+| `minecraft:dye_black_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine noire (`minecraft:black_wool_stairs`) |
+| `minecraft:dye_blue_bed` | `minecraft:crafting_shapeless` | Lit bleu (`minecraft:blue_bed`) |
+| `minecraft:dye_blue_carpet` | `minecraft:crafting_shapeless` | Tapis bleu (`minecraft:blue_carpet`) |
+| `minecraft:dye_blue_cushion` | `minecraft:crafting_shapeless` | Coussin bleu (`minecraft:blue_cushion`) |
+| `minecraft:dye_blue_wool` | `minecraft:crafting_shapeless` | Laine bleue (`minecraft:blue_wool`) |
+| `minecraft:dye_blue_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine bleue (`minecraft:blue_wool_slab`) |
+| `minecraft:dye_blue_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine bleue (`minecraft:blue_wool_stairs`) |
+| `minecraft:dye_brown_bed` | `minecraft:crafting_shapeless` | Lit marron (`minecraft:brown_bed`) |
+| `minecraft:dye_brown_carpet` | `minecraft:crafting_shapeless` | Tapis marron (`minecraft:brown_carpet`) |
+| `minecraft:dye_brown_cushion` | `minecraft:crafting_shapeless` | Coussin marron (`minecraft:brown_cushion`) |
+| `minecraft:dye_brown_wool` | `minecraft:crafting_shapeless` | Laine marron (`minecraft:brown_wool`) |
+| `minecraft:dye_brown_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine marron (`minecraft:brown_wool_slab`) |
+| `minecraft:dye_brown_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine marron (`minecraft:brown_wool_stairs`) |
+| `minecraft:dye_cyan_bed` | `minecraft:crafting_shapeless` | Lit cyan (`minecraft:cyan_bed`) |
+| `minecraft:dye_cyan_carpet` | `minecraft:crafting_shapeless` | Tapis cyan (`minecraft:cyan_carpet`) |
+| `minecraft:dye_cyan_cushion` | `minecraft:crafting_shapeless` | Coussin cyan (`minecraft:cyan_cushion`) |
+| `minecraft:dye_cyan_wool` | `minecraft:crafting_shapeless` | Laine cyan (`minecraft:cyan_wool`) |
+| `minecraft:dye_cyan_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine cyan (`minecraft:cyan_wool_slab`) |
+| `minecraft:dye_cyan_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine cyan (`minecraft:cyan_wool_stairs`) |
+| `minecraft:dye_gray_bed` | `minecraft:crafting_shapeless` | Lit gris (`minecraft:gray_bed`) |
+| `minecraft:dye_gray_carpet` | `minecraft:crafting_shapeless` | Tapis gris (`minecraft:gray_carpet`) |
+| `minecraft:dye_gray_cushion` | `minecraft:crafting_shapeless` | Coussin gris (`minecraft:gray_cushion`) |
+| `minecraft:dye_gray_wool` | `minecraft:crafting_shapeless` | Laine grise (`minecraft:gray_wool`) |
+| `minecraft:dye_gray_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine grise (`minecraft:gray_wool_slab`) |
+| `minecraft:dye_gray_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine grise (`minecraft:gray_wool_stairs`) |
+| `minecraft:dye_green_bed` | `minecraft:crafting_shapeless` | Lit vert (`minecraft:green_bed`) |
+| `minecraft:dye_green_carpet` | `minecraft:crafting_shapeless` | Tapis vert (`minecraft:green_carpet`) |
+| `minecraft:dye_green_cushion` | `minecraft:crafting_shapeless` | Coussin vert (`minecraft:green_cushion`) |
+| `minecraft:dye_green_wool` | `minecraft:crafting_shapeless` | Laine verte (`minecraft:green_wool`) |
+| `minecraft:dye_green_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine verte (`minecraft:green_wool_slab`) |
+| `minecraft:dye_green_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine verte (`minecraft:green_wool_stairs`) |
+| `minecraft:dye_light_blue_bed` | `minecraft:crafting_shapeless` | Lit bleu clair (`minecraft:light_blue_bed`) |
+| `minecraft:dye_light_blue_carpet` | `minecraft:crafting_shapeless` | Tapis bleu clair (`minecraft:light_blue_carpet`) |
+| `minecraft:dye_light_blue_cushion` | `minecraft:crafting_shapeless` | Coussin bleu clair (`minecraft:light_blue_cushion`) |
+| `minecraft:dye_light_blue_wool` | `minecraft:crafting_shapeless` | Laine bleu clair (`minecraft:light_blue_wool`) |
+| `minecraft:dye_light_blue_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine bleu clair (`minecraft:light_blue_wool_slab`) |
+| `minecraft:dye_light_blue_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine bleu clair (`minecraft:light_blue_wool_stairs`) |
+| `minecraft:dye_light_gray_bed` | `minecraft:crafting_shapeless` | Lit gris clair (`minecraft:light_gray_bed`) |
+| `minecraft:dye_light_gray_carpet` | `minecraft:crafting_shapeless` | Tapis gris clair (`minecraft:light_gray_carpet`) |
+| `minecraft:dye_light_gray_cushion` | `minecraft:crafting_shapeless` | Coussin gris clair (`minecraft:light_gray_cushion`) |
+| `minecraft:dye_light_gray_wool` | `minecraft:crafting_shapeless` | Laine gris clair (`minecraft:light_gray_wool`) |
+| `minecraft:dye_light_gray_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine gris clair (`minecraft:light_gray_wool_slab`) |
+| `minecraft:dye_light_gray_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine gris clair (`minecraft:light_gray_wool_stairs`) |
+| `minecraft:dye_lime_bed` | `minecraft:crafting_shapeless` | Lit vert clair (`minecraft:lime_bed`) |
+| `minecraft:dye_lime_carpet` | `minecraft:crafting_shapeless` | Tapis vert clair (`minecraft:lime_carpet`) |
+| `minecraft:dye_lime_cushion` | `minecraft:crafting_shapeless` | Coussin vert clair (`minecraft:lime_cushion`) |
+| `minecraft:dye_lime_wool` | `minecraft:crafting_shapeless` | Laine vert clair (`minecraft:lime_wool`) |
+| `minecraft:dye_lime_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine vert clair (`minecraft:lime_wool_slab`) |
+| `minecraft:dye_lime_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine vert clair (`minecraft:lime_wool_stairs`) |
+| `minecraft:dye_magenta_bed` | `minecraft:crafting_shapeless` | Lit magenta (`minecraft:magenta_bed`) |
+| `minecraft:dye_magenta_carpet` | `minecraft:crafting_shapeless` | Tapis magenta (`minecraft:magenta_carpet`) |
+| `minecraft:dye_magenta_cushion` | `minecraft:crafting_shapeless` | Coussin magenta (`minecraft:magenta_cushion`) |
+| `minecraft:dye_magenta_wool` | `minecraft:crafting_shapeless` | Laine magenta (`minecraft:magenta_wool`) |
+| `minecraft:dye_magenta_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine magenta (`minecraft:magenta_wool_slab`) |
+| `minecraft:dye_magenta_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine magenta (`minecraft:magenta_wool_stairs`) |
+| `minecraft:dye_orange_bed` | `minecraft:crafting_shapeless` | Lit orange (`minecraft:orange_bed`) |
+| `minecraft:dye_orange_carpet` | `minecraft:crafting_shapeless` | Tapis orange (`minecraft:orange_carpet`) |
+| `minecraft:dye_orange_cushion` | `minecraft:crafting_shapeless` | Coussin orange (`minecraft:orange_cushion`) |
+| `minecraft:dye_orange_wool` | `minecraft:crafting_shapeless` | Laine orange (`minecraft:orange_wool`) |
+| `minecraft:dye_orange_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine orange (`minecraft:orange_wool_slab`) |
+| `minecraft:dye_orange_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine orange (`minecraft:orange_wool_stairs`) |
+| `minecraft:dye_pink_bed` | `minecraft:crafting_shapeless` | Lit rose (`minecraft:pink_bed`) |
+| `minecraft:dye_pink_carpet` | `minecraft:crafting_shapeless` | Tapis rose (`minecraft:pink_carpet`) |
+| `minecraft:dye_pink_cushion` | `minecraft:crafting_shapeless` | Coussin rose (`minecraft:pink_cushion`) |
+| `minecraft:dye_pink_wool` | `minecraft:crafting_shapeless` | Laine rose (`minecraft:pink_wool`) |
+| `minecraft:dye_pink_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine rose (`minecraft:pink_wool_slab`) |
+| `minecraft:dye_pink_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine rose (`minecraft:pink_wool_stairs`) |
+| `minecraft:dye_purple_bed` | `minecraft:crafting_shapeless` | Lit violet (`minecraft:purple_bed`) |
+| `minecraft:dye_purple_carpet` | `minecraft:crafting_shapeless` | Tapis violet (`minecraft:purple_carpet`) |
+| `minecraft:dye_purple_cushion` | `minecraft:crafting_shapeless` | Coussin violet (`minecraft:purple_cushion`) |
+| `minecraft:dye_purple_wool` | `minecraft:crafting_shapeless` | Laine violette (`minecraft:purple_wool`) |
+| `minecraft:dye_purple_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine violette (`minecraft:purple_wool_slab`) |
+| `minecraft:dye_purple_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine violette (`minecraft:purple_wool_stairs`) |
+| `minecraft:dye_red_bed` | `minecraft:crafting_shapeless` | Lit rouge (`minecraft:red_bed`) |
+| `minecraft:dye_red_carpet` | `minecraft:crafting_shapeless` | Tapis rouge (`minecraft:red_carpet`) |
+| `minecraft:dye_red_cushion` | `minecraft:crafting_shapeless` | Coussin rouge (`minecraft:red_cushion`) |
+| `minecraft:dye_red_wool` | `minecraft:crafting_shapeless` | Laine rouge (`minecraft:red_wool`) |
+| `minecraft:dye_red_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine rouge (`minecraft:red_wool_slab`) |
+| `minecraft:dye_red_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine rouge (`minecraft:red_wool_stairs`) |
+| `minecraft:dye_white_bed` | `minecraft:crafting_shapeless` | Lit blanc (`minecraft:white_bed`) |
+| `minecraft:dye_white_carpet` | `minecraft:crafting_shapeless` | Tapis blanc (`minecraft:white_carpet`) |
+| `minecraft:dye_white_cushion` | `minecraft:crafting_shapeless` | Coussin blanc (`minecraft:white_cushion`) |
+| `minecraft:dye_white_wool` | `minecraft:crafting_shapeless` | Laine blanche (`minecraft:white_wool`) |
+| `minecraft:dye_white_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine blanche (`minecraft:white_wool_slab`) |
+| `minecraft:dye_white_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine blanche (`minecraft:white_wool_stairs`) |
+| `minecraft:dye_yellow_bed` | `minecraft:crafting_shapeless` | Lit jaune (`minecraft:yellow_bed`) |
+| `minecraft:dye_yellow_carpet` | `minecraft:crafting_shapeless` | Tapis jaune (`minecraft:yellow_carpet`) |
+| `minecraft:dye_yellow_cushion` | `minecraft:crafting_shapeless` | Coussin jaune (`minecraft:yellow_cushion`) |
+| `minecraft:dye_yellow_wool` | `minecraft:crafting_shapeless` | Laine jaune (`minecraft:yellow_wool`) |
+| `minecraft:dye_yellow_wool_slab` | `minecraft:crafting_shapeless` | Dalle de laine jaune (`minecraft:yellow_wool_slab`) |
+| `minecraft:dye_yellow_wool_stairs` | `minecraft:crafting_shapeless` | Escalier en laine jaune (`minecraft:yellow_wool_stairs`) |
+| `minecraft:gray_bed` | `minecraft:crafting_shaped` | Lit gris (`minecraft:gray_bed`) |
+| `minecraft:gray_carpet` | `minecraft:crafting_shaped` | Tapis gris (`minecraft:gray_carpet`) × 3 |
+| `minecraft:gray_cushion` | `minecraft:crafting_shaped` | Coussin gris (`minecraft:gray_cushion`) |
+| `minecraft:gray_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine grise (`minecraft:gray_wool_slab`) × 6 |
+| `minecraft:gray_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine grise (`minecraft:gray_wool_stairs`) × 4 |
+| `minecraft:green_bed` | `minecraft:crafting_shaped` | Lit vert (`minecraft:green_bed`) |
+| `minecraft:green_carpet` | `minecraft:crafting_shaped` | Tapis vert (`minecraft:green_carpet`) × 3 |
+| `minecraft:green_cushion` | `minecraft:crafting_shaped` | Coussin vert (`minecraft:green_cushion`) |
+| `minecraft:green_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine verte (`minecraft:green_wool_slab`) × 6 |
+| `minecraft:green_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine verte (`minecraft:green_wool_stairs`) × 4 |
+| `minecraft:light_blue_bed` | `minecraft:crafting_shaped` | Lit bleu clair (`minecraft:light_blue_bed`) |
+| `minecraft:light_blue_carpet` | `minecraft:crafting_shaped` | Tapis bleu clair (`minecraft:light_blue_carpet`) × 3 |
+| `minecraft:light_blue_cushion` | `minecraft:crafting_shaped` | Coussin bleu clair (`minecraft:light_blue_cushion`) |
+| `minecraft:light_blue_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine bleu clair (`minecraft:light_blue_wool_slab`) × 6 |
+| `minecraft:light_blue_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine bleu clair (`minecraft:light_blue_wool_stairs`) × 4 |
+| `minecraft:light_gray_bed` | `minecraft:crafting_shaped` | Lit gris clair (`minecraft:light_gray_bed`) |
+| `minecraft:light_gray_carpet` | `minecraft:crafting_shaped` | Tapis gris clair (`minecraft:light_gray_carpet`) × 3 |
+| `minecraft:light_gray_cushion` | `minecraft:crafting_shaped` | Coussin gris clair (`minecraft:light_gray_cushion`) |
+| `minecraft:light_gray_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine gris clair (`minecraft:light_gray_wool_slab`) × 6 |
+| `minecraft:light_gray_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine gris clair (`minecraft:light_gray_wool_stairs`) × 4 |
+| `minecraft:lime_bed` | `minecraft:crafting_shaped` | Lit vert clair (`minecraft:lime_bed`) |
+| `minecraft:lime_carpet` | `minecraft:crafting_shaped` | Tapis vert clair (`minecraft:lime_carpet`) × 3 |
+| `minecraft:lime_cushion` | `minecraft:crafting_shaped` | Coussin vert clair (`minecraft:lime_cushion`) |
+| `minecraft:lime_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine vert clair (`minecraft:lime_wool_slab`) × 6 |
+| `minecraft:lime_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine vert clair (`minecraft:lime_wool_stairs`) × 4 |
+| `minecraft:magenta_bed` | `minecraft:crafting_shaped` | Lit magenta (`minecraft:magenta_bed`) |
+| `minecraft:magenta_carpet` | `minecraft:crafting_shaped` | Tapis magenta (`minecraft:magenta_carpet`) × 3 |
+| `minecraft:magenta_cushion` | `minecraft:crafting_shaped` | Coussin magenta (`minecraft:magenta_cushion`) |
+| `minecraft:magenta_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine magenta (`minecraft:magenta_wool_slab`) × 6 |
+| `minecraft:magenta_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine magenta (`minecraft:magenta_wool_stairs`) × 4 |
+| `minecraft:orange_bed` | `minecraft:crafting_shaped` | Lit orange (`minecraft:orange_bed`) |
+| `minecraft:orange_carpet` | `minecraft:crafting_shaped` | Tapis orange (`minecraft:orange_carpet`) × 3 |
+| `minecraft:orange_cushion` | `minecraft:crafting_shaped` | Coussin orange (`minecraft:orange_cushion`) |
+| `minecraft:orange_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine orange (`minecraft:orange_wool_slab`) × 6 |
+| `minecraft:orange_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine orange (`minecraft:orange_wool_stairs`) × 4 |
+| `minecraft:pink_bed` | `minecraft:crafting_shaped` | Lit rose (`minecraft:pink_bed`) |
+| `minecraft:pink_carpet` | `minecraft:crafting_shaped` | Tapis rose (`minecraft:pink_carpet`) × 3 |
+| `minecraft:pink_cushion` | `minecraft:crafting_shaped` | Coussin rose (`minecraft:pink_cushion`) |
+| `minecraft:pink_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine rose (`minecraft:pink_wool_slab`) × 6 |
+| `minecraft:pink_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine rose (`minecraft:pink_wool_stairs`) × 4 |
+| `minecraft:purple_bed` | `minecraft:crafting_shaped` | Lit violet (`minecraft:purple_bed`) |
+| `minecraft:purple_carpet` | `minecraft:crafting_shaped` | Tapis violet (`minecraft:purple_carpet`) × 3 |
+| `minecraft:purple_cushion` | `minecraft:crafting_shaped` | Coussin violet (`minecraft:purple_cushion`) |
+| `minecraft:purple_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine violette (`minecraft:purple_wool_slab`) × 6 |
+| `minecraft:purple_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine violette (`minecraft:purple_wool_stairs`) × 4 |
+| `minecraft:red_bed` | `minecraft:crafting_shaped` | Lit rouge (`minecraft:red_bed`) |
+| `minecraft:red_carpet` | `minecraft:crafting_shaped` | Tapis rouge (`minecraft:red_carpet`) × 3 |
+| `minecraft:red_cushion` | `minecraft:crafting_shaped` | Coussin rouge (`minecraft:red_cushion`) |
+| `minecraft:red_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine rouge (`minecraft:red_wool_slab`) × 6 |
+| `minecraft:red_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine rouge (`minecraft:red_wool_stairs`) × 4 |
+| `minecraft:straw_bed` | `minecraft:crafting_shaped` | Lit de paille (`minecraft:straw_bed`) × 4 |
+| `minecraft:white_bed` | `minecraft:crafting_shaped` | Lit blanc (`minecraft:white_bed`) |
+| `minecraft:white_carpet` | `minecraft:crafting_shaped` | Tapis blanc (`minecraft:white_carpet`) × 3 |
+| `minecraft:white_cushion` | `minecraft:crafting_shaped` | Coussin blanc (`minecraft:white_cushion`) |
+| `minecraft:white_wool_from_string` | `minecraft:crafting_shaped` | Laine blanche (`minecraft:white_wool`) |
+| `minecraft:white_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine blanche (`minecraft:white_wool_slab`) × 6 |
+| `minecraft:white_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine blanche (`minecraft:white_wool_stairs`) × 4 |
+| `minecraft:yellow_bed` | `minecraft:crafting_shaped` | Lit jaune (`minecraft:yellow_bed`) |
+| `minecraft:yellow_carpet` | `minecraft:crafting_shaped` | Tapis jaune (`minecraft:yellow_carpet`) × 3 |
+| `minecraft:yellow_cushion` | `minecraft:crafting_shaped` | Coussin jaune (`minecraft:yellow_cushion`) |
+| `minecraft:yellow_wool_slab` | `minecraft:crafting_shaped` | Dalle de laine jaune (`minecraft:yellow_wool_slab`) × 6 |
+| `minecraft:yellow_wool_stairs` | `minecraft:crafting_shaped` | Escalier en laine jaune (`minecraft:yellow_wool_stairs`) × 4 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_bed` | Lit noir | Bloc et objet | 2 |
+| `minecraft:black_carpet` | Tapis noir | Bloc et objet | 2 |
+| `minecraft:black_cushion` | Coussin noir | Objet | 2 |
+| `minecraft:black_wool` | Laine noire | Bloc et objet | 1 |
+| `minecraft:black_wool_slab` | Dalle de laine noire | Bloc et objet | 2 |
+| `minecraft:black_wool_stairs` | Escalier en laine noire | Bloc et objet | 2 |
+| `minecraft:blue_bed` | Lit bleu | Bloc et objet | 2 |
+| `minecraft:blue_carpet` | Tapis bleu | Bloc et objet | 2 |
+| `minecraft:blue_cushion` | Coussin bleu | Objet | 2 |
+| `minecraft:blue_wool` | Laine bleue | Bloc et objet | 1 |
+| `minecraft:blue_wool_slab` | Dalle de laine bleue | Bloc et objet | 2 |
+| `minecraft:blue_wool_stairs` | Escalier en laine bleue | Bloc et objet | 2 |
+| `minecraft:brown_bed` | Lit marron | Bloc et objet | 2 |
+| `minecraft:brown_carpet` | Tapis marron | Bloc et objet | 2 |
+| `minecraft:brown_cushion` | Coussin marron | Objet | 2 |
+| `minecraft:brown_wool` | Laine marron | Bloc et objet | 1 |
+| `minecraft:brown_wool_slab` | Dalle de laine marron | Bloc et objet | 2 |
+| `minecraft:brown_wool_stairs` | Escalier en laine marron | Bloc et objet | 2 |
+| `minecraft:cyan_bed` | Lit cyan | Bloc et objet | 2 |
+| `minecraft:cyan_carpet` | Tapis cyan | Bloc et objet | 2 |
+| `minecraft:cyan_cushion` | Coussin cyan | Objet | 2 |
+| `minecraft:cyan_wool` | Laine cyan | Bloc et objet | 1 |
+| `minecraft:cyan_wool_slab` | Dalle de laine cyan | Bloc et objet | 2 |
+| `minecraft:cyan_wool_stairs` | Escalier en laine cyan | Bloc et objet | 2 |
+| `minecraft:gray_bed` | Lit gris | Bloc et objet | 2 |
+| `minecraft:gray_carpet` | Tapis gris | Bloc et objet | 2 |
+| `minecraft:gray_cushion` | Coussin gris | Objet | 2 |
+| `minecraft:gray_wool` | Laine grise | Bloc et objet | 1 |
+| `minecraft:gray_wool_slab` | Dalle de laine grise | Bloc et objet | 2 |
+| `minecraft:gray_wool_stairs` | Escalier en laine grise | Bloc et objet | 2 |
+| `minecraft:green_bed` | Lit vert | Bloc et objet | 2 |
+| `minecraft:green_carpet` | Tapis vert | Bloc et objet | 2 |
+| `minecraft:green_cushion` | Coussin vert | Objet | 2 |
+| `minecraft:green_wool` | Laine verte | Bloc et objet | 1 |
+| `minecraft:green_wool_slab` | Dalle de laine verte | Bloc et objet | 2 |
+| `minecraft:green_wool_stairs` | Escalier en laine verte | Bloc et objet | 2 |
+| `minecraft:light_blue_bed` | Lit bleu clair | Bloc et objet | 2 |
+| `minecraft:light_blue_carpet` | Tapis bleu clair | Bloc et objet | 2 |
+| `minecraft:light_blue_cushion` | Coussin bleu clair | Objet | 2 |
+| `minecraft:light_blue_wool` | Laine bleu clair | Bloc et objet | 1 |
+| `minecraft:light_blue_wool_slab` | Dalle de laine bleu clair | Bloc et objet | 2 |
+| `minecraft:light_blue_wool_stairs` | Escalier en laine bleu clair | Bloc et objet | 2 |
+| `minecraft:light_gray_bed` | Lit gris clair | Bloc et objet | 2 |
+| `minecraft:light_gray_carpet` | Tapis gris clair | Bloc et objet | 2 |
+| `minecraft:light_gray_cushion` | Coussin gris clair | Objet | 2 |
+| `minecraft:light_gray_wool` | Laine gris clair | Bloc et objet | 1 |
+| `minecraft:light_gray_wool_slab` | Dalle de laine gris clair | Bloc et objet | 2 |
+| `minecraft:light_gray_wool_stairs` | Escalier en laine gris clair | Bloc et objet | 2 |
+| `minecraft:lime_bed` | Lit vert clair | Bloc et objet | 2 |
+| `minecraft:lime_carpet` | Tapis vert clair | Bloc et objet | 2 |
+| `minecraft:lime_cushion` | Coussin vert clair | Objet | 2 |
+| `minecraft:lime_wool` | Laine vert clair | Bloc et objet | 1 |
+| `minecraft:lime_wool_slab` | Dalle de laine vert clair | Bloc et objet | 2 |
+| `minecraft:lime_wool_stairs` | Escalier en laine vert clair | Bloc et objet | 2 |
+| `minecraft:magenta_bed` | Lit magenta | Bloc et objet | 2 |
+| `minecraft:magenta_carpet` | Tapis magenta | Bloc et objet | 2 |
+| `minecraft:magenta_cushion` | Coussin magenta | Objet | 2 |
+| `minecraft:magenta_wool` | Laine magenta | Bloc et objet | 1 |
+| `minecraft:magenta_wool_slab` | Dalle de laine magenta | Bloc et objet | 2 |
+| `minecraft:magenta_wool_stairs` | Escalier en laine magenta | Bloc et objet | 2 |
+| `minecraft:orange_bed` | Lit orange | Bloc et objet | 2 |
+| `minecraft:orange_carpet` | Tapis orange | Bloc et objet | 2 |
+| `minecraft:orange_cushion` | Coussin orange | Objet | 2 |
+| `minecraft:orange_wool` | Laine orange | Bloc et objet | 1 |
+| `minecraft:orange_wool_slab` | Dalle de laine orange | Bloc et objet | 2 |
+| `minecraft:orange_wool_stairs` | Escalier en laine orange | Bloc et objet | 2 |
+| `minecraft:pink_bed` | Lit rose | Bloc et objet | 2 |
+| `minecraft:pink_carpet` | Tapis rose | Bloc et objet | 2 |
+| `minecraft:pink_cushion` | Coussin rose | Objet | 2 |
+| `minecraft:pink_wool` | Laine rose | Bloc et objet | 1 |
+| `minecraft:pink_wool_slab` | Dalle de laine rose | Bloc et objet | 2 |
+| `minecraft:pink_wool_stairs` | Escalier en laine rose | Bloc et objet | 2 |
+| `minecraft:purple_bed` | Lit violet | Bloc et objet | 2 |
+| `minecraft:purple_carpet` | Tapis violet | Bloc et objet | 2 |
+| `minecraft:purple_cushion` | Coussin violet | Objet | 2 |
+| `minecraft:purple_wool` | Laine violette | Bloc et objet | 1 |
+| `minecraft:purple_wool_slab` | Dalle de laine violette | Bloc et objet | 2 |
+| `minecraft:purple_wool_stairs` | Escalier en laine violette | Bloc et objet | 2 |
+| `minecraft:red_bed` | Lit rouge | Bloc et objet | 2 |
+| `minecraft:red_carpet` | Tapis rouge | Bloc et objet | 2 |
+| `minecraft:red_cushion` | Coussin rouge | Objet | 2 |
+| `minecraft:red_wool` | Laine rouge | Bloc et objet | 1 |
+| `minecraft:red_wool_slab` | Dalle de laine rouge | Bloc et objet | 2 |
+| `minecraft:red_wool_stairs` | Escalier en laine rouge | Bloc et objet | 2 |
+| `minecraft:straw_bed` | Lit de paille | Bloc et objet | 1 |
+| `minecraft:white_bed` | Lit blanc | Bloc et objet | 2 |
+| `minecraft:white_carpet` | Tapis blanc | Bloc et objet | 2 |
+| `minecraft:white_cushion` | Coussin blanc | Objet | 2 |
+| `minecraft:white_wool` | Laine blanche | Bloc et objet | 2 |
+| `minecraft:white_wool_slab` | Dalle de laine blanche | Bloc et objet | 2 |
+| `minecraft:white_wool_stairs` | Escalier en laine blanche | Bloc et objet | 2 |
+| `minecraft:yellow_bed` | Lit jaune | Bloc et objet | 2 |
+| `minecraft:yellow_carpet` | Tapis jaune | Bloc et objet | 2 |
+| `minecraft:yellow_cushion` | Coussin jaune | Objet | 2 |
+| `minecraft:yellow_wool` | Laine jaune | Bloc et objet | 1 |
+| `minecraft:yellow_wool_slab` | Dalle de laine jaune | Bloc et objet | 2 |
+| `minecraft:yellow_wool_stairs` | Escalier en laine jaune | Bloc et objet | 2 |
+
+## C11
+
+**Terres cuites · Terracotta** — [déclencheurs et règles](collections-minecraft.md#c11).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée noire (`minecraft:black_glazed_terracotta`) |
+| `minecraft:black_terracotta` | `minecraft:crafting_shaped` | Terre cuite noire (`minecraft:black_terracotta`) × 8 |
+| `minecraft:blue_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée bleue (`minecraft:blue_glazed_terracotta`) |
+| `minecraft:blue_terracotta` | `minecraft:crafting_shaped` | Terre cuite bleue (`minecraft:blue_terracotta`) × 8 |
+| `minecraft:brown_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée marron (`minecraft:brown_glazed_terracotta`) |
+| `minecraft:brown_terracotta` | `minecraft:crafting_shaped` | Terre cuite marron (`minecraft:brown_terracotta`) × 8 |
+| `minecraft:cyan_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée cyan (`minecraft:cyan_glazed_terracotta`) |
+| `minecraft:cyan_terracotta` | `minecraft:crafting_shaped` | Terre cuite cyan (`minecraft:cyan_terracotta`) × 8 |
+| `minecraft:gray_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée grise (`minecraft:gray_glazed_terracotta`) |
+| `minecraft:gray_terracotta` | `minecraft:crafting_shaped` | Terre cuite grise (`minecraft:gray_terracotta`) × 8 |
+| `minecraft:green_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée verte (`minecraft:green_glazed_terracotta`) |
+| `minecraft:green_terracotta` | `minecraft:crafting_shaped` | Terre cuite verte (`minecraft:green_terracotta`) × 8 |
+| `minecraft:light_blue_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée bleu clair (`minecraft:light_blue_glazed_terracotta`) |
+| `minecraft:light_blue_terracotta` | `minecraft:crafting_shaped` | Terre cuite bleu clair (`minecraft:light_blue_terracotta`) × 8 |
+| `minecraft:light_gray_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée gris clair (`minecraft:light_gray_glazed_terracotta`) |
+| `minecraft:light_gray_terracotta` | `minecraft:crafting_shaped` | Terre cuite gris clair (`minecraft:light_gray_terracotta`) × 8 |
+| `minecraft:lime_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée vert clair (`minecraft:lime_glazed_terracotta`) |
+| `minecraft:lime_terracotta` | `minecraft:crafting_shaped` | Terre cuite vert clair (`minecraft:lime_terracotta`) × 8 |
+| `minecraft:magenta_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée magenta (`minecraft:magenta_glazed_terracotta`) |
+| `minecraft:magenta_terracotta` | `minecraft:crafting_shaped` | Terre cuite magenta (`minecraft:magenta_terracotta`) × 8 |
+| `minecraft:orange_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée orange (`minecraft:orange_glazed_terracotta`) |
+| `minecraft:orange_terracotta` | `minecraft:crafting_shaped` | Terre cuite orange (`minecraft:orange_terracotta`) × 8 |
+| `minecraft:pink_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée rose (`minecraft:pink_glazed_terracotta`) |
+| `minecraft:pink_terracotta` | `minecraft:crafting_shaped` | Terre cuite rose (`minecraft:pink_terracotta`) × 8 |
+| `minecraft:purple_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée violette (`minecraft:purple_glazed_terracotta`) |
+| `minecraft:purple_terracotta` | `minecraft:crafting_shaped` | Terre cuite violette (`minecraft:purple_terracotta`) × 8 |
+| `minecraft:red_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée rouge (`minecraft:red_glazed_terracotta`) |
+| `minecraft:red_terracotta` | `minecraft:crafting_shaped` | Terre cuite rouge (`minecraft:red_terracotta`) × 8 |
+| `minecraft:terracotta` | `minecraft:smelting` | Terre cuite (`minecraft:terracotta`) |
+| `minecraft:white_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée blanche (`minecraft:white_glazed_terracotta`) |
+| `minecraft:white_terracotta` | `minecraft:crafting_shaped` | Terre cuite blanche (`minecraft:white_terracotta`) × 8 |
+| `minecraft:yellow_glazed_terracotta` | `minecraft:smelting` | Terre cuite émaillée jaune (`minecraft:yellow_glazed_terracotta`) |
+| `minecraft:yellow_terracotta` | `minecraft:crafting_shaped` | Terre cuite jaune (`minecraft:yellow_terracotta`) × 8 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_glazed_terracotta` | Terre cuite émaillée noire | Bloc et objet | 1 |
+| `minecraft:black_terracotta` | Terre cuite noire | Bloc et objet | 1 |
+| `minecraft:blue_glazed_terracotta` | Terre cuite émaillée bleue | Bloc et objet | 1 |
+| `minecraft:blue_terracotta` | Terre cuite bleue | Bloc et objet | 1 |
+| `minecraft:brown_glazed_terracotta` | Terre cuite émaillée marron | Bloc et objet | 1 |
+| `minecraft:brown_terracotta` | Terre cuite marron | Bloc et objet | 1 |
+| `minecraft:cyan_glazed_terracotta` | Terre cuite émaillée cyan | Bloc et objet | 1 |
+| `minecraft:cyan_terracotta` | Terre cuite cyan | Bloc et objet | 1 |
+| `minecraft:gray_glazed_terracotta` | Terre cuite émaillée grise | Bloc et objet | 1 |
+| `minecraft:gray_terracotta` | Terre cuite grise | Bloc et objet | 1 |
+| `minecraft:green_glazed_terracotta` | Terre cuite émaillée verte | Bloc et objet | 1 |
+| `minecraft:green_terracotta` | Terre cuite verte | Bloc et objet | 1 |
+| `minecraft:light_blue_glazed_terracotta` | Terre cuite émaillée bleu clair | Bloc et objet | 1 |
+| `minecraft:light_blue_terracotta` | Terre cuite bleu clair | Bloc et objet | 1 |
+| `minecraft:light_gray_glazed_terracotta` | Terre cuite émaillée gris clair | Bloc et objet | 1 |
+| `minecraft:light_gray_terracotta` | Terre cuite gris clair | Bloc et objet | 1 |
+| `minecraft:lime_glazed_terracotta` | Terre cuite émaillée vert clair | Bloc et objet | 1 |
+| `minecraft:lime_terracotta` | Terre cuite vert clair | Bloc et objet | 1 |
+| `minecraft:magenta_glazed_terracotta` | Terre cuite émaillée magenta | Bloc et objet | 1 |
+| `minecraft:magenta_terracotta` | Terre cuite magenta | Bloc et objet | 1 |
+| `minecraft:orange_glazed_terracotta` | Terre cuite émaillée orange | Bloc et objet | 1 |
+| `minecraft:orange_terracotta` | Terre cuite orange | Bloc et objet | 1 |
+| `minecraft:pink_glazed_terracotta` | Terre cuite émaillée rose | Bloc et objet | 1 |
+| `minecraft:pink_terracotta` | Terre cuite rose | Bloc et objet | 1 |
+| `minecraft:purple_glazed_terracotta` | Terre cuite émaillée violette | Bloc et objet | 1 |
+| `minecraft:purple_terracotta` | Terre cuite violette | Bloc et objet | 1 |
+| `minecraft:red_glazed_terracotta` | Terre cuite émaillée rouge | Bloc et objet | 1 |
+| `minecraft:red_terracotta` | Terre cuite rouge | Bloc et objet | 1 |
+| `minecraft:terracotta` | Terre cuite | Bloc et objet | 1 |
+| `minecraft:white_glazed_terracotta` | Terre cuite émaillée blanche | Bloc et objet | 1 |
+| `minecraft:white_terracotta` | Terre cuite blanche | Bloc et objet | 1 |
+| `minecraft:yellow_glazed_terracotta` | Terre cuite émaillée jaune | Bloc et objet | 1 |
+| `minecraft:yellow_terracotta` | Terre cuite jaune | Bloc et objet | 1 |
+
+## C12
+
+**Bétons · Concrete** — [déclencheurs et règles](collections-minecraft.md#c12).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_concrete_powder` | `minecraft:crafting_shapeless` | Béton noir en poudre (`minecraft:black_concrete_powder`) × 8 |
+| `minecraft:black_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton noir (`minecraft:black_concrete_slab`) × 6 |
+| `minecraft:black_concrete_slab_from_black_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton noir (`minecraft:black_concrete_slab`) × 2 |
+| `minecraft:black_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton noir (`minecraft:black_concrete_stairs`) × 4 |
+| `minecraft:black_concrete_stairs_from_black_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton noir (`minecraft:black_concrete_stairs`) |
+| `minecraft:blue_concrete_powder` | `minecraft:crafting_shapeless` | Béton bleu en poudre (`minecraft:blue_concrete_powder`) × 8 |
+| `minecraft:blue_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton bleu (`minecraft:blue_concrete_slab`) × 6 |
+| `minecraft:blue_concrete_slab_from_blue_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton bleu (`minecraft:blue_concrete_slab`) × 2 |
+| `minecraft:blue_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton bleu (`minecraft:blue_concrete_stairs`) × 4 |
+| `minecraft:blue_concrete_stairs_from_blue_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton bleu (`minecraft:blue_concrete_stairs`) |
+| `minecraft:brown_concrete_powder` | `minecraft:crafting_shapeless` | Béton marron en poudre (`minecraft:brown_concrete_powder`) × 8 |
+| `minecraft:brown_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton marron (`minecraft:brown_concrete_slab`) × 6 |
+| `minecraft:brown_concrete_slab_from_brown_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton marron (`minecraft:brown_concrete_slab`) × 2 |
+| `minecraft:brown_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton marron (`minecraft:brown_concrete_stairs`) × 4 |
+| `minecraft:brown_concrete_stairs_from_brown_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton marron (`minecraft:brown_concrete_stairs`) |
+| `minecraft:cyan_concrete_powder` | `minecraft:crafting_shapeless` | Béton cyan en poudre (`minecraft:cyan_concrete_powder`) × 8 |
+| `minecraft:cyan_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton cyan (`minecraft:cyan_concrete_slab`) × 6 |
+| `minecraft:cyan_concrete_slab_from_cyan_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton cyan (`minecraft:cyan_concrete_slab`) × 2 |
+| `minecraft:cyan_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton cyan (`minecraft:cyan_concrete_stairs`) × 4 |
+| `minecraft:cyan_concrete_stairs_from_cyan_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton cyan (`minecraft:cyan_concrete_stairs`) |
+| `minecraft:gray_concrete_powder` | `minecraft:crafting_shapeless` | Béton gris en poudre (`minecraft:gray_concrete_powder`) × 8 |
+| `minecraft:gray_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton gris (`minecraft:gray_concrete_slab`) × 6 |
+| `minecraft:gray_concrete_slab_from_gray_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton gris (`minecraft:gray_concrete_slab`) × 2 |
+| `minecraft:gray_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton gris (`minecraft:gray_concrete_stairs`) × 4 |
+| `minecraft:gray_concrete_stairs_from_gray_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton gris (`minecraft:gray_concrete_stairs`) |
+| `minecraft:green_concrete_powder` | `minecraft:crafting_shapeless` | Béton vert en poudre (`minecraft:green_concrete_powder`) × 8 |
+| `minecraft:green_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton vert (`minecraft:green_concrete_slab`) × 6 |
+| `minecraft:green_concrete_slab_from_green_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton vert (`minecraft:green_concrete_slab`) × 2 |
+| `minecraft:green_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton vert (`minecraft:green_concrete_stairs`) × 4 |
+| `minecraft:green_concrete_stairs_from_green_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton vert (`minecraft:green_concrete_stairs`) |
+| `minecraft:light_blue_concrete_powder` | `minecraft:crafting_shapeless` | Béton bleu clair en poudre (`minecraft:light_blue_concrete_powder`) × 8 |
+| `minecraft:light_blue_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton bleu clair (`minecraft:light_blue_concrete_slab`) × 6 |
+| `minecraft:light_blue_concrete_slab_from_light_blue_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton bleu clair (`minecraft:light_blue_concrete_slab`) × 2 |
+| `minecraft:light_blue_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton bleu clair (`minecraft:light_blue_concrete_stairs`) × 4 |
+| `minecraft:light_blue_concrete_stairs_from_light_blue_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton bleu clair (`minecraft:light_blue_concrete_stairs`) |
+| `minecraft:light_gray_concrete_powder` | `minecraft:crafting_shapeless` | Béton gris clair en poudre (`minecraft:light_gray_concrete_powder`) × 8 |
+| `minecraft:light_gray_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton gris clair (`minecraft:light_gray_concrete_slab`) × 6 |
+| `minecraft:light_gray_concrete_slab_from_light_gray_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton gris clair (`minecraft:light_gray_concrete_slab`) × 2 |
+| `minecraft:light_gray_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton gris clair (`minecraft:light_gray_concrete_stairs`) × 4 |
+| `minecraft:light_gray_concrete_stairs_from_light_gray_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton gris clair (`minecraft:light_gray_concrete_stairs`) |
+| `minecraft:lime_concrete_powder` | `minecraft:crafting_shapeless` | Béton vert clair en poudre (`minecraft:lime_concrete_powder`) × 8 |
+| `minecraft:lime_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton vert clair (`minecraft:lime_concrete_slab`) × 6 |
+| `minecraft:lime_concrete_slab_from_lime_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton vert clair (`minecraft:lime_concrete_slab`) × 2 |
+| `minecraft:lime_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton vert clair (`minecraft:lime_concrete_stairs`) × 4 |
+| `minecraft:lime_concrete_stairs_from_lime_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton vert clair (`minecraft:lime_concrete_stairs`) |
+| `minecraft:magenta_concrete_powder` | `minecraft:crafting_shapeless` | Béton magenta en poudre (`minecraft:magenta_concrete_powder`) × 8 |
+| `minecraft:magenta_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton magenta (`minecraft:magenta_concrete_slab`) × 6 |
+| `minecraft:magenta_concrete_slab_from_magenta_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton magenta (`minecraft:magenta_concrete_slab`) × 2 |
+| `minecraft:magenta_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton magenta (`minecraft:magenta_concrete_stairs`) × 4 |
+| `minecraft:magenta_concrete_stairs_from_magenta_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton magenta (`minecraft:magenta_concrete_stairs`) |
+| `minecraft:orange_concrete_powder` | `minecraft:crafting_shapeless` | Béton orange en poudre (`minecraft:orange_concrete_powder`) × 8 |
+| `minecraft:orange_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton orange (`minecraft:orange_concrete_slab`) × 6 |
+| `minecraft:orange_concrete_slab_from_orange_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton orange (`minecraft:orange_concrete_slab`) × 2 |
+| `minecraft:orange_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton orange (`minecraft:orange_concrete_stairs`) × 4 |
+| `minecraft:orange_concrete_stairs_from_orange_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton orange (`minecraft:orange_concrete_stairs`) |
+| `minecraft:pink_concrete_powder` | `minecraft:crafting_shapeless` | Béton rose en poudre (`minecraft:pink_concrete_powder`) × 8 |
+| `minecraft:pink_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton rose (`minecraft:pink_concrete_slab`) × 6 |
+| `minecraft:pink_concrete_slab_from_pink_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton rose (`minecraft:pink_concrete_slab`) × 2 |
+| `minecraft:pink_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton rose (`minecraft:pink_concrete_stairs`) × 4 |
+| `minecraft:pink_concrete_stairs_from_pink_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton rose (`minecraft:pink_concrete_stairs`) |
+| `minecraft:purple_concrete_powder` | `minecraft:crafting_shapeless` | Béton violet en poudre (`minecraft:purple_concrete_powder`) × 8 |
+| `minecraft:purple_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton violet (`minecraft:purple_concrete_slab`) × 6 |
+| `minecraft:purple_concrete_slab_from_purple_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton violet (`minecraft:purple_concrete_slab`) × 2 |
+| `minecraft:purple_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton violet (`minecraft:purple_concrete_stairs`) × 4 |
+| `minecraft:purple_concrete_stairs_from_purple_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton violet (`minecraft:purple_concrete_stairs`) |
+| `minecraft:red_concrete_powder` | `minecraft:crafting_shapeless` | Béton rouge en poudre (`minecraft:red_concrete_powder`) × 8 |
+| `minecraft:red_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton rouge (`minecraft:red_concrete_slab`) × 6 |
+| `minecraft:red_concrete_slab_from_red_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton rouge (`minecraft:red_concrete_slab`) × 2 |
+| `minecraft:red_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton rouge (`minecraft:red_concrete_stairs`) × 4 |
+| `minecraft:red_concrete_stairs_from_red_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton rouge (`minecraft:red_concrete_stairs`) |
+| `minecraft:white_concrete_powder` | `minecraft:crafting_shapeless` | Béton blanc en poudre (`minecraft:white_concrete_powder`) × 8 |
+| `minecraft:white_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton blanc (`minecraft:white_concrete_slab`) × 6 |
+| `minecraft:white_concrete_slab_from_white_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton blanc (`minecraft:white_concrete_slab`) × 2 |
+| `minecraft:white_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton blanc (`minecraft:white_concrete_stairs`) × 4 |
+| `minecraft:white_concrete_stairs_from_white_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton blanc (`minecraft:white_concrete_stairs`) |
+| `minecraft:yellow_concrete_powder` | `minecraft:crafting_shapeless` | Béton jaune en poudre (`minecraft:yellow_concrete_powder`) × 8 |
+| `minecraft:yellow_concrete_slab` | `minecraft:crafting_shaped` | Dalle de béton jaune (`minecraft:yellow_concrete_slab`) × 6 |
+| `minecraft:yellow_concrete_slab_from_yellow_concrete_stonecutting` | `minecraft:stonecutting` | Dalle de béton jaune (`minecraft:yellow_concrete_slab`) × 2 |
+| `minecraft:yellow_concrete_stairs` | `minecraft:crafting_shaped` | Escalier en béton jaune (`minecraft:yellow_concrete_stairs`) × 4 |
+| `minecraft:yellow_concrete_stairs_from_yellow_concrete_stonecutting` | `minecraft:stonecutting` | Escalier en béton jaune (`minecraft:yellow_concrete_stairs`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_concrete` | Béton noir | Bloc et objet | 0 |
+| `minecraft:black_concrete_powder` | Béton noir en poudre | Bloc et objet | 1 |
+| `minecraft:black_concrete_slab` | Dalle de béton noir | Bloc et objet | 2 |
+| `minecraft:black_concrete_stairs` | Escalier en béton noir | Bloc et objet | 2 |
+| `minecraft:blue_concrete` | Béton bleu | Bloc et objet | 0 |
+| `minecraft:blue_concrete_powder` | Béton bleu en poudre | Bloc et objet | 1 |
+| `minecraft:blue_concrete_slab` | Dalle de béton bleu | Bloc et objet | 2 |
+| `minecraft:blue_concrete_stairs` | Escalier en béton bleu | Bloc et objet | 2 |
+| `minecraft:brown_concrete` | Béton marron | Bloc et objet | 0 |
+| `minecraft:brown_concrete_powder` | Béton marron en poudre | Bloc et objet | 1 |
+| `minecraft:brown_concrete_slab` | Dalle de béton marron | Bloc et objet | 2 |
+| `minecraft:brown_concrete_stairs` | Escalier en béton marron | Bloc et objet | 2 |
+| `minecraft:cyan_concrete` | Béton cyan | Bloc et objet | 0 |
+| `minecraft:cyan_concrete_powder` | Béton cyan en poudre | Bloc et objet | 1 |
+| `minecraft:cyan_concrete_slab` | Dalle de béton cyan | Bloc et objet | 2 |
+| `minecraft:cyan_concrete_stairs` | Escalier en béton cyan | Bloc et objet | 2 |
+| `minecraft:gray_concrete` | Béton gris | Bloc et objet | 0 |
+| `minecraft:gray_concrete_powder` | Béton gris en poudre | Bloc et objet | 1 |
+| `minecraft:gray_concrete_slab` | Dalle de béton gris | Bloc et objet | 2 |
+| `minecraft:gray_concrete_stairs` | Escalier en béton gris | Bloc et objet | 2 |
+| `minecraft:green_concrete` | Béton vert | Bloc et objet | 0 |
+| `minecraft:green_concrete_powder` | Béton vert en poudre | Bloc et objet | 1 |
+| `minecraft:green_concrete_slab` | Dalle de béton vert | Bloc et objet | 2 |
+| `minecraft:green_concrete_stairs` | Escalier en béton vert | Bloc et objet | 2 |
+| `minecraft:light_blue_concrete` | Béton bleu clair | Bloc et objet | 0 |
+| `minecraft:light_blue_concrete_powder` | Béton bleu clair en poudre | Bloc et objet | 1 |
+| `minecraft:light_blue_concrete_slab` | Dalle de béton bleu clair | Bloc et objet | 2 |
+| `minecraft:light_blue_concrete_stairs` | Escalier en béton bleu clair | Bloc et objet | 2 |
+| `minecraft:light_gray_concrete` | Béton gris clair | Bloc et objet | 0 |
+| `minecraft:light_gray_concrete_powder` | Béton gris clair en poudre | Bloc et objet | 1 |
+| `minecraft:light_gray_concrete_slab` | Dalle de béton gris clair | Bloc et objet | 2 |
+| `minecraft:light_gray_concrete_stairs` | Escalier en béton gris clair | Bloc et objet | 2 |
+| `minecraft:lime_concrete` | Béton vert clair | Bloc et objet | 0 |
+| `minecraft:lime_concrete_powder` | Béton vert clair en poudre | Bloc et objet | 1 |
+| `minecraft:lime_concrete_slab` | Dalle de béton vert clair | Bloc et objet | 2 |
+| `minecraft:lime_concrete_stairs` | Escalier en béton vert clair | Bloc et objet | 2 |
+| `minecraft:magenta_concrete` | Béton magenta | Bloc et objet | 0 |
+| `minecraft:magenta_concrete_powder` | Béton magenta en poudre | Bloc et objet | 1 |
+| `minecraft:magenta_concrete_slab` | Dalle de béton magenta | Bloc et objet | 2 |
+| `minecraft:magenta_concrete_stairs` | Escalier en béton magenta | Bloc et objet | 2 |
+| `minecraft:orange_concrete` | Béton orange | Bloc et objet | 0 |
+| `minecraft:orange_concrete_powder` | Béton orange en poudre | Bloc et objet | 1 |
+| `minecraft:orange_concrete_slab` | Dalle de béton orange | Bloc et objet | 2 |
+| `minecraft:orange_concrete_stairs` | Escalier en béton orange | Bloc et objet | 2 |
+| `minecraft:pink_concrete` | Béton rose | Bloc et objet | 0 |
+| `minecraft:pink_concrete_powder` | Béton rose en poudre | Bloc et objet | 1 |
+| `minecraft:pink_concrete_slab` | Dalle de béton rose | Bloc et objet | 2 |
+| `minecraft:pink_concrete_stairs` | Escalier en béton rose | Bloc et objet | 2 |
+| `minecraft:purple_concrete` | Béton violet | Bloc et objet | 0 |
+| `minecraft:purple_concrete_powder` | Béton violet en poudre | Bloc et objet | 1 |
+| `minecraft:purple_concrete_slab` | Dalle de béton violet | Bloc et objet | 2 |
+| `minecraft:purple_concrete_stairs` | Escalier en béton violet | Bloc et objet | 2 |
+| `minecraft:red_concrete` | Béton rouge | Bloc et objet | 0 |
+| `minecraft:red_concrete_powder` | Béton rouge en poudre | Bloc et objet | 1 |
+| `minecraft:red_concrete_slab` | Dalle de béton rouge | Bloc et objet | 2 |
+| `minecraft:red_concrete_stairs` | Escalier en béton rouge | Bloc et objet | 2 |
+| `minecraft:white_concrete` | Béton blanc | Bloc et objet | 0 |
+| `minecraft:white_concrete_powder` | Béton blanc en poudre | Bloc et objet | 1 |
+| `minecraft:white_concrete_slab` | Dalle de béton blanc | Bloc et objet | 2 |
+| `minecraft:white_concrete_stairs` | Escalier en béton blanc | Bloc et objet | 2 |
+| `minecraft:yellow_concrete` | Béton jaune | Bloc et objet | 0 |
+| `minecraft:yellow_concrete_powder` | Béton jaune en poudre | Bloc et objet | 1 |
+| `minecraft:yellow_concrete_slab` | Dalle de béton jaune | Bloc et objet | 2 |
+| `minecraft:yellow_concrete_stairs` | Escalier en béton jaune | Bloc et objet | 2 |
+
+## C13
+
+**Sols et mousses · Soils and Moss** — [déclencheurs et règles](collections-minecraft.md#c13).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:coarse_dirt` | `minecraft:crafting_shaped` | Terre stérile (`minecraft:coarse_dirt`) × 4 |
+| `minecraft:leaf_litter` | `minecraft:smelting` | Tas de feuilles (`minecraft:leaf_litter`) |
+| `minecraft:moss_carpet` | `minecraft:crafting_shaped` | Tapis de mousse (`minecraft:moss_carpet`) × 3 |
+| `minecraft:mud_brick_slab` | `minecraft:crafting_shaped` | Dalle de briques de terre crue (`minecraft:mud_brick_slab`) × 6 |
+| `minecraft:mud_brick_slab_from_mud_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de briques de terre crue (`minecraft:mud_brick_slab`) × 2 |
+| `minecraft:mud_brick_stairs` | `minecraft:crafting_shaped` | Escalier en briques de terre crue (`minecraft:mud_brick_stairs`) × 4 |
+| `minecraft:mud_brick_stairs_from_mud_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en briques de terre crue (`minecraft:mud_brick_stairs`) |
+| `minecraft:mud_brick_wall` | `minecraft:crafting_shaped` | Muret de briques de terre crue (`minecraft:mud_brick_wall`) × 6 |
+| `minecraft:mud_brick_wall_from_mud_bricks_stonecutting` | `minecraft:stonecutting` | Muret de briques de terre crue (`minecraft:mud_brick_wall`) |
+| `minecraft:mud_bricks` | `minecraft:crafting_shaped` | Briques de terre crue (`minecraft:mud_bricks`) × 4 |
+| `minecraft:muddy_mangrove_roots` | `minecraft:crafting_shapeless` | Racines de palétuvier boueuses (`minecraft:muddy_mangrove_roots`) |
+| `minecraft:packed_mud` | `minecraft:crafting_shapeless` | Terre crue (`minecraft:packed_mud`) |
+| `minecraft:pale_moss_carpet` | `minecraft:crafting_shaped` | Tapis de mousse pâle (`minecraft:pale_moss_carpet`) × 3 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:coarse_dirt` | Terre stérile | Bloc et objet | 1 |
+| `minecraft:dirt` | Terre | Bloc et objet | 0 |
+| `minecraft:dirt_path` | Chemin de terre | Bloc et objet | 0 |
+| `minecraft:farmland` | Terre labourée | Bloc et objet | 0 |
+| `minecraft:grass_block` | Bloc d'herbe | Bloc et objet | 0 |
+| `minecraft:hanging_roots` | Racines suspendues | Bloc et objet | 0 |
+| `minecraft:leaf_litter` | Tas de feuilles | Bloc et objet | 1 |
+| `minecraft:moss_block` | Bloc de mousse | Bloc et objet | 0 |
+| `minecraft:moss_carpet` | Tapis de mousse | Bloc et objet | 1 |
+| `minecraft:mud` | Boue | Bloc et objet | 0 |
+| `minecraft:mud_brick_slab` | Dalle de briques de terre crue | Bloc et objet | 2 |
+| `minecraft:mud_brick_stairs` | Escalier en briques de terre crue | Bloc et objet | 2 |
+| `minecraft:mud_brick_wall` | Muret de briques de terre crue | Bloc et objet | 2 |
+| `minecraft:mud_bricks` | Briques de terre crue | Bloc et objet | 1 |
+| `minecraft:muddy_mangrove_roots` | Racines de palétuvier boueuses | Bloc et objet | 1 |
+| `minecraft:mycelium` | Mycélium | Bloc et objet | 0 |
+| `minecraft:packed_mud` | Terre crue | Bloc et objet | 1 |
+| `minecraft:pale_hanging_moss` | Mousse pâle suspendue | Bloc et objet | 0 |
+| `minecraft:pale_moss_block` | Bloc de mousse pâle | Bloc et objet | 0 |
+| `minecraft:pale_moss_carpet` | Tapis de mousse pâle | Bloc et objet | 1 |
+| `minecraft:podzol` | Podzol | Bloc et objet | 0 |
+| `minecraft:rooted_dirt` | Terre racineuse | Bloc et objet | 0 |
+
+## C14
+
+**Neige et glace · Snow and Ice** — [déclencheurs et règles](collections-minecraft.md#c14).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:blue_ice` | `minecraft:crafting_shapeless` | Glace bleue (`minecraft:blue_ice`) |
+| `minecraft:packed_ice` | `minecraft:crafting_shapeless` | Glace compactée (`minecraft:packed_ice`) |
+| `minecraft:snow` | `minecraft:crafting_shaped` | Neige (`minecraft:snow`) × 6 |
+| `minecraft:snow_block` | `minecraft:crafting_shaped` | Bloc de neige (`minecraft:snow_block`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:blue_ice` | Glace bleue | Bloc et objet | 1 |
+| `minecraft:frosted_ice` | Glace givrée | Bloc sans objet homonyme | 0 |
+| `minecraft:ice` | Glace | Bloc et objet | 0 |
+| `minecraft:packed_ice` | Glace compactée | Bloc et objet | 1 |
+| `minecraft:powder_snow` | Neige poudreuse | Bloc sans objet homonyme | 0 |
+| `minecraft:powder_snow_bucket` | Seau de neige poudreuse | Objet | 0 |
+| `minecraft:snow` | Neige | Bloc et objet | 1 |
+| `minecraft:snow_block` | Bloc de neige | Bloc et objet | 1 |
+| `minecraft:snowball` | Boule de neige | Objet | 0 |
+
+## C15
+
+**Améthyste et optique · Amethyst and Optics** — [déclencheurs et règles](collections-minecraft.md#c15).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:amethyst_block` | `minecraft:crafting_shaped` | Bloc d'améthyste (`minecraft:amethyst_block`) |
+| `minecraft:spyglass` | `minecraft:crafting_shaped` | Longue-vue (`minecraft:spyglass`) |
+| `minecraft:tinted_glass` | `minecraft:crafting_shaped` | Verre teinté (`minecraft:tinted_glass`) × 2 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:amethyst_block` | Bloc d'améthyste | Bloc et objet | 1 |
+| `minecraft:amethyst_cluster` | Amas d'améthyste | Bloc et objet | 0 |
+| `minecraft:amethyst_shard` | Éclat d'améthyste | Objet | 0 |
+| `minecraft:budding_amethyst` | Druse d'améthyste | Bloc et objet | 0 |
+| `minecraft:calcite` | Calcite | Bloc et objet | 0 |
+| `minecraft:large_amethyst_bud` | Grand cristal d'améthyste | Bloc et objet | 0 |
+| `minecraft:medium_amethyst_bud` | Cristal moyen d'améthyste | Bloc et objet | 0 |
+| `minecraft:small_amethyst_bud` | Petit cristal d'améthyste | Bloc et objet | 0 |
+| `minecraft:spyglass` | Longue-vue | Objet | 1 |
+| `minecraft:tinted_glass` | Verre teinté | Bloc et objet | 1 |
+
+## C16
+
+**Spéléothèmes · Dripstone** — [déclencheurs et règles](collections-minecraft.md#c16).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:dripstone_block` | `minecraft:crafting_shaped` | Bloc de spéléothème (`minecraft:dripstone_block`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:dripstone_block` | Bloc de spéléothème | Bloc et objet | 1 |
+| `minecraft:pointed_dripstone` | Spéléothème pointu | Bloc et objet | 0 |
+
+## C17
+
+**Soufre · Sulfur** — [déclencheurs et règles](collections-minecraft.md#c17).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_sulfur` | `minecraft:crafting_shaped` | Soufre sculpté (`minecraft:chiseled_sulfur`) |
+| `minecraft:chiseled_sulfur_from_sulfur_stonecutting` | `minecraft:stonecutting` | Soufre sculpté (`minecraft:chiseled_sulfur`) |
+| `minecraft:polished_sulfur` | `minecraft:crafting_shaped` | Soufre poli (`minecraft:polished_sulfur`) × 4 |
+| `minecraft:polished_sulfur_from_sulfur_stonecutting` | `minecraft:stonecutting` | Soufre poli (`minecraft:polished_sulfur`) |
+| `minecraft:polished_sulfur_slab` | `minecraft:crafting_shaped` | Dalle de soufre poli (`minecraft:polished_sulfur_slab`) × 6 |
+| `minecraft:polished_sulfur_slab_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Dalle de soufre poli (`minecraft:polished_sulfur_slab`) × 2 |
+| `minecraft:polished_sulfur_slab_from_sulfur_stonecutting` | `minecraft:stonecutting` | Dalle de soufre poli (`minecraft:polished_sulfur_slab`) × 2 |
+| `minecraft:polished_sulfur_stairs` | `minecraft:crafting_shaped` | Escalier en soufre poli (`minecraft:polished_sulfur_stairs`) × 4 |
+| `minecraft:polished_sulfur_stairs_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Escalier en soufre poli (`minecraft:polished_sulfur_stairs`) |
+| `minecraft:polished_sulfur_stairs_from_sulfur_stonecutting` | `minecraft:stonecutting` | Escalier en soufre poli (`minecraft:polished_sulfur_stairs`) |
+| `minecraft:polished_sulfur_wall` | `minecraft:crafting_shaped` | Muret de soufre poli (`minecraft:polished_sulfur_wall`) × 6 |
+| `minecraft:polished_sulfur_wall_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Muret de soufre poli (`minecraft:polished_sulfur_wall`) |
+| `minecraft:polished_sulfur_wall_from_sulfur_stonecutting` | `minecraft:stonecutting` | Muret de soufre poli (`minecraft:polished_sulfur_wall`) |
+| `minecraft:potent_sulfur` | `minecraft:crafting_shapeless` | Soufre concentré (`minecraft:potent_sulfur`) |
+| `minecraft:sulfur_brick_slab` | `minecraft:crafting_shaped` | Dalle de soufre taillé (`minecraft:sulfur_brick_slab`) × 6 |
+| `minecraft:sulfur_brick_slab_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Dalle de soufre taillé (`minecraft:sulfur_brick_slab`) × 2 |
+| `minecraft:sulfur_brick_slab_from_sulfur_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de soufre taillé (`minecraft:sulfur_brick_slab`) × 2 |
+| `minecraft:sulfur_brick_slab_from_sulfur_stonecutting` | `minecraft:stonecutting` | Dalle de soufre taillé (`minecraft:sulfur_brick_slab`) × 2 |
+| `minecraft:sulfur_brick_stairs` | `minecraft:crafting_shaped` | Escalier en soufre taillé (`minecraft:sulfur_brick_stairs`) × 4 |
+| `minecraft:sulfur_brick_stairs_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Escalier en soufre taillé (`minecraft:sulfur_brick_stairs`) |
+| `minecraft:sulfur_brick_stairs_from_sulfur_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en soufre taillé (`minecraft:sulfur_brick_stairs`) |
+| `minecraft:sulfur_brick_stairs_from_sulfur_stonecutting` | `minecraft:stonecutting` | Escalier en soufre taillé (`minecraft:sulfur_brick_stairs`) |
+| `minecraft:sulfur_brick_wall` | `minecraft:crafting_shaped` | Muret de soufre taillé (`minecraft:sulfur_brick_wall`) × 6 |
+| `minecraft:sulfur_brick_wall_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Muret de soufre taillé (`minecraft:sulfur_brick_wall`) |
+| `minecraft:sulfur_brick_wall_from_sulfur_bricks_stonecutting` | `minecraft:stonecutting` | Muret de soufre taillé (`minecraft:sulfur_brick_wall`) |
+| `minecraft:sulfur_brick_wall_from_sulfur_stonecutting` | `minecraft:stonecutting` | Muret de soufre taillé (`minecraft:sulfur_brick_wall`) |
+| `minecraft:sulfur_bricks` | `minecraft:crafting_shaped` | Soufre taillé (`minecraft:sulfur_bricks`) × 4 |
+| `minecraft:sulfur_bricks_from_polished_sulfur_stonecutting` | `minecraft:stonecutting` | Soufre taillé (`minecraft:sulfur_bricks`) |
+| `minecraft:sulfur_bricks_from_sulfur_stonecutting` | `minecraft:stonecutting` | Soufre taillé (`minecraft:sulfur_bricks`) |
+| `minecraft:sulfur_from_sulfur_spikes` | `minecraft:crafting_shaped` | Soufre (`minecraft:sulfur`) |
+| `minecraft:sulfur_slab` | `minecraft:crafting_shaped` | Dalle de soufre (`minecraft:sulfur_slab`) × 6 |
+| `minecraft:sulfur_slab_from_sulfur_stonecutting` | `minecraft:stonecutting` | Dalle de soufre (`minecraft:sulfur_slab`) × 2 |
+| `minecraft:sulfur_stairs` | `minecraft:crafting_shaped` | Escalier en soufre (`minecraft:sulfur_stairs`) × 4 |
+| `minecraft:sulfur_stairs_from_sulfur_stonecutting` | `minecraft:stonecutting` | Escalier en soufre (`minecraft:sulfur_stairs`) |
+| `minecraft:sulfur_wall` | `minecraft:crafting_shaped` | Muret de soufre (`minecraft:sulfur_wall`) × 6 |
+| `minecraft:sulfur_wall_from_sulfur_stonecutting` | `minecraft:stonecutting` | Muret de soufre (`minecraft:sulfur_wall`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_sulfur` | Soufre sculpté | Bloc et objet | 2 |
+| `minecraft:polished_sulfur` | Soufre poli | Bloc et objet | 2 |
+| `minecraft:polished_sulfur_slab` | Dalle de soufre poli | Bloc et objet | 3 |
+| `minecraft:polished_sulfur_stairs` | Escalier en soufre poli | Bloc et objet | 3 |
+| `minecraft:polished_sulfur_wall` | Muret de soufre poli | Bloc et objet | 3 |
+| `minecraft:potent_sulfur` | Soufre concentré | Bloc et objet | 1 |
+| `minecraft:sulfur` | Soufre | Bloc et objet | 1 |
+| `minecraft:sulfur_brick_slab` | Dalle de soufre taillé | Bloc et objet | 4 |
+| `minecraft:sulfur_brick_stairs` | Escalier en soufre taillé | Bloc et objet | 4 |
+| `minecraft:sulfur_brick_wall` | Muret de soufre taillé | Bloc et objet | 4 |
+| `minecraft:sulfur_bricks` | Soufre taillé | Bloc et objet | 3 |
+| `minecraft:sulfur_cube_bucket` | Cube de soufre dans un seau | Objet | 0 |
+| `minecraft:sulfur_slab` | Dalle de soufre | Bloc et objet | 2 |
+| `minecraft:sulfur_spike` | Pointe de soufre | Bloc et objet | 0 |
+| `minecraft:sulfur_stairs` | Escalier en soufre | Bloc et objet | 2 |
+| `minecraft:sulfur_wall` | Muret de soufre | Bloc et objet | 2 |
+
+## C18
+
+**Cinabre · Cinnabar** — [déclencheurs et règles](collections-minecraft.md#c18).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_cinnabar` | `minecraft:crafting_shaped` | Cinabre sculpté (`minecraft:chiseled_cinnabar`) |
+| `minecraft:chiseled_cinnabar_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Cinabre sculpté (`minecraft:chiseled_cinnabar`) |
+| `minecraft:cinnabar_brick_slab` | `minecraft:crafting_shaped` | Dalle de cinabre taillé (`minecraft:cinnabar_brick_slab`) × 6 |
+| `minecraft:cinnabar_brick_slab_from_cinnabar_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de cinabre taillé (`minecraft:cinnabar_brick_slab`) × 2 |
+| `minecraft:cinnabar_brick_slab_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Dalle de cinabre taillé (`minecraft:cinnabar_brick_slab`) × 2 |
+| `minecraft:cinnabar_brick_slab_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Dalle de cinabre taillé (`minecraft:cinnabar_brick_slab`) × 2 |
+| `minecraft:cinnabar_brick_stairs` | `minecraft:crafting_shaped` | Escalier en cinabre taillé (`minecraft:cinnabar_brick_stairs`) × 4 |
+| `minecraft:cinnabar_brick_stairs_from_cinnabar_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en cinabre taillé (`minecraft:cinnabar_brick_stairs`) |
+| `minecraft:cinnabar_brick_stairs_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Escalier en cinabre taillé (`minecraft:cinnabar_brick_stairs`) |
+| `minecraft:cinnabar_brick_stairs_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Escalier en cinabre taillé (`minecraft:cinnabar_brick_stairs`) |
+| `minecraft:cinnabar_brick_wall` | `minecraft:crafting_shaped` | Muret de cinabre taillé (`minecraft:cinnabar_brick_wall`) × 6 |
+| `minecraft:cinnabar_brick_wall_from_cinnabar_bricks_stonecutting` | `minecraft:stonecutting` | Muret de cinabre taillé (`minecraft:cinnabar_brick_wall`) |
+| `minecraft:cinnabar_brick_wall_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Muret de cinabre taillé (`minecraft:cinnabar_brick_wall`) |
+| `minecraft:cinnabar_brick_wall_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Muret de cinabre taillé (`minecraft:cinnabar_brick_wall`) |
+| `minecraft:cinnabar_bricks` | `minecraft:crafting_shaped` | Cinabre taillé (`minecraft:cinnabar_bricks`) × 4 |
+| `minecraft:cinnabar_bricks_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Cinabre taillé (`minecraft:cinnabar_bricks`) |
+| `minecraft:cinnabar_bricks_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Cinabre taillé (`minecraft:cinnabar_bricks`) |
+| `minecraft:cinnabar_slab` | `minecraft:crafting_shaped` | Dalle de cinabre (`minecraft:cinnabar_slab`) × 6 |
+| `minecraft:cinnabar_slab_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Dalle de cinabre (`minecraft:cinnabar_slab`) × 2 |
+| `minecraft:cinnabar_stairs` | `minecraft:crafting_shaped` | Escalier en cinabre (`minecraft:cinnabar_stairs`) × 4 |
+| `minecraft:cinnabar_stairs_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Escalier en cinabre (`minecraft:cinnabar_stairs`) |
+| `minecraft:cinnabar_wall` | `minecraft:crafting_shaped` | Muret de cinabre (`minecraft:cinnabar_wall`) × 6 |
+| `minecraft:cinnabar_wall_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Muret de cinabre (`minecraft:cinnabar_wall`) |
+| `minecraft:polished_cinnabar` | `minecraft:crafting_shaped` | Cinabre poli (`minecraft:polished_cinnabar`) × 4 |
+| `minecraft:polished_cinnabar_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Cinabre poli (`minecraft:polished_cinnabar`) |
+| `minecraft:polished_cinnabar_slab` | `minecraft:crafting_shaped` | Dalle de cinabre poli (`minecraft:polished_cinnabar_slab`) × 6 |
+| `minecraft:polished_cinnabar_slab_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Dalle de cinabre poli (`minecraft:polished_cinnabar_slab`) × 2 |
+| `minecraft:polished_cinnabar_slab_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Dalle de cinabre poli (`minecraft:polished_cinnabar_slab`) × 2 |
+| `minecraft:polished_cinnabar_stairs` | `minecraft:crafting_shaped` | Escalier en cinabre poli (`minecraft:polished_cinnabar_stairs`) × 4 |
+| `minecraft:polished_cinnabar_stairs_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Escalier en cinabre poli (`minecraft:polished_cinnabar_stairs`) |
+| `minecraft:polished_cinnabar_stairs_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Escalier en cinabre poli (`minecraft:polished_cinnabar_stairs`) |
+| `minecraft:polished_cinnabar_wall` | `minecraft:crafting_shaped` | Muret de cinabre poli (`minecraft:polished_cinnabar_wall`) × 6 |
+| `minecraft:polished_cinnabar_wall_from_cinnabar_stonecutting` | `minecraft:stonecutting` | Muret de cinabre poli (`minecraft:polished_cinnabar_wall`) |
+| `minecraft:polished_cinnabar_wall_from_polished_cinnabar_stonecutting` | `minecraft:stonecutting` | Muret de cinabre poli (`minecraft:polished_cinnabar_wall`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_cinnabar` | Cinabre sculpté | Bloc et objet | 2 |
+| `minecraft:cinnabar` | Cinabre | Bloc et objet | 0 |
+| `minecraft:cinnabar_brick_slab` | Dalle de cinabre taillé | Bloc et objet | 4 |
+| `minecraft:cinnabar_brick_stairs` | Escalier en cinabre taillé | Bloc et objet | 4 |
+| `minecraft:cinnabar_brick_wall` | Muret de cinabre taillé | Bloc et objet | 4 |
+| `minecraft:cinnabar_bricks` | Cinabre taillé | Bloc et objet | 3 |
+| `minecraft:cinnabar_slab` | Dalle de cinabre | Bloc et objet | 2 |
+| `minecraft:cinnabar_stairs` | Escalier en cinabre | Bloc et objet | 2 |
+| `minecraft:cinnabar_wall` | Muret de cinabre | Bloc et objet | 2 |
+| `minecraft:polished_cinnabar` | Cinabre poli | Bloc et objet | 2 |
+| `minecraft:polished_cinnabar_slab` | Dalle de cinabre poli | Bloc et objet | 3 |
+| `minecraft:polished_cinnabar_stairs` | Escalier en cinabre poli | Bloc et objet | 3 |
+| `minecraft:polished_cinnabar_wall` | Muret de cinabre poli | Bloc et objet | 3 |
+
+## C19
+
+**Cuivre et chaudronnerie · Copperworking** — [déclencheurs et règles](collections-minecraft.md#c19).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté (`minecraft:chiseled_copper`) |
+| `minecraft:chiseled_copper_from_copper_block_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté (`minecraft:chiseled_copper`) × 4 |
+| `minecraft:chiseled_copper_from_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté (`minecraft:chiseled_copper`) |
+| `minecraft:copper_axe` | `minecraft:crafting_shaped` | Hache en cuivre (`minecraft:copper_axe`) |
+| `minecraft:copper_bars` | `minecraft:crafting_shaped` | Barreaux de cuivre (`minecraft:copper_bars`) × 16 |
+| `minecraft:copper_block` | `minecraft:crafting_shaped` | Bloc de cuivre (`minecraft:copper_block`) |
+| `minecraft:copper_boots` | `minecraft:crafting_shaped` | Bottes en cuivre (`minecraft:copper_boots`) |
+| `minecraft:copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre (`minecraft:copper_bulb`) × 4 |
+| `minecraft:copper_chain` | `minecraft:crafting_shaped` | Chaîne en cuivre (`minecraft:copper_chain`) |
+| `minecraft:copper_chest` | `minecraft:crafting_shaped` | Coffre en cuivre (`minecraft:copper_chest`) |
+| `minecraft:copper_chestplate` | `minecraft:crafting_shaped` | Plastron en cuivre (`minecraft:copper_chestplate`) |
+| `minecraft:copper_door` | `minecraft:crafting_shaped` | Porte en cuivre (`minecraft:copper_door`) × 3 |
+| `minecraft:copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre (`minecraft:copper_grate`) × 4 |
+| `minecraft:copper_grate_from_copper_block_stonecutting` | `minecraft:stonecutting` | Grille en cuivre (`minecraft:copper_grate`) × 4 |
+| `minecraft:copper_helmet` | `minecraft:crafting_shaped` | Casque en cuivre (`minecraft:copper_helmet`) |
+| `minecraft:copper_hoe` | `minecraft:crafting_shaped` | Houe en cuivre (`minecraft:copper_hoe`) |
+| `minecraft:copper_ingot` | `minecraft:crafting_shapeless` | Lingot de cuivre (`minecraft:copper_ingot`) × 9 |
+| `minecraft:copper_ingot_from_blasting_copper_ore` | `minecraft:blasting` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_blasting_deepslate_copper_ore` | `minecraft:blasting` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_blasting_raw_copper` | `minecraft:blasting` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_nuggets` | `minecraft:crafting_shaped` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_smelting_copper_ore` | `minecraft:smelting` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_smelting_deepslate_copper_ore` | `minecraft:smelting` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_smelting_raw_copper` | `minecraft:smelting` | Lingot de cuivre (`minecraft:copper_ingot`) |
+| `minecraft:copper_ingot_from_waxed_copper_block` | `minecraft:crafting_shapeless` | Lingot de cuivre (`minecraft:copper_ingot`) × 9 |
+| `minecraft:copper_lantern` | `minecraft:crafting_shaped` | Lanterne en cuivre (`minecraft:copper_lantern`) |
+| `minecraft:copper_leggings` | `minecraft:crafting_shaped` | Jambières en cuivre (`minecraft:copper_leggings`) |
+| `minecraft:copper_nugget` | `minecraft:crafting_shapeless` | Pépite de cuivre (`minecraft:copper_nugget`) × 9 |
+| `minecraft:copper_nugget_from_blasting` | `minecraft:blasting` | Pépite de cuivre (`minecraft:copper_nugget`) |
+| `minecraft:copper_nugget_from_smelting` | `minecraft:smelting` | Pépite de cuivre (`minecraft:copper_nugget`) |
+| `minecraft:copper_pickaxe` | `minecraft:crafting_shaped` | Pioche en cuivre (`minecraft:copper_pickaxe`) |
+| `minecraft:copper_shovel` | `minecraft:crafting_shaped` | Pelle en cuivre (`minecraft:copper_shovel`) |
+| `minecraft:copper_spear` | `minecraft:crafting_shaped` | Lance en cuivre (`minecraft:copper_spear`) |
+| `minecraft:copper_sword` | `minecraft:crafting_shaped` | Épée en cuivre (`minecraft:copper_sword`) |
+| `minecraft:copper_torch` | `minecraft:crafting_shaped` | Torche de cuivre (`minecraft:copper_torch`) × 4 |
+| `minecraft:copper_trapdoor` | `minecraft:crafting_shaped` | Trappe en cuivre (`minecraft:copper_trapdoor`) |
+| `minecraft:cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé (`minecraft:cut_copper`) × 4 |
+| `minecraft:cut_copper_from_copper_block_stonecutting` | `minecraft:stonecutting` | Cuivre taillé (`minecraft:cut_copper`) × 4 |
+| `minecraft:cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé (`minecraft:cut_copper_slab`) × 6 |
+| `minecraft:cut_copper_slab_from_copper_block_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé (`minecraft:cut_copper_slab`) × 8 |
+| `minecraft:cut_copper_slab_from_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé (`minecraft:cut_copper_slab`) × 2 |
+| `minecraft:cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé (`minecraft:cut_copper_stairs`) × 4 |
+| `minecraft:cut_copper_stairs_from_copper_block_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé (`minecraft:cut_copper_stairs`) × 4 |
+| `minecraft:cut_copper_stairs_from_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé (`minecraft:cut_copper_stairs`) |
+| `minecraft:exposed_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté exposé (`minecraft:exposed_chiseled_copper`) |
+| `minecraft:exposed_chiseled_copper_from_exposed_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté exposé (`minecraft:exposed_chiseled_copper`) × 4 |
+| `minecraft:exposed_chiseled_copper_from_exposed_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté exposé (`minecraft:exposed_chiseled_copper`) |
+| `minecraft:exposed_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre exposé (`minecraft:exposed_copper_bulb`) × 4 |
+| `minecraft:exposed_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre exposé (`minecraft:exposed_copper_grate`) × 4 |
+| `minecraft:exposed_copper_grate_from_exposed_copper_stonecutting` | `minecraft:stonecutting` | Grille en cuivre exposé (`minecraft:exposed_copper_grate`) × 4 |
+| `minecraft:exposed_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé exposé (`minecraft:exposed_cut_copper`) × 4 |
+| `minecraft:exposed_cut_copper_from_exposed_copper_stonecutting` | `minecraft:stonecutting` | Cuivre taillé exposé (`minecraft:exposed_cut_copper`) × 4 |
+| `minecraft:exposed_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé exposé (`minecraft:exposed_cut_copper_slab`) × 6 |
+| `minecraft:exposed_cut_copper_slab_from_exposed_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé exposé (`minecraft:exposed_cut_copper_slab`) × 8 |
+| `minecraft:exposed_cut_copper_slab_from_exposed_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé exposé (`minecraft:exposed_cut_copper_slab`) × 2 |
+| `minecraft:exposed_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé exposé (`minecraft:exposed_cut_copper_stairs`) × 4 |
+| `minecraft:exposed_cut_copper_stairs_from_exposed_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé exposé (`minecraft:exposed_cut_copper_stairs`) × 4 |
+| `minecraft:exposed_cut_copper_stairs_from_exposed_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé exposé (`minecraft:exposed_cut_copper_stairs`) |
+| `minecraft:lightning_rod` | `minecraft:crafting_shaped` | Paratonnerre (`minecraft:lightning_rod`) |
+| `minecraft:oxidized_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté oxydé (`minecraft:oxidized_chiseled_copper`) |
+| `minecraft:oxidized_chiseled_copper_from_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté oxydé (`minecraft:oxidized_chiseled_copper`) × 4 |
+| `minecraft:oxidized_chiseled_copper_from_oxidized_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté oxydé (`minecraft:oxidized_chiseled_copper`) |
+| `minecraft:oxidized_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre oxydé (`minecraft:oxidized_copper_bulb`) × 4 |
+| `minecraft:oxidized_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre oxydé (`minecraft:oxidized_copper_grate`) × 4 |
+| `minecraft:oxidized_copper_grate_from_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Grille en cuivre oxydé (`minecraft:oxidized_copper_grate`) × 4 |
+| `minecraft:oxidized_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé oxydé (`minecraft:oxidized_cut_copper`) × 4 |
+| `minecraft:oxidized_cut_copper_from_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Cuivre taillé oxydé (`minecraft:oxidized_cut_copper`) × 4 |
+| `minecraft:oxidized_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé oxydé (`minecraft:oxidized_cut_copper_slab`) × 6 |
+| `minecraft:oxidized_cut_copper_slab_from_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé oxydé (`minecraft:oxidized_cut_copper_slab`) × 8 |
+| `minecraft:oxidized_cut_copper_slab_from_oxidized_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé oxydé (`minecraft:oxidized_cut_copper_slab`) × 2 |
+| `minecraft:oxidized_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé oxydé (`minecraft:oxidized_cut_copper_stairs`) × 4 |
+| `minecraft:oxidized_cut_copper_stairs_from_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé oxydé (`minecraft:oxidized_cut_copper_stairs`) × 4 |
+| `minecraft:oxidized_cut_copper_stairs_from_oxidized_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé oxydé (`minecraft:oxidized_cut_copper_stairs`) |
+| `minecraft:raw_copper` | `minecraft:crafting_shapeless` | Cuivre brut (`minecraft:raw_copper`) × 9 |
+| `minecraft:raw_copper_block` | `minecraft:crafting_shaped` | Bloc de cuivre brut (`minecraft:raw_copper_block`) |
+| `minecraft:weathered_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté érodé (`minecraft:weathered_chiseled_copper`) |
+| `minecraft:weathered_chiseled_copper_from_weathered_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté érodé (`minecraft:weathered_chiseled_copper`) × 4 |
+| `minecraft:weathered_chiseled_copper_from_weathered_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté érodé (`minecraft:weathered_chiseled_copper`) |
+| `minecraft:weathered_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre érodé (`minecraft:weathered_copper_bulb`) × 4 |
+| `minecraft:weathered_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre érodé (`minecraft:weathered_copper_grate`) × 4 |
+| `minecraft:weathered_copper_grate_from_weathered_copper_stonecutting` | `minecraft:stonecutting` | Grille en cuivre érodé (`minecraft:weathered_copper_grate`) × 4 |
+| `minecraft:weathered_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé érodé (`minecraft:weathered_cut_copper`) × 4 |
+| `minecraft:weathered_cut_copper_from_weathered_copper_stonecutting` | `minecraft:stonecutting` | Cuivre taillé érodé (`minecraft:weathered_cut_copper`) × 4 |
+| `minecraft:weathered_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé érodé (`minecraft:weathered_cut_copper_slab`) × 6 |
+| `minecraft:weathered_cut_copper_slab_from_weathered_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé érodé (`minecraft:weathered_cut_copper_slab`) × 8 |
+| `minecraft:weathered_cut_copper_slab_from_weathered_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé érodé (`minecraft:weathered_cut_copper_slab`) × 2 |
+| `minecraft:weathered_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé érodé (`minecraft:weathered_cut_copper_stairs`) × 4 |
+| `minecraft:weathered_cut_copper_stairs_from_weathered_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé érodé (`minecraft:weathered_cut_copper_stairs`) × 4 |
+| `minecraft:weathered_cut_copper_stairs_from_weathered_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé érodé (`minecraft:weathered_cut_copper_stairs`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_copper` | Cuivre sculpté | Bloc et objet | 3 |
+| `minecraft:copper_axe` | Hache en cuivre | Objet | 1 |
+| `minecraft:copper_bars` | Barreaux de cuivre | Bloc et objet | 1 |
+| `minecraft:copper_block` | Bloc de cuivre | Bloc et objet | 1 |
+| `minecraft:copper_boots` | Bottes en cuivre | Objet | 1 |
+| `minecraft:copper_bulb` | Ampoule en cuivre | Bloc et objet | 1 |
+| `minecraft:copper_chain` | Chaîne en cuivre | Bloc et objet | 1 |
+| `minecraft:copper_chest` | Coffre en cuivre | Bloc et objet | 1 |
+| `minecraft:copper_chestplate` | Plastron en cuivre | Objet | 1 |
+| `minecraft:copper_door` | Porte en cuivre | Bloc et objet | 1 |
+| `minecraft:copper_golem_statue` | Statue de golem de cuivre | Bloc et objet | 0 |
+| `minecraft:copper_grate` | Grille en cuivre | Bloc et objet | 2 |
+| `minecraft:copper_helmet` | Casque en cuivre | Objet | 1 |
+| `minecraft:copper_hoe` | Houe en cuivre | Objet | 1 |
+| `minecraft:copper_ingot` | Lingot de cuivre | Objet | 9 |
+| `minecraft:copper_lantern` | Lanterne en cuivre | Bloc et objet | 1 |
+| `minecraft:copper_leggings` | Jambières en cuivre | Objet | 1 |
+| `minecraft:copper_nugget` | Pépite de cuivre | Objet | 3 |
+| `minecraft:copper_ore` | Minerai de cuivre | Bloc et objet | 0 |
+| `minecraft:copper_pickaxe` | Pioche en cuivre | Objet | 1 |
+| `minecraft:copper_shovel` | Pelle en cuivre | Objet | 1 |
+| `minecraft:copper_spear` | Lance en cuivre | Objet | 1 |
+| `minecraft:copper_sword` | Épée en cuivre | Objet | 1 |
+| `minecraft:copper_torch` | Torche de cuivre | Bloc et objet | 1 |
+| `minecraft:copper_trapdoor` | Trappe en cuivre | Bloc et objet | 1 |
+| `minecraft:copper_wall_torch` | Torche de cuivre murale | Bloc sans objet homonyme | 0 |
+| `minecraft:cut_copper` | Cuivre taillé | Bloc et objet | 2 |
+| `minecraft:cut_copper_slab` | Dalle de cuivre taillé | Bloc et objet | 3 |
+| `minecraft:cut_copper_stairs` | Escalier en cuivre taillé | Bloc et objet | 3 |
+| `minecraft:deepslate_copper_ore` | Minerai de cuivre des abîmes | Bloc et objet | 0 |
+| `minecraft:exposed_chiseled_copper` | Cuivre sculpté exposé | Bloc et objet | 3 |
+| `minecraft:exposed_copper` | Cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_bars` | Barreaux de cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_bulb` | Ampoule en cuivre exposé | Bloc et objet | 1 |
+| `minecraft:exposed_copper_chain` | Chaîne en cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_chest` | Coffre en cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_door` | Porte en cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_golem_statue` | Statue de golem de cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_grate` | Grille en cuivre exposé | Bloc et objet | 2 |
+| `minecraft:exposed_copper_lantern` | Lanterne en cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_copper_trapdoor` | Trappe en cuivre exposé | Bloc et objet | 0 |
+| `minecraft:exposed_cut_copper` | Cuivre taillé exposé | Bloc et objet | 2 |
+| `minecraft:exposed_cut_copper_slab` | Dalle de cuivre taillé exposé | Bloc et objet | 3 |
+| `minecraft:exposed_cut_copper_stairs` | Escalier en cuivre taillé exposé | Bloc et objet | 3 |
+| `minecraft:exposed_lightning_rod` | Paratonnerre exposé | Bloc et objet | 0 |
+| `minecraft:lightning_rod` | Paratonnerre | Bloc et objet | 1 |
+| `minecraft:oxidized_chiseled_copper` | Cuivre sculpté oxydé | Bloc et objet | 3 |
+| `minecraft:oxidized_copper` | Cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_bars` | Barreaux de cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_bulb` | Ampoule en cuivre oxydé | Bloc et objet | 1 |
+| `minecraft:oxidized_copper_chain` | Chaîne en cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_chest` | Coffre en cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_door` | Porte en cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_golem_statue` | Statue de golem de cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_grate` | Grille en cuivre oxydé | Bloc et objet | 2 |
+| `minecraft:oxidized_copper_lantern` | Lanterne en cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_copper_trapdoor` | Trappe en cuivre oxydé | Bloc et objet | 0 |
+| `minecraft:oxidized_cut_copper` | Cuivre taillé oxydé | Bloc et objet | 2 |
+| `minecraft:oxidized_cut_copper_slab` | Dalle de cuivre taillé oxydé | Bloc et objet | 3 |
+| `minecraft:oxidized_cut_copper_stairs` | Escalier en cuivre taillé oxydé | Bloc et objet | 3 |
+| `minecraft:oxidized_lightning_rod` | Paratonnerre oxydé | Bloc et objet | 0 |
+| `minecraft:raw_copper` | Cuivre brut | Objet | 1 |
+| `minecraft:raw_copper_block` | Bloc de cuivre brut | Bloc et objet | 1 |
+| `minecraft:weathered_chiseled_copper` | Cuivre sculpté érodé | Bloc et objet | 3 |
+| `minecraft:weathered_copper` | Cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_bars` | Barreaux de cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_bulb` | Ampoule en cuivre érodé | Bloc et objet | 1 |
+| `minecraft:weathered_copper_chain` | Chaîne en cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_chest` | Coffre en cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_door` | Porte en cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_golem_statue` | Statue de golem de cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_grate` | Grille en cuivre érodé | Bloc et objet | 2 |
+| `minecraft:weathered_copper_lantern` | Lanterne en cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_copper_trapdoor` | Trappe en cuivre érodé | Bloc et objet | 0 |
+| `minecraft:weathered_cut_copper` | Cuivre taillé érodé | Bloc et objet | 2 |
+| `minecraft:weathered_cut_copper_slab` | Dalle de cuivre taillé érodé | Bloc et objet | 3 |
+| `minecraft:weathered_cut_copper_stairs` | Escalier en cuivre taillé érodé | Bloc et objet | 3 |
+| `minecraft:weathered_lightning_rod` | Paratonnerre érodé | Bloc et objet | 0 |
+
+## C20
+
+**Cuivre ciré · Waxed Copper** — [déclencheurs et règles](collections-minecraft.md#c20).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:waxed_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté ciré (`minecraft:waxed_chiseled_copper`) |
+| `minecraft:waxed_chiseled_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre sculpté ciré (`minecraft:waxed_chiseled_copper`) |
+| `minecraft:waxed_chiseled_copper_from_waxed_copper_block_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté ciré (`minecraft:waxed_chiseled_copper`) × 4 |
+| `minecraft:waxed_chiseled_copper_from_waxed_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté ciré (`minecraft:waxed_chiseled_copper`) |
+| `minecraft:waxed_copper_bars_from_honeycomb` | `minecraft:crafting_shapeless` | Barreaux de cuivre ciré (`minecraft:waxed_copper_bars`) |
+| `minecraft:waxed_copper_block_from_honeycomb` | `minecraft:crafting_shapeless` | Bloc de cuivre ciré (`minecraft:waxed_copper_block`) |
+| `minecraft:waxed_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre ciré (`minecraft:waxed_copper_bulb`) × 4 |
+| `minecraft:waxed_copper_bulb_from_honeycomb` | `minecraft:crafting_shapeless` | Ampoule en cuivre ciré (`minecraft:waxed_copper_bulb`) |
+| `minecraft:waxed_copper_chain_from_honeycomb` | `minecraft:crafting_shapeless` | Chaîne en cuivre ciré (`minecraft:waxed_copper_chain`) |
+| `minecraft:waxed_copper_chest_from_honeycomb` | `minecraft:crafting_shapeless` | Coffre en cuivre ciré (`minecraft:waxed_copper_chest`) |
+| `minecraft:waxed_copper_door_from_honeycomb` | `minecraft:crafting_shapeless` | Porte en cuivre ciré (`minecraft:waxed_copper_door`) |
+| `minecraft:waxed_copper_golem_statue_from_honeycomb` | `minecraft:crafting_shapeless` | Statue de golem de cuivre ciré (`minecraft:waxed_copper_golem_statue`) |
+| `minecraft:waxed_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre ciré (`minecraft:waxed_copper_grate`) × 4 |
+| `minecraft:waxed_copper_grate_from_honeycomb` | `minecraft:crafting_shapeless` | Grille en cuivre ciré (`minecraft:waxed_copper_grate`) |
+| `minecraft:waxed_copper_grate_from_waxed_copper_block_stonecutting` | `minecraft:stonecutting` | Grille en cuivre ciré (`minecraft:waxed_copper_grate`) × 4 |
+| `minecraft:waxed_copper_lantern_from_honeycomb` | `minecraft:crafting_shapeless` | Lanterne en cuivre ciré (`minecraft:waxed_copper_lantern`) |
+| `minecraft:waxed_copper_trapdoor_from_honeycomb` | `minecraft:crafting_shapeless` | Trappe en cuivre ciré (`minecraft:waxed_copper_trapdoor`) |
+| `minecraft:waxed_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé ciré (`minecraft:waxed_cut_copper`) × 4 |
+| `minecraft:waxed_cut_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre taillé ciré (`minecraft:waxed_cut_copper`) |
+| `minecraft:waxed_cut_copper_from_waxed_copper_block_stonecutting` | `minecraft:stonecutting` | Cuivre taillé ciré (`minecraft:waxed_cut_copper`) × 4 |
+| `minecraft:waxed_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé ciré (`minecraft:waxed_cut_copper_slab`) × 6 |
+| `minecraft:waxed_cut_copper_slab_from_honeycomb` | `minecraft:crafting_shapeless` | Dalle de cuivre taillé ciré (`minecraft:waxed_cut_copper_slab`) |
+| `minecraft:waxed_cut_copper_slab_from_waxed_copper_block_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé ciré (`minecraft:waxed_cut_copper_slab`) × 8 |
+| `minecraft:waxed_cut_copper_slab_from_waxed_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé ciré (`minecraft:waxed_cut_copper_slab`) × 2 |
+| `minecraft:waxed_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé ciré (`minecraft:waxed_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_cut_copper_stairs_from_honeycomb` | `minecraft:crafting_shapeless` | Escalier en cuivre taillé ciré (`minecraft:waxed_cut_copper_stairs`) |
+| `minecraft:waxed_cut_copper_stairs_from_waxed_copper_block_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé ciré (`minecraft:waxed_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_cut_copper_stairs_from_waxed_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé ciré (`minecraft:waxed_cut_copper_stairs`) |
+| `minecraft:waxed_exposed_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté exposé ciré (`minecraft:waxed_exposed_chiseled_copper`) |
+| `minecraft:waxed_exposed_chiseled_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre sculpté exposé ciré (`minecraft:waxed_exposed_chiseled_copper`) |
+| `minecraft:waxed_exposed_chiseled_copper_from_waxed_exposed_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté exposé ciré (`minecraft:waxed_exposed_chiseled_copper`) × 4 |
+| `minecraft:waxed_exposed_chiseled_copper_from_waxed_exposed_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté exposé ciré (`minecraft:waxed_exposed_chiseled_copper`) |
+| `minecraft:waxed_exposed_copper_bars_from_honeycomb` | `minecraft:crafting_shapeless` | Barreaux de cuivre exposé ciré (`minecraft:waxed_exposed_copper_bars`) |
+| `minecraft:waxed_exposed_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre exposé ciré (`minecraft:waxed_exposed_copper_bulb`) × 4 |
+| `minecraft:waxed_exposed_copper_bulb_from_honeycomb` | `minecraft:crafting_shapeless` | Ampoule en cuivre exposé ciré (`minecraft:waxed_exposed_copper_bulb`) |
+| `minecraft:waxed_exposed_copper_chain_from_honeycomb` | `minecraft:crafting_shapeless` | Chaîne en cuivre exposé ciré (`minecraft:waxed_exposed_copper_chain`) |
+| `minecraft:waxed_exposed_copper_chest_from_honeycomb` | `minecraft:crafting_shapeless` | Coffre en cuivre exposé ciré (`minecraft:waxed_exposed_copper_chest`) |
+| `minecraft:waxed_exposed_copper_door_from_honeycomb` | `minecraft:crafting_shapeless` | Porte en cuivre exposé ciré (`minecraft:waxed_exposed_copper_door`) |
+| `minecraft:waxed_exposed_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre exposé ciré (`minecraft:waxed_exposed_copper`) |
+| `minecraft:waxed_exposed_copper_golem_statue_from_honeycomb` | `minecraft:crafting_shapeless` | Statue de golem de cuivre exposé ciré (`minecraft:waxed_exposed_copper_golem_statue`) |
+| `minecraft:waxed_exposed_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre exposé ciré (`minecraft:waxed_exposed_copper_grate`) × 4 |
+| `minecraft:waxed_exposed_copper_grate_from_honeycomb` | `minecraft:crafting_shapeless` | Grille en cuivre exposé ciré (`minecraft:waxed_exposed_copper_grate`) |
+| `minecraft:waxed_exposed_copper_grate_from_waxed_exposed_copper_stonecutting` | `minecraft:stonecutting` | Grille en cuivre exposé ciré (`minecraft:waxed_exposed_copper_grate`) × 4 |
+| `minecraft:waxed_exposed_copper_lantern_from_honeycomb` | `minecraft:crafting_shapeless` | Lanterne en cuivre exposé ciré (`minecraft:waxed_exposed_copper_lantern`) |
+| `minecraft:waxed_exposed_copper_trapdoor_from_honeycomb` | `minecraft:crafting_shapeless` | Trappe en cuivre exposé ciré (`minecraft:waxed_exposed_copper_trapdoor`) |
+| `minecraft:waxed_exposed_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper`) × 4 |
+| `minecraft:waxed_exposed_cut_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper`) |
+| `minecraft:waxed_exposed_cut_copper_from_waxed_exposed_copper_stonecutting` | `minecraft:stonecutting` | Cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper`) × 4 |
+| `minecraft:waxed_exposed_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_slab`) × 6 |
+| `minecraft:waxed_exposed_cut_copper_slab_from_honeycomb` | `minecraft:crafting_shapeless` | Dalle de cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_slab`) |
+| `minecraft:waxed_exposed_cut_copper_slab_from_waxed_exposed_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_slab`) × 8 |
+| `minecraft:waxed_exposed_cut_copper_slab_from_waxed_exposed_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_slab`) × 2 |
+| `minecraft:waxed_exposed_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_exposed_cut_copper_stairs_from_honeycomb` | `minecraft:crafting_shapeless` | Escalier en cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_stairs`) |
+| `minecraft:waxed_exposed_cut_copper_stairs_from_waxed_exposed_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_exposed_cut_copper_stairs_from_waxed_exposed_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé exposé ciré (`minecraft:waxed_exposed_cut_copper_stairs`) |
+| `minecraft:waxed_exposed_lightning_rod_from_honeycomb` | `minecraft:crafting_shapeless` | Paratonnerre exposé ciré (`minecraft:waxed_exposed_lightning_rod`) |
+| `minecraft:waxed_lightning_rod_from_honeycomb` | `minecraft:crafting_shapeless` | Paratonnerre ciré (`minecraft:waxed_lightning_rod`) |
+| `minecraft:waxed_oxidized_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté oxydé ciré (`minecraft:waxed_oxidized_chiseled_copper`) |
+| `minecraft:waxed_oxidized_chiseled_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre sculpté oxydé ciré (`minecraft:waxed_oxidized_chiseled_copper`) |
+| `minecraft:waxed_oxidized_chiseled_copper_from_waxed_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté oxydé ciré (`minecraft:waxed_oxidized_chiseled_copper`) × 4 |
+| `minecraft:waxed_oxidized_chiseled_copper_from_waxed_oxidized_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté oxydé ciré (`minecraft:waxed_oxidized_chiseled_copper`) |
+| `minecraft:waxed_oxidized_copper_bars_from_honeycomb` | `minecraft:crafting_shapeless` | Barreaux de cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_bars`) |
+| `minecraft:waxed_oxidized_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_bulb`) × 4 |
+| `minecraft:waxed_oxidized_copper_bulb_from_honeycomb` | `minecraft:crafting_shapeless` | Ampoule en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_bulb`) |
+| `minecraft:waxed_oxidized_copper_chain_from_honeycomb` | `minecraft:crafting_shapeless` | Chaîne en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_chain`) |
+| `minecraft:waxed_oxidized_copper_chest_from_honeycomb` | `minecraft:crafting_shapeless` | Coffre en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_chest`) |
+| `minecraft:waxed_oxidized_copper_door_from_honeycomb` | `minecraft:crafting_shapeless` | Porte en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_door`) |
+| `minecraft:waxed_oxidized_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre oxydé ciré (`minecraft:waxed_oxidized_copper`) |
+| `minecraft:waxed_oxidized_copper_golem_statue_from_honeycomb` | `minecraft:crafting_shapeless` | Statue de golem de cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_golem_statue`) |
+| `minecraft:waxed_oxidized_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_grate`) × 4 |
+| `minecraft:waxed_oxidized_copper_grate_from_honeycomb` | `minecraft:crafting_shapeless` | Grille en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_grate`) |
+| `minecraft:waxed_oxidized_copper_grate_from_waxed_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Grille en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_grate`) × 4 |
+| `minecraft:waxed_oxidized_copper_lantern_from_honeycomb` | `minecraft:crafting_shapeless` | Lanterne en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_lantern`) |
+| `minecraft:waxed_oxidized_copper_trapdoor_from_honeycomb` | `minecraft:crafting_shapeless` | Trappe en cuivre oxydé ciré (`minecraft:waxed_oxidized_copper_trapdoor`) |
+| `minecraft:waxed_oxidized_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper`) × 4 |
+| `minecraft:waxed_oxidized_cut_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper`) |
+| `minecraft:waxed_oxidized_cut_copper_from_waxed_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper`) × 4 |
+| `minecraft:waxed_oxidized_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_slab`) × 6 |
+| `minecraft:waxed_oxidized_cut_copper_slab_from_honeycomb` | `minecraft:crafting_shapeless` | Dalle de cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_slab`) |
+| `minecraft:waxed_oxidized_cut_copper_slab_from_waxed_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_slab`) × 8 |
+| `minecraft:waxed_oxidized_cut_copper_slab_from_waxed_oxidized_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_slab`) × 2 |
+| `minecraft:waxed_oxidized_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_oxidized_cut_copper_stairs_from_honeycomb` | `minecraft:crafting_shapeless` | Escalier en cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_stairs`) |
+| `minecraft:waxed_oxidized_cut_copper_stairs_from_waxed_oxidized_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_oxidized_cut_copper_stairs_from_waxed_oxidized_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé oxydé ciré (`minecraft:waxed_oxidized_cut_copper_stairs`) |
+| `minecraft:waxed_oxidized_lightning_rod_from_honeycomb` | `minecraft:crafting_shapeless` | Paratonnerre oxydé ciré (`minecraft:waxed_oxidized_lightning_rod`) |
+| `minecraft:waxed_weathered_chiseled_copper` | `minecraft:crafting_shaped` | Cuivre sculpté érodé ciré (`minecraft:waxed_weathered_chiseled_copper`) |
+| `minecraft:waxed_weathered_chiseled_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre sculpté érodé ciré (`minecraft:waxed_weathered_chiseled_copper`) |
+| `minecraft:waxed_weathered_chiseled_copper_from_waxed_weathered_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté érodé ciré (`minecraft:waxed_weathered_chiseled_copper`) × 4 |
+| `minecraft:waxed_weathered_chiseled_copper_from_waxed_weathered_cut_copper_stonecutting` | `minecraft:stonecutting` | Cuivre sculpté érodé ciré (`minecraft:waxed_weathered_chiseled_copper`) |
+| `minecraft:waxed_weathered_copper_bars_from_honeycomb` | `minecraft:crafting_shapeless` | Barreaux de cuivre érodé ciré (`minecraft:waxed_weathered_copper_bars`) |
+| `minecraft:waxed_weathered_copper_bulb` | `minecraft:crafting_shaped` | Ampoule en cuivre érodé ciré (`minecraft:waxed_weathered_copper_bulb`) × 4 |
+| `minecraft:waxed_weathered_copper_bulb_from_honeycomb` | `minecraft:crafting_shapeless` | Ampoule en cuivre érodé ciré (`minecraft:waxed_weathered_copper_bulb`) |
+| `minecraft:waxed_weathered_copper_chain_from_honeycomb` | `minecraft:crafting_shapeless` | Chaîne en cuivre érodé ciré (`minecraft:waxed_weathered_copper_chain`) |
+| `minecraft:waxed_weathered_copper_chest_from_honeycomb` | `minecraft:crafting_shapeless` | Coffre en cuivre érodé ciré (`minecraft:waxed_weathered_copper_chest`) |
+| `minecraft:waxed_weathered_copper_door_from_honeycomb` | `minecraft:crafting_shapeless` | Porte en cuivre érodé ciré (`minecraft:waxed_weathered_copper_door`) |
+| `minecraft:waxed_weathered_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre érodé ciré (`minecraft:waxed_weathered_copper`) |
+| `minecraft:waxed_weathered_copper_golem_statue_from_honeycomb` | `minecraft:crafting_shapeless` | Statue de golem de cuivre érodé ciré (`minecraft:waxed_weathered_copper_golem_statue`) |
+| `minecraft:waxed_weathered_copper_grate` | `minecraft:crafting_shaped` | Grille en cuivre érodé ciré (`minecraft:waxed_weathered_copper_grate`) × 4 |
+| `minecraft:waxed_weathered_copper_grate_from_honeycomb` | `minecraft:crafting_shapeless` | Grille en cuivre érodé ciré (`minecraft:waxed_weathered_copper_grate`) |
+| `minecraft:waxed_weathered_copper_grate_from_waxed_weathered_copper_stonecutting` | `minecraft:stonecutting` | Grille en cuivre érodé ciré (`minecraft:waxed_weathered_copper_grate`) × 4 |
+| `minecraft:waxed_weathered_copper_lantern_from_honeycomb` | `minecraft:crafting_shapeless` | Lanterne en cuivre érodé ciré (`minecraft:waxed_weathered_copper_lantern`) |
+| `minecraft:waxed_weathered_copper_trapdoor_from_honeycomb` | `minecraft:crafting_shapeless` | Trappe en cuivre érodé ciré (`minecraft:waxed_weathered_copper_trapdoor`) |
+| `minecraft:waxed_weathered_cut_copper` | `minecraft:crafting_shaped` | Cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper`) × 4 |
+| `minecraft:waxed_weathered_cut_copper_from_honeycomb` | `minecraft:crafting_shapeless` | Cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper`) |
+| `minecraft:waxed_weathered_cut_copper_from_waxed_weathered_copper_stonecutting` | `minecraft:stonecutting` | Cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper`) × 4 |
+| `minecraft:waxed_weathered_cut_copper_slab` | `minecraft:crafting_shaped` | Dalle de cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_slab`) × 6 |
+| `minecraft:waxed_weathered_cut_copper_slab_from_honeycomb` | `minecraft:crafting_shapeless` | Dalle de cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_slab`) |
+| `minecraft:waxed_weathered_cut_copper_slab_from_waxed_weathered_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_slab`) × 8 |
+| `minecraft:waxed_weathered_cut_copper_slab_from_waxed_weathered_cut_copper_stonecutting` | `minecraft:stonecutting` | Dalle de cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_slab`) × 2 |
+| `minecraft:waxed_weathered_cut_copper_stairs` | `minecraft:crafting_shaped` | Escalier en cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_weathered_cut_copper_stairs_from_honeycomb` | `minecraft:crafting_shapeless` | Escalier en cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_stairs`) |
+| `minecraft:waxed_weathered_cut_copper_stairs_from_waxed_weathered_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_stairs`) × 4 |
+| `minecraft:waxed_weathered_cut_copper_stairs_from_waxed_weathered_cut_copper_stonecutting` | `minecraft:stonecutting` | Escalier en cuivre taillé érodé ciré (`minecraft:waxed_weathered_cut_copper_stairs`) |
+| `minecraft:waxed_weathered_lightning_rod_from_honeycomb` | `minecraft:crafting_shapeless` | Paratonnerre érodé ciré (`minecraft:waxed_weathered_lightning_rod`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:waxed_chiseled_copper` | Cuivre sculpté ciré | Bloc et objet | 4 |
+| `minecraft:waxed_copper_bars` | Barreaux de cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_block` | Bloc de cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_bulb` | Ampoule en cuivre ciré | Bloc et objet | 2 |
+| `minecraft:waxed_copper_chain` | Chaîne en cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_chest` | Coffre en cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_door` | Porte en cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_golem_statue` | Statue de golem de cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_grate` | Grille en cuivre ciré | Bloc et objet | 3 |
+| `minecraft:waxed_copper_lantern` | Lanterne en cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_copper_trapdoor` | Trappe en cuivre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_cut_copper` | Cuivre taillé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_cut_copper_slab` | Dalle de cuivre taillé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_cut_copper_stairs` | Escalier en cuivre taillé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_exposed_chiseled_copper` | Cuivre sculpté exposé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_exposed_copper` | Cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_bars` | Barreaux de cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_bulb` | Ampoule en cuivre exposé ciré | Bloc et objet | 2 |
+| `minecraft:waxed_exposed_copper_chain` | Chaîne en cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_chest` | Coffre en cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_door` | Porte en cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_golem_statue` | Statue de golem de cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_grate` | Grille en cuivre exposé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_exposed_copper_lantern` | Lanterne en cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_copper_trapdoor` | Trappe en cuivre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_exposed_cut_copper` | Cuivre taillé exposé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_exposed_cut_copper_slab` | Dalle de cuivre taillé exposé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_exposed_cut_copper_stairs` | Escalier en cuivre taillé exposé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_exposed_lightning_rod` | Paratonnerre exposé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_lightning_rod` | Paratonnerre ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_chiseled_copper` | Cuivre sculpté oxydé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_oxidized_copper` | Cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_bars` | Barreaux de cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_bulb` | Ampoule en cuivre oxydé ciré | Bloc et objet | 2 |
+| `minecraft:waxed_oxidized_copper_chain` | Chaîne en cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_chest` | Coffre en cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_door` | Porte en cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_golem_statue` | Statue de golem de cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_grate` | Grille en cuivre oxydé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_oxidized_copper_lantern` | Lanterne en cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_copper_trapdoor` | Trappe en cuivre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_oxidized_cut_copper` | Cuivre taillé oxydé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_oxidized_cut_copper_slab` | Dalle de cuivre taillé oxydé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_oxidized_cut_copper_stairs` | Escalier en cuivre taillé oxydé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_oxidized_lightning_rod` | Paratonnerre oxydé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_chiseled_copper` | Cuivre sculpté érodé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_weathered_copper` | Cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_bars` | Barreaux de cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_bulb` | Ampoule en cuivre érodé ciré | Bloc et objet | 2 |
+| `minecraft:waxed_weathered_copper_chain` | Chaîne en cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_chest` | Coffre en cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_door` | Porte en cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_golem_statue` | Statue de golem de cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_grate` | Grille en cuivre érodé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_weathered_copper_lantern` | Lanterne en cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_copper_trapdoor` | Trappe en cuivre érodé ciré | Bloc et objet | 1 |
+| `minecraft:waxed_weathered_cut_copper` | Cuivre taillé érodé ciré | Bloc et objet | 3 |
+| `minecraft:waxed_weathered_cut_copper_slab` | Dalle de cuivre taillé érodé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_weathered_cut_copper_stairs` | Escalier en cuivre taillé érodé ciré | Bloc et objet | 4 |
+| `minecraft:waxed_weathered_lightning_rod` | Paratonnerre érodé ciré | Bloc et objet | 1 |
+
+## C21
+
+**Fer et forge de base · Ironworking** — [déclencheurs et règles](collections-minecraft.md#c21).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:anvil` | `minecraft:crafting_shaped` | Enclume (`minecraft:anvil`) |
+| `minecraft:blast_furnace` | `minecraft:crafting_shaped` | Haut fourneau (`minecraft:blast_furnace`) |
+| `minecraft:bucket` | `minecraft:crafting_shaped` | Seau (`minecraft:bucket`) |
+| `minecraft:flint_and_steel` | `minecraft:crafting_shapeless` | Briquet (`minecraft:flint_and_steel`) |
+| `minecraft:iron_axe` | `minecraft:crafting_shaped` | Hache en fer (`minecraft:iron_axe`) |
+| `minecraft:iron_bars` | `minecraft:crafting_shaped` | Barreaux de fer (`minecraft:iron_bars`) × 16 |
+| `minecraft:iron_block` | `minecraft:crafting_shaped` | Bloc de fer (`minecraft:iron_block`) |
+| `minecraft:iron_boots` | `minecraft:crafting_shaped` | Bottes en fer (`minecraft:iron_boots`) |
+| `minecraft:iron_chain` | `minecraft:crafting_shaped` | Chaîne en fer (`minecraft:iron_chain`) |
+| `minecraft:iron_chestplate` | `minecraft:crafting_shaped` | Plastron en fer (`minecraft:iron_chestplate`) |
+| `minecraft:iron_door` | `minecraft:crafting_shaped` | Porte en fer (`minecraft:iron_door`) × 3 |
+| `minecraft:iron_helmet` | `minecraft:crafting_shaped` | Casque en fer (`minecraft:iron_helmet`) |
+| `minecraft:iron_hoe` | `minecraft:crafting_shaped` | Houe en fer (`minecraft:iron_hoe`) |
+| `minecraft:iron_ingot_from_blasting_deepslate_iron_ore` | `minecraft:blasting` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_ingot_from_blasting_iron_ore` | `minecraft:blasting` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_ingot_from_blasting_raw_iron` | `minecraft:blasting` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_ingot_from_iron_block` | `minecraft:crafting_shapeless` | Lingot de fer (`minecraft:iron_ingot`) × 9 |
+| `minecraft:iron_ingot_from_nuggets` | `minecraft:crafting_shaped` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_ingot_from_smelting_deepslate_iron_ore` | `minecraft:smelting` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_ingot_from_smelting_iron_ore` | `minecraft:smelting` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_ingot_from_smelting_raw_iron` | `minecraft:smelting` | Lingot de fer (`minecraft:iron_ingot`) |
+| `minecraft:iron_leggings` | `minecraft:crafting_shaped` | Jambières en fer (`minecraft:iron_leggings`) |
+| `minecraft:iron_nugget` | `minecraft:crafting_shapeless` | Pépite de fer (`minecraft:iron_nugget`) × 9 |
+| `minecraft:iron_nugget_from_blasting` | `minecraft:blasting` | Pépite de fer (`minecraft:iron_nugget`) |
+| `minecraft:iron_nugget_from_smelting` | `minecraft:smelting` | Pépite de fer (`minecraft:iron_nugget`) |
+| `minecraft:iron_pickaxe` | `minecraft:crafting_shaped` | Pioche en fer (`minecraft:iron_pickaxe`) |
+| `minecraft:iron_shovel` | `minecraft:crafting_shaped` | Pelle en fer (`minecraft:iron_shovel`) |
+| `minecraft:iron_spear` | `minecraft:crafting_shaped` | Lance en fer (`minecraft:iron_spear`) |
+| `minecraft:iron_sword` | `minecraft:crafting_shaped` | Épée en fer (`minecraft:iron_sword`) |
+| `minecraft:iron_trapdoor` | `minecraft:crafting_shaped` | Trappe en fer (`minecraft:iron_trapdoor`) |
+| `minecraft:raw_iron` | `minecraft:crafting_shapeless` | Fer brut (`minecraft:raw_iron`) × 9 |
+| `minecraft:raw_iron_block` | `minecraft:crafting_shaped` | Bloc de fer brut (`minecraft:raw_iron_block`) |
+| `minecraft:shears` | `minecraft:crafting_shaped` | Cisailles (`minecraft:shears`) |
+| `minecraft:shield` | `minecraft:crafting_shaped` | Bouclier (`minecraft:shield`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:anvil` | Enclume | Bloc et objet | 1 |
+| `minecraft:blast_furnace` | Haut fourneau | Bloc et objet | 1 |
+| `minecraft:bucket` | Seau | Objet | 1 |
+| `minecraft:chainmail_boots` | Bottes de mailles | Objet | 0 |
+| `minecraft:chainmail_chestplate` | Cotte de mailles | Objet | 0 |
+| `minecraft:chainmail_helmet` | Casque de mailles | Objet | 0 |
+| `minecraft:chainmail_leggings` | Jambières de mailles | Objet | 0 |
+| `minecraft:chipped_anvil` | Enclume ébréchée | Bloc et objet | 0 |
+| `minecraft:damaged_anvil` | Enclume endommagée | Bloc et objet | 0 |
+| `minecraft:deepslate_iron_ore` | Minerai de fer des abîmes | Bloc et objet | 0 |
+| `minecraft:flint_and_steel` | Briquet | Objet | 1 |
+| `minecraft:iron_axe` | Hache en fer | Objet | 1 |
+| `minecraft:iron_bars` | Barreaux de fer | Bloc et objet | 1 |
+| `minecraft:iron_block` | Bloc de fer | Bloc et objet | 1 |
+| `minecraft:iron_boots` | Bottes en fer | Objet | 1 |
+| `minecraft:iron_chain` | Chaîne en fer | Bloc et objet | 1 |
+| `minecraft:iron_chestplate` | Plastron en fer | Objet | 1 |
+| `minecraft:iron_door` | Porte en fer | Bloc et objet | 1 |
+| `minecraft:iron_helmet` | Casque en fer | Objet | 1 |
+| `minecraft:iron_hoe` | Houe en fer | Objet | 1 |
+| `minecraft:iron_ingot` | Lingot de fer | Objet | 8 |
+| `minecraft:iron_leggings` | Jambières en fer | Objet | 1 |
+| `minecraft:iron_nugget` | Pépite de fer | Objet | 3 |
+| `minecraft:iron_ore` | Minerai de fer | Bloc et objet | 0 |
+| `minecraft:iron_pickaxe` | Pioche en fer | Objet | 1 |
+| `minecraft:iron_shovel` | Pelle en fer | Objet | 1 |
+| `minecraft:iron_spear` | Lance en fer | Objet | 1 |
+| `minecraft:iron_sword` | Épée en fer | Objet | 1 |
+| `minecraft:iron_trapdoor` | Trappe en fer | Bloc et objet | 1 |
+| `minecraft:lava_bucket` | Seau de lave | Objet | 0 |
+| `minecraft:raw_iron` | Fer brut | Objet | 1 |
+| `minecraft:raw_iron_block` | Bloc de fer brut | Bloc et objet | 1 |
+| `minecraft:shears` | Cisailles | Objet | 1 |
+| `minecraft:shield` | Bouclier | Objet | 2 |
+| `minecraft:water_bucket` | Seau d'eau | Objet | 0 |
+
+## C22
+
+**Or et dorure · Goldworking** — [déclencheurs et règles](collections-minecraft.md#c22).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:gold_block` | `minecraft:crafting_shaped` | Bloc d'or (`minecraft:gold_block`) |
+| `minecraft:gold_ingot_from_blasting_deepslate_gold_ore` | `minecraft:blasting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_blasting_gold_ore` | `minecraft:blasting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_blasting_nether_gold_ore` | `minecraft:blasting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_blasting_raw_gold` | `minecraft:blasting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_gold_block` | `minecraft:crafting_shapeless` | Lingot d'or (`minecraft:gold_ingot`) × 9 |
+| `minecraft:gold_ingot_from_nuggets` | `minecraft:crafting_shaped` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_smelting_deepslate_gold_ore` | `minecraft:smelting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_smelting_gold_ore` | `minecraft:smelting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_smelting_nether_gold_ore` | `minecraft:smelting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_ingot_from_smelting_raw_gold` | `minecraft:smelting` | Lingot d'or (`minecraft:gold_ingot`) |
+| `minecraft:gold_nugget` | `minecraft:crafting_shapeless` | Pépite d'or (`minecraft:gold_nugget`) × 9 |
+| `minecraft:gold_nugget_from_blasting` | `minecraft:blasting` | Pépite d'or (`minecraft:gold_nugget`) |
+| `minecraft:gold_nugget_from_smelting` | `minecraft:smelting` | Pépite d'or (`minecraft:gold_nugget`) |
+| `minecraft:golden_axe` | `minecraft:crafting_shaped` | Hache en or (`minecraft:golden_axe`) |
+| `minecraft:golden_boots` | `minecraft:crafting_shaped` | Bottes en or (`minecraft:golden_boots`) |
+| `minecraft:golden_chestplate` | `minecraft:crafting_shaped` | Plastron en or (`minecraft:golden_chestplate`) |
+| `minecraft:golden_helmet` | `minecraft:crafting_shaped` | Casque en or (`minecraft:golden_helmet`) |
+| `minecraft:golden_hoe` | `minecraft:crafting_shaped` | Houe en or (`minecraft:golden_hoe`) |
+| `minecraft:golden_leggings` | `minecraft:crafting_shaped` | Jambières en or (`minecraft:golden_leggings`) |
+| `minecraft:golden_pickaxe` | `minecraft:crafting_shaped` | Pioche en or (`minecraft:golden_pickaxe`) |
+| `minecraft:golden_shovel` | `minecraft:crafting_shaped` | Pelle en or (`minecraft:golden_shovel`) |
+| `minecraft:golden_spear` | `minecraft:crafting_shaped` | Lance en or (`minecraft:golden_spear`) |
+| `minecraft:golden_sword` | `minecraft:crafting_shaped` | Épée en or (`minecraft:golden_sword`) |
+| `minecraft:raw_gold` | `minecraft:crafting_shapeless` | Or brut (`minecraft:raw_gold`) × 9 |
+| `minecraft:raw_gold_block` | `minecraft:crafting_shaped` | Bloc d'or brut (`minecraft:raw_gold_block`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:deepslate_gold_ore` | Minerai d'or des abîmes | Bloc et objet | 0 |
+| `minecraft:gold_block` | Bloc d'or | Bloc et objet | 1 |
+| `minecraft:gold_ingot` | Lingot d'or | Objet | 10 |
+| `minecraft:gold_nugget` | Pépite d'or | Objet | 3 |
+| `minecraft:gold_ore` | Minerai d'or | Bloc et objet | 0 |
+| `minecraft:golden_axe` | Hache en or | Objet | 1 |
+| `minecraft:golden_boots` | Bottes en or | Objet | 1 |
+| `minecraft:golden_chestplate` | Plastron en or | Objet | 1 |
+| `minecraft:golden_helmet` | Casque en or | Objet | 1 |
+| `minecraft:golden_hoe` | Houe en or | Objet | 1 |
+| `minecraft:golden_leggings` | Jambières en or | Objet | 1 |
+| `minecraft:golden_pickaxe` | Pioche en or | Objet | 1 |
+| `minecraft:golden_shovel` | Pelle en or | Objet | 1 |
+| `minecraft:golden_spear` | Lance en or | Objet | 1 |
+| `minecraft:golden_sword` | Épée en or | Objet | 1 |
+| `minecraft:raw_gold` | Or brut | Objet | 1 |
+| `minecraft:raw_gold_block` | Bloc d'or brut | Bloc et objet | 1 |
+
+## C23
+
+**Diamants · Diamondworking** — [déclencheurs et règles](collections-minecraft.md#c23).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:diamond` | `minecraft:crafting_shapeless` | Diamant (`minecraft:diamond`) × 9 |
+| `minecraft:diamond_axe` | `minecraft:crafting_shaped` | Hache en diamant (`minecraft:diamond_axe`) |
+| `minecraft:diamond_block` | `minecraft:crafting_shaped` | Bloc de diamant (`minecraft:diamond_block`) |
+| `minecraft:diamond_boots` | `minecraft:crafting_shaped` | Bottes en diamant (`minecraft:diamond_boots`) |
+| `minecraft:diamond_chestplate` | `minecraft:crafting_shaped` | Plastron en diamant (`minecraft:diamond_chestplate`) |
+| `minecraft:diamond_from_blasting_deepslate_diamond_ore` | `minecraft:blasting` | Diamant (`minecraft:diamond`) |
+| `minecraft:diamond_from_blasting_diamond_ore` | `minecraft:blasting` | Diamant (`minecraft:diamond`) |
+| `minecraft:diamond_from_smelting_deepslate_diamond_ore` | `minecraft:smelting` | Diamant (`minecraft:diamond`) |
+| `minecraft:diamond_from_smelting_diamond_ore` | `minecraft:smelting` | Diamant (`minecraft:diamond`) |
+| `minecraft:diamond_helmet` | `minecraft:crafting_shaped` | Casque en diamant (`minecraft:diamond_helmet`) |
+| `minecraft:diamond_hoe` | `minecraft:crafting_shaped` | Houe en diamant (`minecraft:diamond_hoe`) |
+| `minecraft:diamond_leggings` | `minecraft:crafting_shaped` | Jambières en diamant (`minecraft:diamond_leggings`) |
+| `minecraft:diamond_pickaxe` | `minecraft:crafting_shaped` | Pioche en diamant (`minecraft:diamond_pickaxe`) |
+| `minecraft:diamond_shovel` | `minecraft:crafting_shaped` | Pelle en diamant (`minecraft:diamond_shovel`) |
+| `minecraft:diamond_spear` | `minecraft:crafting_shaped` | Lance en diamant (`minecraft:diamond_spear`) |
+| `minecraft:diamond_sword` | `minecraft:crafting_shaped` | Épée en diamant (`minecraft:diamond_sword`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:deepslate_diamond_ore` | Minerai de diamant des abîmes | Bloc et objet | 0 |
+| `minecraft:diamond` | Diamant | Objet | 5 |
+| `minecraft:diamond_axe` | Hache en diamant | Objet | 1 |
+| `minecraft:diamond_block` | Bloc de diamant | Bloc et objet | 1 |
+| `minecraft:diamond_boots` | Bottes en diamant | Objet | 1 |
+| `minecraft:diamond_chestplate` | Plastron en diamant | Objet | 1 |
+| `minecraft:diamond_helmet` | Casque en diamant | Objet | 1 |
+| `minecraft:diamond_hoe` | Houe en diamant | Objet | 1 |
+| `minecraft:diamond_leggings` | Jambières en diamant | Objet | 1 |
+| `minecraft:diamond_ore` | Minerai de diamant | Bloc et objet | 0 |
+| `minecraft:diamond_pickaxe` | Pioche en diamant | Objet | 1 |
+| `minecraft:diamond_shovel` | Pelle en diamant | Objet | 1 |
+| `minecraft:diamond_spear` | Lance en diamant | Objet | 1 |
+| `minecraft:diamond_sword` | Épée en diamant | Objet | 1 |
+
+## C24
+
+**Netherite · Netherite Smithing** — [déclencheurs et règles](collections-minecraft.md#c24).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:netherite_axe_smithing` | `minecraft:smithing_transform` | Hache en netherite (`minecraft:netherite_axe`) |
+| `minecraft:netherite_block` | `minecraft:crafting_shaped` | Bloc de netherite (`minecraft:netherite_block`) |
+| `minecraft:netherite_boots_smithing` | `minecraft:smithing_transform` | Bottes en netherite (`minecraft:netherite_boots`) |
+| `minecraft:netherite_chestplate_smithing` | `minecraft:smithing_transform` | Plastron en netherite (`minecraft:netherite_chestplate`) |
+| `minecraft:netherite_helmet_smithing` | `minecraft:smithing_transform` | Casque en netherite (`minecraft:netherite_helmet`) |
+| `minecraft:netherite_hoe_smithing` | `minecraft:smithing_transform` | Houe en netherite (`minecraft:netherite_hoe`) |
+| `minecraft:netherite_horse_armor_smithing` | `minecraft:smithing_transform` | Armure en netherite pour cheval (`minecraft:netherite_horse_armor`) |
+| `minecraft:netherite_ingot` | `minecraft:crafting_shapeless` | Lingot de netherite (`minecraft:netherite_ingot`) |
+| `minecraft:netherite_ingot_from_netherite_block` | `minecraft:crafting_shapeless` | Lingot de netherite (`minecraft:netherite_ingot`) × 9 |
+| `minecraft:netherite_leggings_smithing` | `minecraft:smithing_transform` | Jambières en netherite (`minecraft:netherite_leggings`) |
+| `minecraft:netherite_nautilus_armor_smithing` | `minecraft:smithing_transform` | Armure en netherite pour nautile (`minecraft:netherite_nautilus_armor`) |
+| `minecraft:netherite_pickaxe_smithing` | `minecraft:smithing_transform` | Pioche en netherite (`minecraft:netherite_pickaxe`) |
+| `minecraft:netherite_scrap` | `minecraft:smelting` | Fragments de netherite (`minecraft:netherite_scrap`) |
+| `minecraft:netherite_scrap_from_blasting` | `minecraft:blasting` | Fragments de netherite (`minecraft:netherite_scrap`) |
+| `minecraft:netherite_shovel_smithing` | `minecraft:smithing_transform` | Pelle en netherite (`minecraft:netherite_shovel`) |
+| `minecraft:netherite_spear_smithing` | `minecraft:smithing_transform` | Lance en netherite (`minecraft:netherite_spear`) |
+| `minecraft:netherite_sword_smithing` | `minecraft:smithing_transform` | Épée en netherite (`minecraft:netherite_sword`) |
+| `minecraft:netherite_upgrade_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:netherite_upgrade_smithing_template`) × 2 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:ancient_debris` | Débris antiques | Bloc et objet | 0 |
+| `minecraft:netherite_axe` | Hache en netherite | Objet | 1 |
+| `minecraft:netherite_block` | Bloc de netherite | Bloc et objet | 1 |
+| `minecraft:netherite_boots` | Bottes en netherite | Objet | 1 |
+| `minecraft:netherite_chestplate` | Plastron en netherite | Objet | 1 |
+| `minecraft:netherite_helmet` | Casque en netherite | Objet | 1 |
+| `minecraft:netherite_hoe` | Houe en netherite | Objet | 1 |
+| `minecraft:netherite_horse_armor` | Armure en netherite pour cheval | Objet | 1 |
+| `minecraft:netherite_ingot` | Lingot de netherite | Objet | 2 |
+| `minecraft:netherite_leggings` | Jambières en netherite | Objet | 1 |
+| `minecraft:netherite_nautilus_armor` | Armure en netherite pour nautile | Objet | 1 |
+| `minecraft:netherite_pickaxe` | Pioche en netherite | Objet | 1 |
+| `minecraft:netherite_scrap` | Fragments de netherite | Objet | 2 |
+| `minecraft:netherite_shovel` | Pelle en netherite | Objet | 1 |
+| `minecraft:netherite_spear` | Lance en netherite | Objet | 1 |
+| `minecraft:netherite_sword` | Épée en netherite | Objet | 1 |
+| `minecraft:netherite_upgrade_smithing_template` | Modèle de forge | Objet | 1 |
+
+## C25
+
+**Charbon, feu et éclairage · Fire and Light** — [déclencheurs et règles](collections-minecraft.md#c25).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_candle` | `minecraft:crafting_shapeless` | Bougie noire (`minecraft:black_candle`) |
+| `minecraft:blue_candle` | `minecraft:crafting_shapeless` | Bougie bleue (`minecraft:blue_candle`) |
+| `minecraft:brown_candle` | `minecraft:crafting_shapeless` | Bougie marron (`minecraft:brown_candle`) |
+| `minecraft:campfire` | `minecraft:crafting_shaped` | Feu de camp (`minecraft:campfire`) |
+| `minecraft:candle` | `minecraft:crafting_shaped` | Bougie (`minecraft:candle`) |
+| `minecraft:charcoal` | `minecraft:smelting` | Charbon de bois (`minecraft:charcoal`) |
+| `minecraft:coal` | `minecraft:crafting_shapeless` | Charbon (`minecraft:coal`) × 9 |
+| `minecraft:coal_block` | `minecraft:crafting_shaped` | Bloc de charbon (`minecraft:coal_block`) |
+| `minecraft:coal_from_blasting_coal_ore` | `minecraft:blasting` | Charbon (`minecraft:coal`) |
+| `minecraft:coal_from_blasting_deepslate_coal_ore` | `minecraft:blasting` | Charbon (`minecraft:coal`) |
+| `minecraft:coal_from_smelting_coal_ore` | `minecraft:smelting` | Charbon (`minecraft:coal`) |
+| `minecraft:coal_from_smelting_deepslate_coal_ore` | `minecraft:smelting` | Charbon (`minecraft:coal`) |
+| `minecraft:cyan_candle` | `minecraft:crafting_shapeless` | Bougie cyan (`minecraft:cyan_candle`) |
+| `minecraft:gray_candle` | `minecraft:crafting_shapeless` | Bougie grise (`minecraft:gray_candle`) |
+| `minecraft:green_candle` | `minecraft:crafting_shapeless` | Bougie verte (`minecraft:green_candle`) |
+| `minecraft:jack_o_lantern` | `minecraft:crafting_shaped` | Citrouille-lanterne (`minecraft:jack_o_lantern`) |
+| `minecraft:lantern` | `minecraft:crafting_shaped` | Lanterne (`minecraft:lantern`) |
+| `minecraft:light_blue_candle` | `minecraft:crafting_shapeless` | Bougie bleu clair (`minecraft:light_blue_candle`) |
+| `minecraft:light_gray_candle` | `minecraft:crafting_shapeless` | Bougie gris clair (`minecraft:light_gray_candle`) |
+| `minecraft:lime_candle` | `minecraft:crafting_shapeless` | Bougie vert clair (`minecraft:lime_candle`) |
+| `minecraft:magenta_candle` | `minecraft:crafting_shapeless` | Bougie magenta (`minecraft:magenta_candle`) |
+| `minecraft:orange_candle` | `minecraft:crafting_shapeless` | Bougie orange (`minecraft:orange_candle`) |
+| `minecraft:pink_candle` | `minecraft:crafting_shapeless` | Bougie rose (`minecraft:pink_candle`) |
+| `minecraft:purple_candle` | `minecraft:crafting_shapeless` | Bougie violette (`minecraft:purple_candle`) |
+| `minecraft:red_candle` | `minecraft:crafting_shapeless` | Bougie rouge (`minecraft:red_candle`) |
+| `minecraft:torch` | `minecraft:crafting_shaped` | Torche (`minecraft:torch`) × 4 |
+| `minecraft:white_candle` | `minecraft:crafting_shapeless` | Bougie blanche (`minecraft:white_candle`) |
+| `minecraft:yellow_candle` | `minecraft:crafting_shapeless` | Bougie jaune (`minecraft:yellow_candle`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_candle` | Bougie noire | Bloc et objet | 1 |
+| `minecraft:black_candle_cake` | Gâteau à bougie noire | Bloc sans objet homonyme | 0 |
+| `minecraft:blue_candle` | Bougie bleue | Bloc et objet | 1 |
+| `minecraft:blue_candle_cake` | Gâteau à bougie bleue | Bloc sans objet homonyme | 0 |
+| `minecraft:brown_candle` | Bougie marron | Bloc et objet | 1 |
+| `minecraft:brown_candle_cake` | Gâteau à bougie marron | Bloc sans objet homonyme | 0 |
+| `minecraft:campfire` | Feu de camp | Bloc et objet | 1 |
+| `minecraft:candle` | Bougie | Bloc et objet | 1 |
+| `minecraft:candle_cake` | Gâteau à bougie | Bloc sans objet homonyme | 0 |
+| `minecraft:charcoal` | Charbon de bois | Objet | 1 |
+| `minecraft:coal` | Charbon | Objet | 5 |
+| `minecraft:coal_block` | Bloc de charbon | Bloc et objet | 1 |
+| `minecraft:coal_ore` | Minerai de charbon | Bloc et objet | 0 |
+| `minecraft:cyan_candle` | Bougie cyan | Bloc et objet | 1 |
+| `minecraft:cyan_candle_cake` | Gâteau à bougie cyan | Bloc sans objet homonyme | 0 |
+| `minecraft:deepslate_coal_ore` | Minerai de charbon des abîmes | Bloc et objet | 0 |
+| `minecraft:fire` | Feu | Bloc sans objet homonyme | 0 |
+| `minecraft:gray_candle` | Bougie grise | Bloc et objet | 1 |
+| `minecraft:gray_candle_cake` | Gâteau à bougie grise | Bloc sans objet homonyme | 0 |
+| `minecraft:green_candle` | Bougie verte | Bloc et objet | 1 |
+| `minecraft:green_candle_cake` | Gâteau à bougie verte | Bloc sans objet homonyme | 0 |
+| `minecraft:jack_o_lantern` | Citrouille-lanterne | Bloc et objet | 1 |
+| `minecraft:lantern` | Lanterne | Bloc et objet | 1 |
+| `minecraft:light_blue_candle` | Bougie bleu clair | Bloc et objet | 1 |
+| `minecraft:light_blue_candle_cake` | Gâteau à bougie bleu clair | Bloc sans objet homonyme | 0 |
+| `minecraft:light_gray_candle` | Bougie gris clair | Bloc et objet | 1 |
+| `minecraft:light_gray_candle_cake` | Gâteau à bougie gris clair | Bloc sans objet homonyme | 0 |
+| `minecraft:lime_candle` | Bougie vert clair | Bloc et objet | 1 |
+| `minecraft:lime_candle_cake` | Gâteau à bougie vert clair | Bloc sans objet homonyme | 0 |
+| `minecraft:magenta_candle` | Bougie magenta | Bloc et objet | 1 |
+| `minecraft:magenta_candle_cake` | Gâteau à bougie magenta | Bloc sans objet homonyme | 0 |
+| `minecraft:orange_candle` | Bougie orange | Bloc et objet | 1 |
+| `minecraft:orange_candle_cake` | Gâteau à bougie orange | Bloc sans objet homonyme | 0 |
+| `minecraft:pink_candle` | Bougie rose | Bloc et objet | 1 |
+| `minecraft:pink_candle_cake` | Gâteau à bougie rose | Bloc sans objet homonyme | 0 |
+| `minecraft:purple_candle` | Bougie violette | Bloc et objet | 1 |
+| `minecraft:purple_candle_cake` | Gâteau à bougie violette | Bloc sans objet homonyme | 0 |
+| `minecraft:red_candle` | Bougie rouge | Bloc et objet | 1 |
+| `minecraft:red_candle_cake` | Gâteau à bougie rouge | Bloc sans objet homonyme | 0 |
+| `minecraft:torch` | Torche | Bloc et objet | 1 |
+| `minecraft:wall_torch` | Torche murale | Bloc sans objet homonyme | 0 |
+| `minecraft:white_candle` | Bougie blanche | Bloc et objet | 1 |
+| `minecraft:white_candle_cake` | Gâteau à bougie blanche | Bloc sans objet homonyme | 0 |
+| `minecraft:yellow_candle` | Bougie jaune | Bloc et objet | 1 |
+| `minecraft:yellow_candle_cake` | Gâteau à bougie jaune | Bloc sans objet homonyme | 0 |
+
+## C26
+
+**Redstone et mesure · Redstone Basics** — [déclencheurs et règles](collections-minecraft.md#c26).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:comparator` | `minecraft:crafting_shaped` | Comparateur de redstone (`minecraft:comparator`) |
+| `minecraft:daylight_detector` | `minecraft:crafting_shaped` | Capteur de lumière (`minecraft:daylight_detector`) |
+| `minecraft:heavy_weighted_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression pondérée lourde (`minecraft:heavy_weighted_pressure_plate`) |
+| `minecraft:lectern` | `minecraft:crafting_shaped` | Pupitre (`minecraft:lectern`) |
+| `minecraft:lever` | `minecraft:crafting_shaped` | Levier (`minecraft:lever`) |
+| `minecraft:light_weighted_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression pondérée légère (`minecraft:light_weighted_pressure_plate`) |
+| `minecraft:polished_blackstone_button` | `minecraft:crafting_shapeless` | Bouton en pierre noire (`minecraft:polished_blackstone_button`) |
+| `minecraft:polished_blackstone_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en pierre noire (`minecraft:polished_blackstone_pressure_plate`) |
+| `minecraft:redstone` | `minecraft:crafting_shapeless` | Poudre de redstone (`minecraft:redstone`) × 9 |
+| `minecraft:redstone_block` | `minecraft:crafting_shaped` | Bloc de redstone (`minecraft:redstone_block`) |
+| `minecraft:redstone_from_blasting_deepslate_redstone_ore` | `minecraft:blasting` | Poudre de redstone (`minecraft:redstone`) |
+| `minecraft:redstone_from_blasting_redstone_ore` | `minecraft:blasting` | Poudre de redstone (`minecraft:redstone`) |
+| `minecraft:redstone_from_smelting_deepslate_redstone_ore` | `minecraft:smelting` | Poudre de redstone (`minecraft:redstone`) |
+| `minecraft:redstone_from_smelting_redstone_ore` | `minecraft:smelting` | Poudre de redstone (`minecraft:redstone`) |
+| `minecraft:redstone_torch` | `minecraft:crafting_shaped` | Torche de redstone (`minecraft:redstone_torch`) |
+| `minecraft:repeater` | `minecraft:crafting_shaped` | Répéteur de redstone (`minecraft:repeater`) |
+| `minecraft:stone_button` | `minecraft:crafting_shapeless` | Bouton en pierre (`minecraft:stone_button`) |
+| `minecraft:stone_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression en pierre (`minecraft:stone_pressure_plate`) |
+| `minecraft:target` | `minecraft:crafting_shaped` | Cible (`minecraft:target`) |
+| `minecraft:tripwire_hook` | `minecraft:crafting_shaped` | Crochet (`minecraft:tripwire_hook`) × 2 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:comparator` | Comparateur de redstone | Bloc et objet | 1 |
+| `minecraft:daylight_detector` | Capteur de lumière | Bloc et objet | 1 |
+| `minecraft:deepslate_redstone_ore` | Minerai de redstone des abîmes | Bloc et objet | 0 |
+| `minecraft:heavy_weighted_pressure_plate` | Plaque de pression pondérée lourde | Bloc et objet | 1 |
+| `minecraft:lectern` | Pupitre | Bloc et objet | 1 |
+| `minecraft:lever` | Levier | Bloc et objet | 1 |
+| `minecraft:light_weighted_pressure_plate` | Plaque de pression pondérée légère | Bloc et objet | 1 |
+| `minecraft:polished_blackstone_button` | Bouton en pierre noire | Bloc et objet | 1 |
+| `minecraft:polished_blackstone_pressure_plate` | Plaque de pression en pierre noire | Bloc et objet | 1 |
+| `minecraft:redstone` | Poudre de redstone | Objet | 5 |
+| `minecraft:redstone_block` | Bloc de redstone | Bloc et objet | 1 |
+| `minecraft:redstone_ore` | Minerai de redstone | Bloc et objet | 0 |
+| `minecraft:redstone_torch` | Torche de redstone | Bloc et objet | 1 |
+| `minecraft:redstone_wall_torch` | Torche de redstone murale | Bloc sans objet homonyme | 0 |
+| `minecraft:redstone_wire` | Câble de redstone | Bloc sans objet homonyme | 0 |
+| `minecraft:repeater` | Répéteur de redstone | Bloc et objet | 1 |
+| `minecraft:stone_button` | Bouton en pierre | Bloc et objet | 1 |
+| `minecraft:stone_pressure_plate` | Plaque de pression en pierre | Bloc et objet | 1 |
+| `minecraft:target` | Cible | Bloc et objet | 1 |
+| `minecraft:tripwire` | Fil de déclenchement | Bloc sans objet homonyme | 0 |
+| `minecraft:tripwire_hook` | Crochet | Bloc et objet | 1 |
+
+## C27
+
+**Machines et automatisation · Automation** — [déclencheurs et règles](collections-minecraft.md#c27).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:crafter` | `minecraft:crafting_shaped` | Fabricateur (`minecraft:crafter`) |
+| `minecraft:dispenser` | `minecraft:crafting_shaped` | Distributeur (`minecraft:dispenser`) |
+| `minecraft:dropper` | `minecraft:crafting_shaped` | Dropper (`minecraft:dropper`) |
+| `minecraft:hopper` | `minecraft:crafting_shaped` | Entonnoir (`minecraft:hopper`) |
+| `minecraft:observer` | `minecraft:crafting_shaped` | Observateur (`minecraft:observer`) |
+| `minecraft:piston` | `minecraft:crafting_shaped` | Piston (`minecraft:piston`) |
+| `minecraft:redstone_lamp` | `minecraft:crafting_shaped` | Lampe à redstone (`minecraft:redstone_lamp`) |
+| `minecraft:slime_ball` | `minecraft:crafting_shapeless` | Boule de slime (`minecraft:slime_ball`) × 9 |
+| `minecraft:slime_block` | `minecraft:crafting_shaped` | Bloc de slime (`minecraft:slime_block`) |
+| `minecraft:sticky_piston` | `minecraft:crafting_shaped` | Piston collant (`minecraft:sticky_piston`) |
+| `minecraft:trapped_chest` | `minecraft:crafting_shapeless` | Coffre piégé (`minecraft:trapped_chest`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:crafter` | Fabricateur | Bloc et objet | 1 |
+| `minecraft:dispenser` | Distributeur | Bloc et objet | 1 |
+| `minecraft:dropper` | Dropper | Bloc et objet | 1 |
+| `minecraft:hopper` | Entonnoir | Bloc et objet | 1 |
+| `minecraft:moving_piston` | Bloc en mouvement | Bloc sans objet homonyme | 0 |
+| `minecraft:observer` | Observateur | Bloc et objet | 1 |
+| `minecraft:piston` | Piston | Bloc et objet | 1 |
+| `minecraft:piston_head` | Tête de piston | Bloc sans objet homonyme | 0 |
+| `minecraft:redstone_lamp` | Lampe à redstone | Bloc et objet | 1 |
+| `minecraft:slime_ball` | Boule de slime | Objet | 1 |
+| `minecraft:slime_block` | Bloc de slime | Bloc et objet | 1 |
+| `minecraft:sticky_piston` | Piston collant | Bloc et objet | 1 |
+| `minecraft:trapped_chest` | Coffre piégé | Bloc et objet | 1 |
+
+## C28
+
+**Rails et wagonnets · Railways** — [déclencheurs et règles](collections-minecraft.md#c28).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:activator_rail` | `minecraft:crafting_shaped` | Rails déclencheurs (`minecraft:activator_rail`) × 6 |
+| `minecraft:chest_minecart` | `minecraft:crafting_shapeless` | Wagonnet de stockage (`minecraft:chest_minecart`) |
+| `minecraft:detector_rail` | `minecraft:crafting_shaped` | Rails détecteurs (`minecraft:detector_rail`) × 6 |
+| `minecraft:furnace_minecart` | `minecraft:crafting_shapeless` | Wagonnet motorisé (`minecraft:furnace_minecart`) |
+| `minecraft:hopper_minecart` | `minecraft:crafting_shapeless` | Wagonnet à entonnoir (`minecraft:hopper_minecart`) |
+| `minecraft:minecart` | `minecraft:crafting_shaped` | Wagonnet (`minecraft:minecart`) |
+| `minecraft:powered_rail` | `minecraft:crafting_shaped` | Rails de propulsion (`minecraft:powered_rail`) × 6 |
+| `minecraft:rail` | `minecraft:crafting_shaped` | Rails (`minecraft:rail`) × 16 |
+| `minecraft:tnt_minecart` | `minecraft:crafting_shapeless` | Wagonnet à TNT (`minecraft:tnt_minecart`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:activator_rail` | Rails déclencheurs | Bloc et objet | 1 |
+| `minecraft:chest_minecart` | Wagonnet de stockage | Objet | 1 |
+| `minecraft:command_block_minecart` | Wagonnet à bloc de commande | Objet | 0 |
+| `minecraft:detector_rail` | Rails détecteurs | Bloc et objet | 1 |
+| `minecraft:furnace_minecart` | Wagonnet motorisé | Objet | 1 |
+| `minecraft:hopper_minecart` | Wagonnet à entonnoir | Objet | 1 |
+| `minecraft:minecart` | Wagonnet | Objet | 1 |
+| `minecraft:powered_rail` | Rails de propulsion | Bloc et objet | 1 |
+| `minecraft:rail` | Rails | Bloc et objet | 1 |
+| `minecraft:tnt_minecart` | Wagonnet à TNT | Objet | 1 |
+
+## C29
+
+**Bateaux et radeaux · Boats and Rafts** — [déclencheurs et règles](collections-minecraft.md#c29).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:acacia_boat` | `minecraft:crafting_shaped` | Bateau en acacia (`minecraft:acacia_boat`) |
+| `minecraft:acacia_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en acacia (`minecraft:acacia_chest_boat`) |
+| `minecraft:bamboo_chest_raft` | `minecraft:crafting_shapeless` | Radeau de stockage en bambou (`minecraft:bamboo_chest_raft`) |
+| `minecraft:bamboo_raft` | `minecraft:crafting_shaped` | Radeau en bambou (`minecraft:bamboo_raft`) |
+| `minecraft:birch_boat` | `minecraft:crafting_shaped` | Bateau en bouleau (`minecraft:birch_boat`) |
+| `minecraft:birch_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en bouleau (`minecraft:birch_chest_boat`) |
+| `minecraft:cherry_boat` | `minecraft:crafting_shaped` | Bateau en cerisier (`minecraft:cherry_boat`) |
+| `minecraft:cherry_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en cerisier (`minecraft:cherry_chest_boat`) |
+| `minecraft:dark_oak_boat` | `minecraft:crafting_shaped` | Bateau en chêne noir (`minecraft:dark_oak_boat`) |
+| `minecraft:dark_oak_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en chêne noir (`minecraft:dark_oak_chest_boat`) |
+| `minecraft:jungle_boat` | `minecraft:crafting_shaped` | Bateau en acajou (`minecraft:jungle_boat`) |
+| `minecraft:jungle_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en acajou (`minecraft:jungle_chest_boat`) |
+| `minecraft:mangrove_boat` | `minecraft:crafting_shaped` | Bateau en palétuvier (`minecraft:mangrove_boat`) |
+| `minecraft:mangrove_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en palétuvier (`minecraft:mangrove_chest_boat`) |
+| `minecraft:oak_boat` | `minecraft:crafting_shaped` | Bateau en chêne (`minecraft:oak_boat`) |
+| `minecraft:oak_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en chêne (`minecraft:oak_chest_boat`) |
+| `minecraft:pale_oak_boat` | `minecraft:crafting_shaped` | Bateau en chêne pâle (`minecraft:pale_oak_boat`) |
+| `minecraft:pale_oak_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en chêne pâle (`minecraft:pale_oak_chest_boat`) |
+| `minecraft:poplar_boat` | `minecraft:crafting_shaped` | Bateau en peuplier (`minecraft:poplar_boat`) |
+| `minecraft:poplar_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en peuplier (`minecraft:poplar_chest_boat`) |
+| `minecraft:spruce_boat` | `minecraft:crafting_shaped` | Bateau en sapin (`minecraft:spruce_boat`) |
+| `minecraft:spruce_chest_boat` | `minecraft:crafting_shapeless` | Bateau de stockage en sapin (`minecraft:spruce_chest_boat`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:acacia_boat` | Bateau en acacia | Objet | 1 |
+| `minecraft:acacia_chest_boat` | Bateau de stockage en acacia | Objet | 1 |
+| `minecraft:bamboo_chest_raft` | Radeau de stockage en bambou | Objet | 1 |
+| `minecraft:bamboo_raft` | Radeau en bambou | Objet | 1 |
+| `minecraft:birch_boat` | Bateau en bouleau | Objet | 1 |
+| `minecraft:birch_chest_boat` | Bateau de stockage en bouleau | Objet | 1 |
+| `minecraft:cherry_boat` | Bateau en cerisier | Objet | 1 |
+| `minecraft:cherry_chest_boat` | Bateau de stockage en cerisier | Objet | 1 |
+| `minecraft:dark_oak_boat` | Bateau en chêne noir | Objet | 1 |
+| `minecraft:dark_oak_chest_boat` | Bateau de stockage en chêne noir | Objet | 1 |
+| `minecraft:jungle_boat` | Bateau en acajou | Objet | 1 |
+| `minecraft:jungle_chest_boat` | Bateau de stockage en acajou | Objet | 1 |
+| `minecraft:mangrove_boat` | Bateau en palétuvier | Objet | 1 |
+| `minecraft:mangrove_chest_boat` | Bateau de stockage en palétuvier | Objet | 1 |
+| `minecraft:oak_boat` | Bateau en chêne | Objet | 1 |
+| `minecraft:oak_chest_boat` | Bateau de stockage en chêne | Objet | 1 |
+| `minecraft:pale_oak_boat` | Bateau en chêne pâle | Objet | 1 |
+| `minecraft:pale_oak_chest_boat` | Bateau de stockage en chêne pâle | Objet | 1 |
+| `minecraft:poplar_boat` | Bateau en peuplier | Objet | 1 |
+| `minecraft:poplar_chest_boat` | Bateau de stockage en peuplier | Objet | 1 |
+| `minecraft:spruce_boat` | Bateau en sapin | Objet | 1 |
+| `minecraft:spruce_chest_boat` | Bateau de stockage en sapin | Objet | 1 |
+
+## C30
+
+**Cartographie et orientation · Maps and Navigation** — [déclencheurs et règles](collections-minecraft.md#c30).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:cartography_table` | `minecraft:crafting_shaped` | Table de cartographie (`minecraft:cartography_table`) |
+| `minecraft:clock` | `minecraft:crafting_shaped` | Montre (`minecraft:clock`) |
+| `minecraft:compass` | `minecraft:crafting_shaped` | Boussole (`minecraft:compass`) |
+| `minecraft:lodestone` | `minecraft:crafting_shaped` | Magnétite (`minecraft:lodestone`) |
+| `minecraft:map` | `minecraft:crafting_shaped` | Carte vierge (`minecraft:map`) |
+| `minecraft:map_cloning` | `minecraft:crafting_transmute` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:map_extending` | `minecraft:crafting_special_mapextending` | Dynamique : résultat issu de l’entrée ou de l’opération |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:abandoned_camp_map` | Carte de camp abandonné | Objet | 0 |
+| `minecraft:buried_ancient_city_map` | Carte d'ancienne cité souterraine | Objet | 0 |
+| `minecraft:buried_mineshaft_map` | Carte de puits de mine souterrain | Objet | 0 |
+| `minecraft:buried_treasure_map` | Carte au trésor | Objet | 0 |
+| `minecraft:buried_trial_chambers_map` | Carte de chambres des épreuves souterraines | Objet | 0 |
+| `minecraft:cartography_table` | Table de cartographie | Bloc et objet | 1 |
+| `minecraft:clock` | Montre | Objet | 1 |
+| `minecraft:compass` | Boussole | Objet | 1 |
+| `minecraft:desert_pyramid_map` | Carte de pyramide du désert | Objet | 0 |
+| `minecraft:desert_village_map` | Carte de village du désert | Objet | 0 |
+| `minecraft:filled_map` | Carte | Objet | 0 |
+| `minecraft:jungle_pyramid_map` | Carte de pyramide de la jungle | Objet | 0 |
+| `minecraft:lodestone` | Magnétite | Bloc et objet | 1 |
+| `minecraft:map` | Carte vierge | Objet | 1 |
+| `minecraft:ocean_monument_map` | Carte de monument océanique | Objet | 0 |
+| `minecraft:plains_village_map` | Carte de village des plaines | Objet | 0 |
+| `minecraft:savanna_village_map` | Carte de village de la savane | Objet | 0 |
+| `minecraft:snowy_village_map` | Carte de village des neiges | Objet | 0 |
+| `minecraft:swamp_hut_map` | Carte de cabane des marais | Objet | 0 |
+| `minecraft:taiga_village_map` | Carte de village de la taïga | Objet | 0 |
+| `minecraft:warm_ocean_ruins_map` | Carte de ruines océaniques chaudes | Objet | 0 |
+| `minecraft:woodland_mansion_map` | Carte de manoir des bois | Objet | 0 |
+
+## C31
+
+**Papier, livres et rangement · Books and Storage** — [déclencheurs et règles](collections-minecraft.md#c31).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_bundle` | `minecraft:crafting_transmute` | Sac noir (`minecraft:black_bundle`) |
+| `minecraft:blue_bundle` | `minecraft:crafting_transmute` | Sac bleu (`minecraft:blue_bundle`) |
+| `minecraft:book` | `minecraft:crafting_shapeless` | Livre (`minecraft:book`) |
+| `minecraft:book_cloning` | `minecraft:crafting_special_bookcloning` | Livre écrit (`minecraft:written_book`) |
+| `minecraft:bookshelf` | `minecraft:crafting_shaped` | Bibliothèque (`minecraft:bookshelf`) |
+| `minecraft:brown_bundle` | `minecraft:crafting_transmute` | Sac marron (`minecraft:brown_bundle`) |
+| `minecraft:bundle` | `minecraft:crafting_shaped` | Sac (`minecraft:bundle`) |
+| `minecraft:chiseled_bookshelf` | `minecraft:crafting_shaped` | Bibliothèque sculptée (`minecraft:chiseled_bookshelf`) |
+| `minecraft:cyan_bundle` | `minecraft:crafting_transmute` | Sac cyan (`minecraft:cyan_bundle`) |
+| `minecraft:ender_chest` | `minecraft:crafting_shaped` | Coffre de l'Ender (`minecraft:ender_chest`) |
+| `minecraft:glow_item_frame` | `minecraft:crafting_shapeless` | Cadre luisant (`minecraft:glow_item_frame`) |
+| `minecraft:gray_bundle` | `minecraft:crafting_transmute` | Sac gris (`minecraft:gray_bundle`) |
+| `minecraft:green_bundle` | `minecraft:crafting_transmute` | Sac vert (`minecraft:green_bundle`) |
+| `minecraft:item_frame` | `minecraft:crafting_shaped` | Cadre (`minecraft:item_frame`) |
+| `minecraft:light_blue_bundle` | `minecraft:crafting_transmute` | Sac bleu clair (`minecraft:light_blue_bundle`) |
+| `minecraft:light_gray_bundle` | `minecraft:crafting_transmute` | Sac gris clair (`minecraft:light_gray_bundle`) |
+| `minecraft:lime_bundle` | `minecraft:crafting_transmute` | Sac vert clair (`minecraft:lime_bundle`) |
+| `minecraft:magenta_bundle` | `minecraft:crafting_transmute` | Sac magenta (`minecraft:magenta_bundle`) |
+| `minecraft:name_tag` | `minecraft:crafting_shaped` | Étiquette (`minecraft:name_tag`) |
+| `minecraft:orange_bundle` | `minecraft:crafting_transmute` | Sac orange (`minecraft:orange_bundle`) |
+| `minecraft:painting` | `minecraft:crafting_shaped` | Tableau (`minecraft:painting`) |
+| `minecraft:paper` | `minecraft:crafting_shaped` | Papier (`minecraft:paper`) × 3 |
+| `minecraft:pink_bundle` | `minecraft:crafting_transmute` | Sac rose (`minecraft:pink_bundle`) |
+| `minecraft:purple_bundle` | `minecraft:crafting_transmute` | Sac violet (`minecraft:purple_bundle`) |
+| `minecraft:red_bundle` | `minecraft:crafting_transmute` | Sac rouge (`minecraft:red_bundle`) |
+| `minecraft:white_bundle` | `minecraft:crafting_transmute` | Sac blanc (`minecraft:white_bundle`) |
+| `minecraft:writable_book` | `minecraft:crafting_shapeless` | Livre et plume (`minecraft:writable_book`) |
+| `minecraft:yellow_bundle` | `minecraft:crafting_transmute` | Sac jaune (`minecraft:yellow_bundle`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_bundle` | Sac noir | Objet | 1 |
+| `minecraft:blue_bundle` | Sac bleu | Objet | 1 |
+| `minecraft:book` | Livre | Objet | 1 |
+| `minecraft:bookshelf` | Bibliothèque | Bloc et objet | 1 |
+| `minecraft:brown_bundle` | Sac marron | Objet | 1 |
+| `minecraft:bundle` | Sac | Objet | 1 |
+| `minecraft:chiseled_bookshelf` | Bibliothèque sculptée | Bloc et objet | 1 |
+| `minecraft:cyan_bundle` | Sac cyan | Objet | 1 |
+| `minecraft:ender_chest` | Coffre de l'Ender | Bloc et objet | 1 |
+| `minecraft:glow_item_frame` | Cadre luisant | Objet | 1 |
+| `minecraft:gray_bundle` | Sac gris | Objet | 1 |
+| `minecraft:green_bundle` | Sac vert | Objet | 1 |
+| `minecraft:item_frame` | Cadre | Objet | 1 |
+| `minecraft:light_blue_bundle` | Sac bleu clair | Objet | 1 |
+| `minecraft:light_gray_bundle` | Sac gris clair | Objet | 1 |
+| `minecraft:lime_bundle` | Sac vert clair | Objet | 1 |
+| `minecraft:magenta_bundle` | Sac magenta | Objet | 1 |
+| `minecraft:name_tag` | Étiquette | Objet | 1 |
+| `minecraft:orange_bundle` | Sac orange | Objet | 1 |
+| `minecraft:painting` | Tableau | Objet | 1 |
+| `minecraft:paper` | Papier | Objet | 1 |
+| `minecraft:pink_bundle` | Sac rose | Objet | 1 |
+| `minecraft:purple_bundle` | Sac violet | Objet | 1 |
+| `minecraft:red_bundle` | Sac rouge | Objet | 1 |
+| `minecraft:white_bundle` | Sac blanc | Objet | 1 |
+| `minecraft:writable_book` | Livre et plume | Objet | 1 |
+| `minecraft:written_book` | Livre écrit | Objet | 1 |
+| `minecraft:yellow_bundle` | Sac jaune | Objet | 1 |
+
+## C32
+
+**Enchantement et entretien · Enchanting and Repair** — [déclencheurs et règles](collections-minecraft.md#c32).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:enchanting_table` | `minecraft:crafting_shaped` | Table d'enchantement (`minecraft:enchanting_table`) |
+| `minecraft:grindstone` | `minecraft:crafting_shaped` | Meule (`minecraft:grindstone`) |
+| `minecraft:repair_item` | `minecraft:crafting_special_repairitem` | Dynamique : résultat issu de l’entrée ou de l’opération |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:enchanted_book` | Livre enchanté | Objet | 0 |
+| `minecraft:enchanting_table` | Table d'enchantement | Bloc et objet | 1 |
+| `minecraft:experience_bottle` | Fiole d'expérience | Objet | 0 |
+| `minecraft:grindstone` | Meule | Bloc et objet | 1 |
+
+## C33
+
+**Cultures et compost · Farming** — [déclencheurs et règles](collections-minecraft.md#c33).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:bone_block` | `minecraft:crafting_shaped` | Bloc d'os (`minecraft:bone_block`) |
+| `minecraft:bone_meal` | `minecraft:crafting_shapeless` | Poudre d'os (`minecraft:bone_meal`) × 3 |
+| `minecraft:bone_meal_from_bone_block` | `minecraft:crafting_shapeless` | Poudre d'os (`minecraft:bone_meal`) × 9 |
+| `minecraft:composter` | `minecraft:crafting_shaped` | Composteur (`minecraft:composter`) |
+| `minecraft:hay_block` | `minecraft:crafting_shapeless` | Botte de paille (`minecraft:hay_block`) |
+| `minecraft:melon` | `minecraft:crafting_shapeless` | Pastèque (`minecraft:melon`) |
+| `minecraft:melon_seeds` | `minecraft:crafting_shapeless` | Graines de pastèque (`minecraft:melon_seeds`) |
+| `minecraft:pumpkin_seeds` | `minecraft:crafting_shapeless` | Graines de citrouille (`minecraft:pumpkin_seeds`) × 4 |
+| `minecraft:wheat` | `minecraft:crafting_shapeless` | Blé (`minecraft:wheat`) × 9 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:attached_melon_stem` | Tige de pastèque attachée | Bloc sans objet homonyme | 0 |
+| `minecraft:attached_pumpkin_stem` | Tige de citrouille attachée | Bloc sans objet homonyme | 0 |
+| `minecraft:beetroot_seeds` | Graines de betterave | Objet | 0 |
+| `minecraft:beetroots` | Betteraves | Bloc sans objet homonyme | 0 |
+| `minecraft:bone` | Os | Objet | 0 |
+| `minecraft:bone_block` | Bloc d'os | Bloc et objet | 1 |
+| `minecraft:bone_meal` | Poudre d'os | Objet | 2 |
+| `minecraft:carrots` | Carottes | Bloc sans objet homonyme | 0 |
+| `minecraft:carved_pumpkin` | Citrouille sculptée | Bloc et objet | 0 |
+| `minecraft:cocoa` | Cacao | Bloc sans objet homonyme | 0 |
+| `minecraft:composter` | Composteur | Bloc et objet | 1 |
+| `minecraft:hay_block` | Botte de paille | Bloc et objet | 1 |
+| `minecraft:melon` | Pastèque | Bloc et objet | 1 |
+| `minecraft:melon_seeds` | Graines de pastèque | Objet | 1 |
+| `minecraft:melon_slice` | Tranche de pastèque | Objet | 0 |
+| `minecraft:melon_stem` | Tige de pastèque | Bloc sans objet homonyme | 0 |
+| `minecraft:potatoes` | Pommes de terre | Bloc sans objet homonyme | 0 |
+| `minecraft:pumpkin` | Citrouille | Bloc et objet | 0 |
+| `minecraft:pumpkin_seeds` | Graines de citrouille | Objet | 1 |
+| `minecraft:pumpkin_stem` | Tige de citrouille | Bloc sans objet homonyme | 0 |
+| `minecraft:wheat` | Blé | Bloc et objet | 1 |
+| `minecraft:wheat_seeds` | Graines de blé | Objet | 0 |
+
+## C34
+
+**Cuisine · Cooking** — [déclencheurs et règles](collections-minecraft.md#c34).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:baked_potato` | `minecraft:smelting` | Pomme de terre cuite (`minecraft:baked_potato`) |
+| `minecraft:baked_potato_from_campfire_cooking` | `minecraft:campfire_cooking` | Pomme de terre cuite (`minecraft:baked_potato`) |
+| `minecraft:baked_potato_from_smoking` | `minecraft:smoking` | Pomme de terre cuite (`minecraft:baked_potato`) |
+| `minecraft:beetroot_soup` | `minecraft:crafting_shapeless` | Soupe de betteraves (`minecraft:beetroot_soup`) |
+| `minecraft:bread` | `minecraft:crafting_shaped` | Pain (`minecraft:bread`) |
+| `minecraft:cake` | `minecraft:crafting_shaped` | Gâteau (`minecraft:cake`) |
+| `minecraft:cooked_beef` | `minecraft:smelting` | Steak (`minecraft:cooked_beef`) |
+| `minecraft:cooked_beef_from_campfire_cooking` | `minecraft:campfire_cooking` | Steak (`minecraft:cooked_beef`) |
+| `minecraft:cooked_beef_from_smoking` | `minecraft:smoking` | Steak (`minecraft:cooked_beef`) |
+| `minecraft:cooked_chicken` | `minecraft:smelting` | Poulet rôti (`minecraft:cooked_chicken`) |
+| `minecraft:cooked_chicken_from_campfire_cooking` | `minecraft:campfire_cooking` | Poulet rôti (`minecraft:cooked_chicken`) |
+| `minecraft:cooked_chicken_from_smoking` | `minecraft:smoking` | Poulet rôti (`minecraft:cooked_chicken`) |
+| `minecraft:cooked_cod` | `minecraft:smelting` | Morue cuite (`minecraft:cooked_cod`) |
+| `minecraft:cooked_cod_from_campfire_cooking` | `minecraft:campfire_cooking` | Morue cuite (`minecraft:cooked_cod`) |
+| `minecraft:cooked_cod_from_smoking` | `minecraft:smoking` | Morue cuite (`minecraft:cooked_cod`) |
+| `minecraft:cooked_mutton` | `minecraft:smelting` | Mouton cuit (`minecraft:cooked_mutton`) |
+| `minecraft:cooked_mutton_from_campfire_cooking` | `minecraft:campfire_cooking` | Mouton cuit (`minecraft:cooked_mutton`) |
+| `minecraft:cooked_mutton_from_smoking` | `minecraft:smoking` | Mouton cuit (`minecraft:cooked_mutton`) |
+| `minecraft:cooked_porkchop` | `minecraft:smelting` | Côtelette de porc cuite (`minecraft:cooked_porkchop`) |
+| `minecraft:cooked_porkchop_from_campfire_cooking` | `minecraft:campfire_cooking` | Côtelette de porc cuite (`minecraft:cooked_porkchop`) |
+| `minecraft:cooked_porkchop_from_smoking` | `minecraft:smoking` | Côtelette de porc cuite (`minecraft:cooked_porkchop`) |
+| `minecraft:cooked_rabbit` | `minecraft:smelting` | Lapin cuit (`minecraft:cooked_rabbit`) |
+| `minecraft:cooked_rabbit_from_campfire_cooking` | `minecraft:campfire_cooking` | Lapin cuit (`minecraft:cooked_rabbit`) |
+| `minecraft:cooked_rabbit_from_smoking` | `minecraft:smoking` | Lapin cuit (`minecraft:cooked_rabbit`) |
+| `minecraft:cooked_salmon` | `minecraft:smelting` | Saumon cuit (`minecraft:cooked_salmon`) |
+| `minecraft:cooked_salmon_from_campfire_cooking` | `minecraft:campfire_cooking` | Saumon cuit (`minecraft:cooked_salmon`) |
+| `minecraft:cooked_salmon_from_smoking` | `minecraft:smoking` | Saumon cuit (`minecraft:cooked_salmon`) |
+| `minecraft:cookie` | `minecraft:crafting_shaped` | Cookie (`minecraft:cookie`) × 8 |
+| `minecraft:dried_kelp` | `minecraft:crafting_shapeless` | Algue séchée (`minecraft:dried_kelp`) × 9 |
+| `minecraft:dried_kelp_block` | `minecraft:crafting_shaped` | Bloc d'algues séchées (`minecraft:dried_kelp_block`) |
+| `minecraft:dried_kelp_from_campfire_cooking` | `minecraft:campfire_cooking` | Algue séchée (`minecraft:dried_kelp`) |
+| `minecraft:dried_kelp_from_smelting` | `minecraft:smelting` | Algue séchée (`minecraft:dried_kelp`) |
+| `minecraft:dried_kelp_from_smoking` | `minecraft:smoking` | Algue séchée (`minecraft:dried_kelp`) |
+| `minecraft:golden_apple` | `minecraft:crafting_shaped` | Pomme dorée (`minecraft:golden_apple`) |
+| `minecraft:golden_carrot` | `minecraft:crafting_shaped` | Carotte dorée (`minecraft:golden_carrot`) |
+| `minecraft:mushroom_stew` | `minecraft:crafting_shapeless` | Soupe de champignons (`minecraft:mushroom_stew`) |
+| `minecraft:pumpkin_pie` | `minecraft:crafting_shapeless` | Tarte à la citrouille (`minecraft:pumpkin_pie`) |
+| `minecraft:rabbit_stew_from_brown_mushroom` | `minecraft:crafting_shapeless` | Ragoût de lapin (`minecraft:rabbit_stew`) |
+| `minecraft:rabbit_stew_from_red_mushroom` | `minecraft:crafting_shapeless` | Ragoût de lapin (`minecraft:rabbit_stew`) |
+| `minecraft:rabbit_stew_from_shelf_mushroom` | `minecraft:crafting_shapeless` | Ragoût de lapin (`minecraft:rabbit_stew`) |
+| `minecraft:smoker` | `minecraft:crafting_shaped` | Fumoir (`minecraft:smoker`) |
+| `minecraft:sugar_from_sugar_cane` | `minecraft:crafting_shapeless` | Sucre (`minecraft:sugar`) |
+| `minecraft:suspicious_stew_from_allium` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":60,"id":"minecraft:fire_resistance"}]}` |
+| `minecraft:suspicious_stew_from_azure_bluet` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":220,"id":"minecraft:blindness"}]}` |
+| `minecraft:suspicious_stew_from_blue_orchid` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":7,"id":"minecraft:saturation"}]}` |
+| `minecraft:suspicious_stew_from_closed_eyeblossom` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:nausea"}]}` |
+| `minecraft:suspicious_stew_from_cornflower` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":100,"id":"minecraft:jump_boost"}]}` |
+| `minecraft:suspicious_stew_from_dandelion` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":7,"id":"minecraft:saturation"}]}` |
+| `minecraft:suspicious_stew_from_golden_dandelion` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":7,"id":"minecraft:saturation"}]}` |
+| `minecraft:suspicious_stew_from_lily_of_the_valley` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":220,"id":"minecraft:poison"}]}` |
+| `minecraft:suspicious_stew_from_open_eyeblossom` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":220,"id":"minecraft:blindness"}]}` |
+| `minecraft:suspicious_stew_from_orange_tulip` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:weakness"}]}` |
+| `minecraft:suspicious_stew_from_oxeye_daisy` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:regeneration"}]}` |
+| `minecraft:suspicious_stew_from_pink_tulip` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:weakness"}]}` |
+| `minecraft:suspicious_stew_from_poppy` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":100,"id":"minecraft:night_vision"}]}` |
+| `minecraft:suspicious_stew_from_red_tulip` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:weakness"}]}` |
+| `minecraft:suspicious_stew_from_torchflower` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":100,"id":"minecraft:night_vision"}]}` |
+| `minecraft:suspicious_stew_from_white_tulip` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:weakness"}]}` |
+| `minecraft:suspicious_stew_from_wither_rose` | `minecraft:crafting_shapeless` | Soupe suspecte (`minecraft:suspicious_stew`) ; composants : `{"minecraft:suspicious_stew_effects":[{"duration":140,"id":"minecraft:wither"}]}` |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:apple` | Pomme | Objet | 0 |
+| `minecraft:baked_potato` | Pomme de terre cuite | Objet | 3 |
+| `minecraft:beef` | Boeuf cru | Objet | 0 |
+| `minecraft:beetroot` | Betterave | Objet | 0 |
+| `minecraft:beetroot_soup` | Soupe de betteraves | Objet | 1 |
+| `minecraft:blue_egg` | Oeuf bleu | Objet | 0 |
+| `minecraft:bread` | Pain | Objet | 1 |
+| `minecraft:brown_egg` | Oeuf brun | Objet | 0 |
+| `minecraft:cake` | Gâteau | Bloc et objet | 1 |
+| `minecraft:carrot` | Carotte | Objet | 0 |
+| `minecraft:chicken` | Poulet cru | Objet | 0 |
+| `minecraft:cocoa_beans` | Fèves de cacao | Objet | 0 |
+| `minecraft:cooked_beef` | Steak | Objet | 3 |
+| `minecraft:cooked_chicken` | Poulet rôti | Objet | 3 |
+| `minecraft:cooked_cod` | Morue cuite | Objet | 3 |
+| `minecraft:cooked_mutton` | Mouton cuit | Objet | 3 |
+| `minecraft:cooked_porkchop` | Côtelette de porc cuite | Objet | 3 |
+| `minecraft:cooked_rabbit` | Lapin cuit | Objet | 3 |
+| `minecraft:cooked_salmon` | Saumon cuit | Objet | 3 |
+| `minecraft:cookie` | Cookie | Objet | 1 |
+| `minecraft:dried_kelp` | Algue séchée | Objet | 4 |
+| `minecraft:dried_kelp_block` | Bloc d'algues séchées | Bloc et objet | 1 |
+| `minecraft:egg` | Oeuf | Objet | 0 |
+| `minecraft:enchanted_golden_apple` | Pomme dorée enchantée | Objet | 0 |
+| `minecraft:glow_berries` | Baies lumineuses | Objet | 0 |
+| `minecraft:golden_apple` | Pomme dorée | Objet | 1 |
+| `minecraft:golden_carrot` | Carotte dorée | Objet | 1 |
+| `minecraft:milk_bucket` | Seau de lait | Objet | 0 |
+| `minecraft:mushroom_stew` | Soupe de champignons | Objet | 1 |
+| `minecraft:mutton` | Mouton cru | Objet | 0 |
+| `minecraft:poisonous_potato` | Pomme de terre empoisonnée | Objet | 0 |
+| `minecraft:porkchop` | Côtelette de porc crue | Objet | 0 |
+| `minecraft:potato` | Pomme de terre | Objet | 0 |
+| `minecraft:pumpkin_pie` | Tarte à la citrouille | Objet | 1 |
+| `minecraft:rabbit` | Lapin cru | Objet | 0 |
+| `minecraft:rabbit_stew` | Ragoût de lapin | Objet | 3 |
+| `minecraft:rotten_flesh` | Chair putréfiée | Objet | 0 |
+| `minecraft:smoker` | Fumoir | Bloc et objet | 1 |
+| `minecraft:sugar` | Sucre | Objet | 2 |
+| `minecraft:sugar_cane` | Canne à sucre | Bloc et objet | 0 |
+| `minecraft:suspicious_stew` | Soupe suspecte | Objet | 17 |
+| `minecraft:sweet_berries` | Baies sucrées | Objet | 0 |
+
+## C35
+
+**Élevage, cuir et montures · Husbandry and Tack** — [déclencheurs et règles](collections-minecraft.md#c35).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:armor_stand` | `minecraft:crafting_shaped` | Porte-armure (`minecraft:armor_stand`) |
+| `minecraft:carrot_on_a_stick` | `minecraft:crafting_shaped` | Carotte sur un bâton (`minecraft:carrot_on_a_stick`) |
+| `minecraft:lead` | `minecraft:crafting_shaped` | Laisse (`minecraft:lead`) × 2 |
+| `minecraft:leather` | `minecraft:crafting_shaped` | Cuir (`minecraft:leather`) |
+| `minecraft:leather_boots` | `minecraft:crafting_shaped` | Bottes en cuir (`minecraft:leather_boots`) |
+| `minecraft:leather_boots_dyed` | `minecraft:crafting_dye` | Bottes en cuir (`minecraft:leather_boots`) |
+| `minecraft:leather_chestplate` | `minecraft:crafting_shaped` | Tunique en cuir (`minecraft:leather_chestplate`) |
+| `minecraft:leather_chestplate_dyed` | `minecraft:crafting_dye` | Tunique en cuir (`minecraft:leather_chestplate`) |
+| `minecraft:leather_helmet` | `minecraft:crafting_shaped` | Chapeau en cuir (`minecraft:leather_helmet`) |
+| `minecraft:leather_helmet_dyed` | `minecraft:crafting_dye` | Chapeau en cuir (`minecraft:leather_helmet`) |
+| `minecraft:leather_horse_armor` | `minecraft:crafting_shaped` | Armure en cuir pour cheval (`minecraft:leather_horse_armor`) |
+| `minecraft:leather_horse_armor_dyed` | `minecraft:crafting_dye` | Armure en cuir pour cheval (`minecraft:leather_horse_armor`) |
+| `minecraft:leather_leggings` | `minecraft:crafting_shaped` | Pantalon en cuir (`minecraft:leather_leggings`) |
+| `minecraft:leather_leggings_dyed` | `minecraft:crafting_dye` | Pantalon en cuir (`minecraft:leather_leggings`) |
+| `minecraft:saddle` | `minecraft:crafting_shaped` | Selle (`minecraft:saddle`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:armor_stand` | Porte-armure | Objet | 1 |
+| `minecraft:carrot_on_a_stick` | Carotte sur un bâton | Objet | 1 |
+| `minecraft:copper_horse_armor` | Armure en cuivre pour cheval | Objet | 0 |
+| `minecraft:diamond_horse_armor` | Armure en diamant pour cheval | Objet | 0 |
+| `minecraft:golden_horse_armor` | Armure en or pour cheval | Objet | 0 |
+| `minecraft:iron_horse_armor` | Armure en fer pour cheval | Objet | 0 |
+| `minecraft:lead` | Laisse | Objet | 1 |
+| `minecraft:leather` | Cuir | Objet | 1 |
+| `minecraft:leather_boots` | Bottes en cuir | Objet | 2 |
+| `minecraft:leather_chestplate` | Tunique en cuir | Objet | 2 |
+| `minecraft:leather_helmet` | Chapeau en cuir | Objet | 2 |
+| `minecraft:leather_horse_armor` | Armure en cuir pour cheval | Objet | 2 |
+| `minecraft:leather_leggings` | Pantalon en cuir | Objet | 2 |
+| `minecraft:rabbit_hide` | Peau de lapin | Objet | 0 |
+| `minecraft:saddle` | Selle | Objet | 1 |
+
+## C36
+
+**Animaux apprivoisés · Companion Care** — [déclencheurs et règles](collections-minecraft.md#c36).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:wolf_armor` | `minecraft:crafting_shaped` | Armure pour loup (`minecraft:wolf_armor`) |
+| `minecraft:wolf_armor_dyed` | `minecraft:crafting_dye` | Armure pour loup (`minecraft:wolf_armor`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:armadillo_scute` | Écaille de tatou | Objet | 0 |
+| `minecraft:wolf_armor` | Armure pour loup | Objet | 2 |
+
+## C37
+
+**Abeilles et miel · Beekeeping** — [déclencheurs et règles](collections-minecraft.md#c37).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:beehive` | `minecraft:crafting_shaped` | Ruche (`minecraft:beehive`) |
+| `minecraft:honey_block` | `minecraft:crafting_shaped` | Bloc de miel (`minecraft:honey_block`) |
+| `minecraft:honey_bottle` | `minecraft:crafting_shapeless` | Fiole de miel (`minecraft:honey_bottle`) × 4 |
+| `minecraft:honeycomb_block` | `minecraft:crafting_shaped` | Bloc de rayon de miel (`minecraft:honeycomb_block`) |
+| `minecraft:sugar_from_honey_bottle` | `minecraft:crafting_shapeless` | Sucre (`minecraft:sugar`) × 3 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:bee_nest` | Nid d'abeilles | Bloc et objet | 0 |
+| `minecraft:beehive` | Ruche | Bloc et objet | 1 |
+| `minecraft:honey_block` | Bloc de miel | Bloc et objet | 1 |
+| `minecraft:honey_bottle` | Fiole de miel | Objet | 1 |
+| `minecraft:honeycomb` | Rayon de miel | Objet | 0 |
+| `minecraft:honeycomb_block` | Bloc de rayon de miel | Bloc et objet | 1 |
+
+## C38
+
+**Pêche et seaux vivants · Fishing** — [déclencheurs et règles](collections-minecraft.md#c38).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:fishing_rod` | `minecraft:crafting_shaped` | Canne à pêche (`minecraft:fishing_rod`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:axolotl_bucket` | Axolotl dans un seau | Objet | 0 |
+| `minecraft:cod` | Morue crue | Objet | 0 |
+| `minecraft:cod_bucket` | Morue dans un seau | Objet | 0 |
+| `minecraft:fishing_rod` | Canne à pêche | Objet | 1 |
+| `minecraft:pufferfish` | Poisson-globe | Objet | 0 |
+| `minecraft:pufferfish_bucket` | Poisson-globe dans un seau | Objet | 0 |
+| `minecraft:salmon` | Saumon cru | Objet | 0 |
+| `minecraft:salmon_bucket` | Saumon dans un seau | Objet | 0 |
+| `minecraft:tadpole_bucket` | Têtard dans un seau | Objet | 0 |
+| `minecraft:tropical_fish` | Poisson tropical | Objet | 0 |
+| `minecraft:tropical_fish_bucket` | Poisson tropical dans un seau | Objet | 0 |
+
+## C39
+
+**Océan et monuments · Ocean Monuments** — [déclencheurs et règles](collections-minecraft.md#c39).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:conduit` | `minecraft:crafting_shaped` | Conduit (`minecraft:conduit`) |
+| `minecraft:dark_prismarine` | `minecraft:crafting_shaped` | Prismarine sombre (`minecraft:dark_prismarine`) |
+| `minecraft:dark_prismarine_slab` | `minecraft:crafting_shaped` | Dalle de prismarine sombre (`minecraft:dark_prismarine_slab`) × 6 |
+| `minecraft:dark_prismarine_slab_from_dark_prismarine_stonecutting` | `minecraft:stonecutting` | Dalle de prismarine sombre (`minecraft:dark_prismarine_slab`) × 2 |
+| `minecraft:dark_prismarine_stairs` | `minecraft:crafting_shaped` | Escalier en prismarine sombre (`minecraft:dark_prismarine_stairs`) × 4 |
+| `minecraft:dark_prismarine_stairs_from_dark_prismarine_stonecutting` | `minecraft:stonecutting` | Escalier en prismarine sombre (`minecraft:dark_prismarine_stairs`) |
+| `minecraft:prismarine` | `minecraft:crafting_shaped` | Prismarine (`minecraft:prismarine`) |
+| `minecraft:prismarine_brick_slab` | `minecraft:crafting_shaped` | Dalle de briques de prismarine (`minecraft:prismarine_brick_slab`) × 6 |
+| `minecraft:prismarine_brick_slab_from_prismarine_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de briques de prismarine (`minecraft:prismarine_brick_slab`) × 2 |
+| `minecraft:prismarine_brick_stairs` | `minecraft:crafting_shaped` | Escalier en briques de prismarine (`minecraft:prismarine_brick_stairs`) × 4 |
+| `minecraft:prismarine_brick_stairs_from_prismarine_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en briques de prismarine (`minecraft:prismarine_brick_stairs`) |
+| `minecraft:prismarine_bricks` | `minecraft:crafting_shapeless` | Briques de prismarine (`minecraft:prismarine_bricks`) |
+| `minecraft:prismarine_slab` | `minecraft:crafting_shaped` | Dalle de prismarine (`minecraft:prismarine_slab`) × 6 |
+| `minecraft:prismarine_slab_from_prismarine_stonecutting` | `minecraft:stonecutting` | Dalle de prismarine (`minecraft:prismarine_slab`) × 2 |
+| `minecraft:prismarine_stairs` | `minecraft:crafting_shaped` | Escalier en prismarine (`minecraft:prismarine_stairs`) × 4 |
+| `minecraft:prismarine_stairs_from_prismarine_stonecutting` | `minecraft:stonecutting` | Escalier en prismarine (`minecraft:prismarine_stairs`) |
+| `minecraft:prismarine_wall` | `minecraft:crafting_shaped` | Muret de prismarine (`minecraft:prismarine_wall`) × 6 |
+| `minecraft:prismarine_wall_from_prismarine_stonecutting` | `minecraft:stonecutting` | Muret de prismarine (`minecraft:prismarine_wall`) |
+| `minecraft:sea_lantern` | `minecraft:crafting_shaped` | Lanterne aquatique (`minecraft:sea_lantern`) |
+| `minecraft:sponge` | `minecraft:smelting` | Éponge (`minecraft:sponge`) |
+| `minecraft:turtle_helmet` | `minecraft:crafting_shaped` | Carapace de tortue (`minecraft:turtle_helmet`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:conduit` | Conduit | Bloc et objet | 1 |
+| `minecraft:copper_nautilus_armor` | Armure en cuivre pour nautile | Objet | 0 |
+| `minecraft:dark_prismarine` | Prismarine sombre | Bloc et objet | 1 |
+| `minecraft:dark_prismarine_slab` | Dalle de prismarine sombre | Bloc et objet | 2 |
+| `minecraft:dark_prismarine_stairs` | Escalier en prismarine sombre | Bloc et objet | 2 |
+| `minecraft:diamond_nautilus_armor` | Armure en diamant pour nautile | Objet | 0 |
+| `minecraft:golden_nautilus_armor` | Armure en or pour nautile | Objet | 0 |
+| `minecraft:heart_of_the_sea` | Coeur de la mer | Objet | 0 |
+| `minecraft:iron_nautilus_armor` | Armure en fer pour nautile | Objet | 0 |
+| `minecraft:nautilus_shell` | Coquille de nautile | Objet | 0 |
+| `minecraft:prismarine` | Prismarine | Bloc et objet | 1 |
+| `minecraft:prismarine_brick_slab` | Dalle de briques de prismarine | Bloc et objet | 2 |
+| `minecraft:prismarine_brick_stairs` | Escalier en briques de prismarine | Bloc et objet | 2 |
+| `minecraft:prismarine_bricks` | Briques de prismarine | Bloc et objet | 1 |
+| `minecraft:prismarine_crystals` | Cristaux de prismarine | Objet | 0 |
+| `minecraft:prismarine_shard` | Éclat de prismarine | Objet | 0 |
+| `minecraft:prismarine_slab` | Dalle de prismarine | Bloc et objet | 2 |
+| `minecraft:prismarine_stairs` | Escalier en prismarine | Bloc et objet | 2 |
+| `minecraft:prismarine_wall` | Muret de prismarine | Bloc et objet | 2 |
+| `minecraft:sea_lantern` | Lanterne aquatique | Bloc et objet | 1 |
+| `minecraft:sea_pickle` | Cornichon de mer | Bloc et objet | 0 |
+| `minecraft:sponge` | Éponge | Bloc et objet | 1 |
+| `minecraft:trident` | Trident | Objet | 0 |
+| `minecraft:turtle_egg` | Oeuf de tortue | Bloc et objet | 0 |
+| `minecraft:turtle_helmet` | Carapace de tortue | Objet | 1 |
+| `minecraft:turtle_scute` | Écaille de tortue | Objet | 0 |
+| `minecraft:wet_sponge` | Éponge mouillée | Bloc et objet | 0 |
+
+## C40
+
+**Coraux et récifs · Coral Reefs** — [déclencheurs et règles](collections-minecraft.md#c40).
+
+### Recettes
+
+Aucune recette JSON primaire. Cette collection présente des fiches ou des renvois ; elle ne crée pas de recette.
+
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:brain_coral` | Corail cerveau | Bloc et objet | 0 |
+| `minecraft:brain_coral_block` | Bloc de corail cerveau | Bloc et objet | 0 |
+| `minecraft:brain_coral_fan` | Gorgone de corail cerveau | Bloc et objet | 0 |
+| `minecraft:brain_coral_wall_fan` | Gorgone de corail cerveau murale | Bloc sans objet homonyme | 0 |
+| `minecraft:bubble_coral` | Corail bulles | Bloc et objet | 0 |
+| `minecraft:bubble_coral_block` | Bloc de corail bulles | Bloc et objet | 0 |
+| `minecraft:bubble_coral_fan` | Gorgone de corail bulles | Bloc et objet | 0 |
+| `minecraft:bubble_coral_wall_fan` | Gorgone de corail bulles murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dead_brain_coral` | Corail cerveau mort | Bloc et objet | 0 |
+| `minecraft:dead_brain_coral_block` | Bloc de corail cerveau mort | Bloc et objet | 0 |
+| `minecraft:dead_brain_coral_fan` | Gorgone de corail cerveau morte | Bloc et objet | 0 |
+| `minecraft:dead_brain_coral_wall_fan` | Gorgone de corail cerveau morte murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dead_bubble_coral` | Corail bulles mort | Bloc et objet | 0 |
+| `minecraft:dead_bubble_coral_block` | Bloc de corail bulles mort | Bloc et objet | 0 |
+| `minecraft:dead_bubble_coral_fan` | Gorgone de corail bulles morte | Bloc et objet | 0 |
+| `minecraft:dead_bubble_coral_wall_fan` | Gorgone de corail bulles morte murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dead_fire_coral` | Corail de feu mort | Bloc et objet | 0 |
+| `minecraft:dead_fire_coral_block` | Bloc de corail de feu mort | Bloc et objet | 0 |
+| `minecraft:dead_fire_coral_fan` | Gorgone de corail de feu morte | Bloc et objet | 0 |
+| `minecraft:dead_fire_coral_wall_fan` | Gorgone de corail de feu morte murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dead_horn_coral` | Corail corné mort | Bloc et objet | 0 |
+| `minecraft:dead_horn_coral_block` | Bloc de corail corné mort | Bloc et objet | 0 |
+| `minecraft:dead_horn_coral_fan` | Gorgone de corail corné morte | Bloc et objet | 0 |
+| `minecraft:dead_horn_coral_wall_fan` | Gorgone de corail corné morte murale | Bloc sans objet homonyme | 0 |
+| `minecraft:dead_tube_coral` | Corail tubulaire mort | Bloc et objet | 0 |
+| `minecraft:dead_tube_coral_block` | Bloc de corail tubulaire mort | Bloc et objet | 0 |
+| `minecraft:dead_tube_coral_fan` | Gorgone de corail tubulaire morte | Bloc et objet | 0 |
+| `minecraft:dead_tube_coral_wall_fan` | Gorgone de corail tubulaire morte murale | Bloc sans objet homonyme | 0 |
+| `minecraft:fire_coral` | Corail de feu | Bloc et objet | 0 |
+| `minecraft:fire_coral_block` | Bloc de corail de feu | Bloc et objet | 0 |
+| `minecraft:fire_coral_fan` | Gorgone de corail de feu | Bloc et objet | 0 |
+| `minecraft:fire_coral_wall_fan` | Gorgone de corail de feu murale | Bloc sans objet homonyme | 0 |
+| `minecraft:horn_coral` | Corail corné | Bloc et objet | 0 |
+| `minecraft:horn_coral_block` | Bloc de corail corné | Bloc et objet | 0 |
+| `minecraft:horn_coral_fan` | Gorgone de corail corné | Bloc et objet | 0 |
+| `minecraft:horn_coral_wall_fan` | Gorgone de corail corné murale | Bloc sans objet homonyme | 0 |
+| `minecraft:tube_coral` | Corail tubulaire | Bloc et objet | 0 |
+| `minecraft:tube_coral_block` | Bloc de corail tubulaire | Bloc et objet | 0 |
+| `minecraft:tube_coral_fan` | Gorgone de corail tubulaire | Bloc et objet | 0 |
+| `minecraft:tube_coral_wall_fan` | Gorgone de corail tubulaire murale | Bloc sans objet homonyme | 0 |
+
+## C41
+
+**Botanique et renifleurs · Botany and Sniffers** — [déclencheurs et règles](collections-minecraft.md#c41).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:golden_dandelion` | `minecraft:crafting_shaped` | Pissenlit doré (`minecraft:golden_dandelion`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:acacia_leaves` | Feuilles d'acacia | Bloc et objet | 0 |
+| `minecraft:acacia_sapling` | Pousse d'acacia | Bloc et objet | 0 |
+| `minecraft:allium` | Allium | Bloc et objet | 0 |
+| `minecraft:azalea` | Azalée | Bloc et objet | 0 |
+| `minecraft:azalea_leaves` | Feuilles d'azalée | Bloc et objet | 0 |
+| `minecraft:azure_bluet` | Houstonie bleue | Bloc et objet | 0 |
+| `minecraft:bamboo_sapling` | Pousse de bambou | Bloc sans objet homonyme | 0 |
+| `minecraft:big_dripleaf` | Grande foliogoutte | Bloc et objet | 0 |
+| `minecraft:big_dripleaf_stem` | Tige de grande foliogoutte | Bloc sans objet homonyme | 0 |
+| `minecraft:birch_leaves` | Feuilles de bouleau | Bloc et objet | 0 |
+| `minecraft:birch_sapling` | Pousse de bouleau | Bloc et objet | 0 |
+| `minecraft:blue_orchid` | Orchidée bleue | Bloc et objet | 0 |
+| `minecraft:bush` | Buisson | Bloc et objet | 0 |
+| `minecraft:cactus` | Cactus | Bloc et objet | 0 |
+| `minecraft:cactus_flower` | Fleur de cactus | Bloc et objet | 0 |
+| `minecraft:cave_vines` | Lianes des cavernes | Bloc sans objet homonyme | 0 |
+| `minecraft:cave_vines_plant` | Plante de lianes des cavernes | Bloc sans objet homonyme | 0 |
+| `minecraft:cherry_leaves` | Feuilles de cerisier | Bloc et objet | 0 |
+| `minecraft:cherry_sapling` | Pousse de cerisier | Bloc et objet | 0 |
+| `minecraft:closed_eyeblossom` | Oeilchidée fermée | Bloc et objet | 0 |
+| `minecraft:cornflower` | Bleuet | Bloc et objet | 0 |
+| `minecraft:dandelion` | Pissenlit | Bloc et objet | 0 |
+| `minecraft:dark_oak_leaves` | Feuilles de chêne noir | Bloc et objet | 0 |
+| `minecraft:dark_oak_sapling` | Pousse de chêne noir | Bloc et objet | 0 |
+| `minecraft:dead_bush` | Buisson mort | Bloc et objet | 0 |
+| `minecraft:fern` | Fougère | Bloc et objet | 0 |
+| `minecraft:firefly_bush` | Buisson à lucioles | Bloc et objet | 0 |
+| `minecraft:flowering_azalea` | Azalée fleurie | Bloc et objet | 0 |
+| `minecraft:flowering_azalea_leaves` | Feuilles d'azalée fleurie | Bloc et objet | 0 |
+| `minecraft:glow_lichen` | Lichen lumineux | Bloc et objet | 0 |
+| `minecraft:golden_dandelion` | Pissenlit doré | Bloc et objet | 1 |
+| `minecraft:jungle_leaves` | Feuilles d'acajou | Bloc et objet | 0 |
+| `minecraft:jungle_sapling` | Pousse d'acajou | Bloc et objet | 0 |
+| `minecraft:kelp` | Algue | Bloc et objet | 0 |
+| `minecraft:kelp_plant` | Tige d'algue | Bloc sans objet homonyme | 0 |
+| `minecraft:large_fern` | Grande fougère | Bloc et objet | 0 |
+| `minecraft:lilac` | Lilas | Bloc et objet | 0 |
+| `minecraft:lily_of_the_valley` | Muguet | Bloc et objet | 0 |
+| `minecraft:lily_pad` | Nénuphar | Bloc et objet | 0 |
+| `minecraft:mangrove_leaves` | Feuilles de palétuvier | Bloc et objet | 0 |
+| `minecraft:mangrove_propagule` | Propagule de palétuvier | Bloc et objet | 0 |
+| `minecraft:mangrove_roots` | Racines de palétuvier | Bloc et objet | 0 |
+| `minecraft:oak_leaves` | Feuilles de chêne | Bloc et objet | 0 |
+| `minecraft:oak_sapling` | Pousse de chêne | Bloc et objet | 0 |
+| `minecraft:open_eyeblossom` | Oeilchidée ouverte | Bloc et objet | 0 |
+| `minecraft:orange_poplar_leaves` | Feuilles de peuplier orange | Bloc et objet | 0 |
+| `minecraft:orange_tulip` | Tulipe orange | Bloc et objet | 0 |
+| `minecraft:oxeye_daisy` | Marguerite | Bloc et objet | 0 |
+| `minecraft:pale_oak_leaves` | Feuilles de chêne pâle | Bloc et objet | 0 |
+| `minecraft:pale_oak_sapling` | Pousse de chêne pâle | Bloc et objet | 0 |
+| `minecraft:peony` | Pivoine | Bloc et objet | 0 |
+| `minecraft:pink_petals` | Pétales roses | Bloc et objet | 0 |
+| `minecraft:pink_tulip` | Tulipe rose | Bloc et objet | 0 |
+| `minecraft:pitcher_crop` | Plant de planturne | Bloc sans objet homonyme | 0 |
+| `minecraft:pitcher_plant` | Planturne | Bloc et objet | 0 |
+| `minecraft:pitcher_pod` | Gousse de planturne | Objet | 0 |
+| `minecraft:poplar_sapling` | Pousse de peuplier | Bloc et objet | 0 |
+| `minecraft:poppy` | Coquelicot | Bloc et objet | 0 |
+| `minecraft:potted_acacia_sapling` | Pousse d'acacia en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_allium` | Allium en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_azalea_bush` | Azalée en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_azure_bluet` | Houstonie bleue en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_bamboo` | Bambou en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_birch_sapling` | Pousse de bouleau en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_blue_orchid` | Orchidée bleue en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_brown_mushroom` | Champignon brun en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_cactus` | Cactus en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_cherry_sapling` | Pousse de cerisier en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_closed_eyeblossom` | Oeilchidée fermée en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_cornflower` | Bleuet en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_crimson_fungus` | Champignon carmin en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_crimson_roots` | Racines carmin en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_dandelion` | Pissenlit en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_dark_oak_sapling` | Pousse de chêne noir en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_dead_bush` | Buisson mort en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_fern` | Fougère en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_flowering_azalea_bush` | Azalée fleurie en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_golden_dandelion` | Pissenlit doré en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_jungle_sapling` | Pousse d'acajou en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_lily_of_the_valley` | Muguet en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_mangrove_propagule` | Propagule de palétuvier en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_oak_sapling` | Pousse de chêne en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_open_eyeblossom` | Oeilchidée ouverte en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_orange_tulip` | Tulipe orange en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_oxeye_daisy` | Marguerite en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_pale_oak_sapling` | Pousse de chêne pâle en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_pink_tulip` | Tulipe rose en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_poplar_sapling` | Pousse de peuplier en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_poppy` | Coquelicot en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_red_mushroom` | Champignon rouge en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_red_tulip` | Tulipe rouge en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_spruce_sapling` | Pousse de sapin en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_torchflower` | Torche-fleur en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_warped_fungus` | Champignon biscornu en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_warped_roots` | Racines biscornues en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_white_tulip` | Tulipe blanche en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:potted_wither_rose` | Rose de Wither en pot | Bloc sans objet homonyme | 0 |
+| `minecraft:red_poplar_leaves` | Feuilles de peuplier rouges | Bloc et objet | 0 |
+| `minecraft:red_shrub` | Buisson rouge | Bloc et objet | 0 |
+| `minecraft:red_tulip` | Tulipe rouge | Bloc et objet | 0 |
+| `minecraft:rose_bush` | Rosier | Bloc et objet | 0 |
+| `minecraft:seagrass` | Herbe aquatique | Bloc et objet | 0 |
+| `minecraft:short_dry_grass` | Herbes courtes sèches | Bloc et objet | 0 |
+| `minecraft:short_grass` | Herbes courtes | Bloc et objet | 0 |
+| `minecraft:small_dripleaf` | Petite foliogoutte | Bloc et objet | 0 |
+| `minecraft:sniffer_egg` | Oeuf de renifleur | Bloc et objet | 0 |
+| `minecraft:spore_blossom` | Fleur sporifère | Bloc et objet | 0 |
+| `minecraft:spruce_leaves` | Feuilles de sapin | Bloc et objet | 0 |
+| `minecraft:spruce_sapling` | Pousse de sapin | Bloc et objet | 0 |
+| `minecraft:sunflower` | Tournesol | Bloc et objet | 0 |
+| `minecraft:sweet_berry_bush` | Buisson à baies sucrées | Bloc sans objet homonyme | 0 |
+| `minecraft:tall_dry_grass` | Hautes herbes sèches | Bloc et objet | 0 |
+| `minecraft:tall_grass` | Hautes herbes | Bloc et objet | 0 |
+| `minecraft:tall_seagrass` | Hautes herbes aquatiques | Bloc sans objet homonyme | 0 |
+| `minecraft:torchflower` | Torche-fleur | Bloc et objet | 0 |
+| `minecraft:torchflower_crop` | Plant de torche-fleur | Bloc sans objet homonyme | 0 |
+| `minecraft:torchflower_seeds` | Graines de torche-fleur | Objet | 0 |
+| `minecraft:vine` | Lianes | Bloc et objet | 0 |
+| `minecraft:white_tulip` | Tulipe blanche | Bloc et objet | 0 |
+| `minecraft:wildflowers` | Fleurs sauvages | Bloc et objet | 0 |
+| `minecraft:yellow_poplar_leaves` | Feuilles de peuplier jaunes | Bloc et objet | 0 |
+
+## C42
+
+**Champignons · Mushrooms** — [déclencheurs et règles](collections-minecraft.md#c42).
+
+### Recettes
+
+Aucune recette JSON primaire. Cette collection présente des fiches ou des renvois ; elle ne crée pas de recette.
+
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:brown_mushroom` | Champignon brun | Bloc et objet | 0 |
+| `minecraft:brown_mushroom_block` | Bloc de champignon brun | Bloc et objet | 0 |
+| `minecraft:mushroom_stem` | Tige de champignon | Bloc et objet | 0 |
+| `minecraft:red_mushroom` | Champignon rouge | Bloc et objet | 0 |
+| `minecraft:red_mushroom_block` | Bloc de champignon rouge | Bloc et objet | 0 |
+| `minecraft:shelf_mushroom` | Polypore | Bloc et objet | 0 |
+
+## C43
+
+**Archéologie et poterie · Archaeology** — [déclencheurs et règles](collections-minecraft.md#c43).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:brush` | `minecraft:crafting_shaped` | Pinceau (`minecraft:brush`) |
+| `minecraft:decorated_pot` | `minecraft:crafting_decorated_pot` | Vase décoré (`minecraft:decorated_pot`) |
+| `minecraft:decorated_pot_simple` | `minecraft:crafting_shaped` | Vase décoré (`minecraft:decorated_pot`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:angler_pottery_sherd` | Tesson de poterie de pêcheur | Objet | 0 |
+| `minecraft:archer_pottery_sherd` | Tesson de poterie d'archer | Objet | 0 |
+| `minecraft:arms_up_pottery_sherd` | Tesson de poterie de figure | Objet | 0 |
+| `minecraft:blade_pottery_sherd` | Tesson de poterie de lame | Objet | 0 |
+| `minecraft:brewer_pottery_sherd` | Tesson de poterie d'alchimiste | Objet | 0 |
+| `minecraft:brush` | Pinceau | Objet | 1 |
+| `minecraft:burn_pottery_sherd` | Tesson de poterie de feu | Objet | 0 |
+| `minecraft:danger_pottery_sherd` | Tesson de poterie de danger | Objet | 0 |
+| `minecraft:decorated_pot` | Vase décoré | Bloc et objet | 2 |
+| `minecraft:explorer_pottery_sherd` | Tesson de poterie d'explorateur | Objet | 0 |
+| `minecraft:flow_pottery_sherd` | Tesson de poterie de tourbillon | Objet | 0 |
+| `minecraft:friend_pottery_sherd` | Tesson de poterie d'ami | Objet | 0 |
+| `minecraft:guster_pottery_sherd` | Tesson de poterie de rafaleur | Objet | 0 |
+| `minecraft:heart_pottery_sherd` | Tesson de poterie de coeur | Objet | 0 |
+| `minecraft:heartbreak_pottery_sherd` | Tesson de poterie de coeur brisé | Objet | 0 |
+| `minecraft:howl_pottery_sherd` | Tesson de poterie de loup | Objet | 0 |
+| `minecraft:miner_pottery_sherd` | Tesson de poterie de mineur | Objet | 0 |
+| `minecraft:mourner_pottery_sherd` | Tesson de poterie de pleureur | Objet | 0 |
+| `minecraft:plenty_pottery_sherd` | Tesson de poterie de richesse | Objet | 0 |
+| `minecraft:prize_pottery_sherd` | Tesson de poterie de joyau | Objet | 0 |
+| `minecraft:scrape_pottery_sherd` | Tesson de poterie de hache | Objet | 0 |
+| `minecraft:sheaf_pottery_sherd` | Tesson de poterie d'épis | Objet | 0 |
+| `minecraft:shelter_pottery_sherd` | Tesson de poterie d'abri | Objet | 0 |
+| `minecraft:skull_pottery_sherd` | Tesson de poterie de crâne | Objet | 0 |
+| `minecraft:snort_pottery_sherd` | Tesson de poterie de renifleur | Objet | 0 |
+
+## C44
+
+**Ornements d’armure · Armor Trims** — [déclencheurs et règles](collections-minecraft.md#c44).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:bolt_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:bolt_armor_trim_smithing_template`) × 2 |
+| `minecraft:bolt_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:coast_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:coast_armor_trim_smithing_template`) × 2 |
+| `minecraft:coast_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:dune_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:dune_armor_trim_smithing_template`) × 2 |
+| `minecraft:dune_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:eye_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:eye_armor_trim_smithing_template`) × 2 |
+| `minecraft:eye_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:flow_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:flow_armor_trim_smithing_template`) × 2 |
+| `minecraft:flow_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:host_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:host_armor_trim_smithing_template`) × 2 |
+| `minecraft:host_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:raiser_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:raiser_armor_trim_smithing_template`) × 2 |
+| `minecraft:raiser_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:rib_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:rib_armor_trim_smithing_template`) × 2 |
+| `minecraft:rib_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:sentry_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:sentry_armor_trim_smithing_template`) × 2 |
+| `minecraft:sentry_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:shaper_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:shaper_armor_trim_smithing_template`) × 2 |
+| `minecraft:shaper_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:silence_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:silence_armor_trim_smithing_template`) × 2 |
+| `minecraft:silence_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:smithing_table` | `minecraft:crafting_shaped` | Table de forge (`minecraft:smithing_table`) |
+| `minecraft:snout_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:snout_armor_trim_smithing_template`) × 2 |
+| `minecraft:snout_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:spire_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:spire_armor_trim_smithing_template`) × 2 |
+| `minecraft:spire_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:tide_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:tide_armor_trim_smithing_template`) × 2 |
+| `minecraft:tide_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:vex_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:vex_armor_trim_smithing_template`) × 2 |
+| `minecraft:vex_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:ward_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:ward_armor_trim_smithing_template`) × 2 |
+| `minecraft:ward_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:wayfinder_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:wayfinder_armor_trim_smithing_template`) × 2 |
+| `minecraft:wayfinder_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+| `minecraft:wild_armor_trim_smithing_template` | `minecraft:crafting_shaped` | Modèle de forge (`minecraft:wild_armor_trim_smithing_template`) × 2 |
+| `minecraft:wild_armor_trim_smithing_template_smithing_trim` | `minecraft:smithing_trim` | Dynamique : résultat issu de l’entrée ou de l’opération |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:bolt_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:coast_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:dune_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:eye_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:flow_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:host_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:raiser_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:rib_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:sentry_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:shaper_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:silence_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:smithing_table` | Table de forge | Bloc et objet | 1 |
+| `minecraft:snout_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:spire_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:tide_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:vex_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:ward_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:wayfinder_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+| `minecraft:wild_armor_trim_smithing_template` | Modèle de forge | Objet | 1 |
+
+## C45
+
+**Bannières et héraldique · Banners and Heraldry** — [déclencheurs et règles](collections-minecraft.md#c45).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_banner` | `minecraft:crafting_shaped` | Bannière noire (`minecraft:black_banner`) |
+| `minecraft:black_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière noire (`minecraft:black_banner`) |
+| `minecraft:blue_banner` | `minecraft:crafting_shaped` | Bannière bleue (`minecraft:blue_banner`) |
+| `minecraft:blue_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière bleue (`minecraft:blue_banner`) |
+| `minecraft:bordure_indented_banner_pattern` | `minecraft:crafting_shapeless` | Motif de bordure cannelée pour bannière (`minecraft:bordure_indented_banner_pattern`) |
+| `minecraft:brown_banner` | `minecraft:crafting_shaped` | Bannière marron (`minecraft:brown_banner`) |
+| `minecraft:brown_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière marron (`minecraft:brown_banner`) |
+| `minecraft:creeper_banner_pattern` | `minecraft:crafting_shapeless` | Motif de bannière (`minecraft:creeper_banner_pattern`) |
+| `minecraft:cyan_banner` | `minecraft:crafting_shaped` | Bannière cyan (`minecraft:cyan_banner`) |
+| `minecraft:cyan_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière cyan (`minecraft:cyan_banner`) |
+| `minecraft:field_masoned_banner_pattern` | `minecraft:crafting_shapeless` | Motif de champ maçonné pour bannière (`minecraft:field_masoned_banner_pattern`) |
+| `minecraft:flower_banner_pattern` | `minecraft:crafting_shapeless` | Motif de bannière (`minecraft:flower_banner_pattern`) |
+| `minecraft:gray_banner` | `minecraft:crafting_shaped` | Bannière grise (`minecraft:gray_banner`) |
+| `minecraft:gray_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière grise (`minecraft:gray_banner`) |
+| `minecraft:green_banner` | `minecraft:crafting_shaped` | Bannière verte (`minecraft:green_banner`) |
+| `minecraft:green_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière verte (`minecraft:green_banner`) |
+| `minecraft:light_blue_banner` | `minecraft:crafting_shaped` | Bannière bleu clair (`minecraft:light_blue_banner`) |
+| `minecraft:light_blue_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière bleu clair (`minecraft:light_blue_banner`) |
+| `minecraft:light_gray_banner` | `minecraft:crafting_shaped` | Bannière gris clair (`minecraft:light_gray_banner`) |
+| `minecraft:light_gray_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière gris clair (`minecraft:light_gray_banner`) |
+| `minecraft:lime_banner` | `minecraft:crafting_shaped` | Bannière vert clair (`minecraft:lime_banner`) |
+| `minecraft:lime_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière vert clair (`minecraft:lime_banner`) |
+| `minecraft:loom` | `minecraft:crafting_shaped` | Métier à tisser (`minecraft:loom`) |
+| `minecraft:magenta_banner` | `minecraft:crafting_shaped` | Bannière magenta (`minecraft:magenta_banner`) |
+| `minecraft:magenta_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière magenta (`minecraft:magenta_banner`) |
+| `minecraft:mojang_banner_pattern` | `minecraft:crafting_shapeless` | Motif de bannière (`minecraft:mojang_banner_pattern`) |
+| `minecraft:orange_banner` | `minecraft:crafting_shaped` | Bannière orange (`minecraft:orange_banner`) |
+| `minecraft:orange_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière orange (`minecraft:orange_banner`) |
+| `minecraft:pink_banner` | `minecraft:crafting_shaped` | Bannière rose (`minecraft:pink_banner`) |
+| `minecraft:pink_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière rose (`minecraft:pink_banner`) |
+| `minecraft:purple_banner` | `minecraft:crafting_shaped` | Bannière violette (`minecraft:purple_banner`) |
+| `minecraft:purple_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière violette (`minecraft:purple_banner`) |
+| `minecraft:red_banner` | `minecraft:crafting_shaped` | Bannière rouge (`minecraft:red_banner`) |
+| `minecraft:red_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière rouge (`minecraft:red_banner`) |
+| `minecraft:shield_decoration` | `minecraft:crafting_special_shielddecoration` | Bouclier (`minecraft:shield`) |
+| `minecraft:skull_banner_pattern` | `minecraft:crafting_shapeless` | Motif de bannière (`minecraft:skull_banner_pattern`) |
+| `minecraft:white_banner` | `minecraft:crafting_shaped` | Bannière blanche (`minecraft:white_banner`) |
+| `minecraft:white_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière blanche (`minecraft:white_banner`) |
+| `minecraft:yellow_banner` | `minecraft:crafting_shaped` | Bannière jaune (`minecraft:yellow_banner`) |
+| `minecraft:yellow_banner_duplicate` | `minecraft:crafting_special_bannerduplicate` | Bannière jaune (`minecraft:yellow_banner`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_banner` | Bannière noire | Bloc et objet | 2 |
+| `minecraft:black_wall_banner` | black_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:blue_banner` | Bannière bleue | Bloc et objet | 2 |
+| `minecraft:blue_wall_banner` | blue_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:bordure_indented_banner_pattern` | Motif de bordure cannelée pour bannière | Objet | 1 |
+| `minecraft:brown_banner` | Bannière marron | Bloc et objet | 2 |
+| `minecraft:brown_wall_banner` | brown_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:creeper_banner_pattern` | Motif de bannière | Objet | 1 |
+| `minecraft:cyan_banner` | Bannière cyan | Bloc et objet | 2 |
+| `minecraft:cyan_wall_banner` | cyan_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:field_masoned_banner_pattern` | Motif de champ maçonné pour bannière | Objet | 1 |
+| `minecraft:flow_banner_pattern` | Motif de bannière | Objet | 0 |
+| `minecraft:flower_banner_pattern` | Motif de bannière | Objet | 1 |
+| `minecraft:globe_banner_pattern` | Motif de bannière | Objet | 0 |
+| `minecraft:gray_banner` | Bannière grise | Bloc et objet | 2 |
+| `minecraft:gray_wall_banner` | gray_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:green_banner` | Bannière verte | Bloc et objet | 2 |
+| `minecraft:green_wall_banner` | green_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:guster_banner_pattern` | Motif de bannière | Objet | 0 |
+| `minecraft:light_blue_banner` | Bannière bleu clair | Bloc et objet | 2 |
+| `minecraft:light_blue_wall_banner` | light_blue_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:light_gray_banner` | Bannière gris clair | Bloc et objet | 2 |
+| `minecraft:light_gray_wall_banner` | light_gray_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:lime_banner` | Bannière vert clair | Bloc et objet | 2 |
+| `minecraft:lime_wall_banner` | lime_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:loom` | Métier à tisser | Bloc et objet | 1 |
+| `minecraft:magenta_banner` | Bannière magenta | Bloc et objet | 2 |
+| `minecraft:magenta_wall_banner` | magenta_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:mojang_banner_pattern` | Motif de bannière | Objet | 1 |
+| `minecraft:orange_banner` | Bannière orange | Bloc et objet | 2 |
+| `minecraft:orange_wall_banner` | orange_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:piglin_banner_pattern` | Motif de bannière | Objet | 0 |
+| `minecraft:pink_banner` | Bannière rose | Bloc et objet | 2 |
+| `minecraft:pink_wall_banner` | pink_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:purple_banner` | Bannière violette | Bloc et objet | 2 |
+| `minecraft:purple_wall_banner` | purple_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:red_banner` | Bannière rouge | Bloc et objet | 2 |
+| `minecraft:red_wall_banner` | red_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:skull_banner_pattern` | Motif de bannière | Objet | 1 |
+| `minecraft:white_banner` | Bannière blanche | Bloc et objet | 2 |
+| `minecraft:white_wall_banner` | white_wall_banner | Bloc sans objet homonyme | 0 |
+| `minecraft:yellow_banner` | Bannière jaune | Bloc et objet | 2 |
+| `minecraft:yellow_wall_banner` | yellow_wall_banner | Bloc sans objet homonyme | 0 |
+
+## C46
+
+**Arc et arbalète · Archery** — [déclencheurs et règles](collections-minecraft.md#c46).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:arrow` | `minecraft:crafting_shaped` | Flèche (`minecraft:arrow`) × 4 |
+| `minecraft:bow` | `minecraft:crafting_shaped` | Arc (`minecraft:bow`) |
+| `minecraft:crossbow` | `minecraft:crafting_shaped` | Arbalète (`minecraft:crossbow`) |
+| `minecraft:fletching_table` | `minecraft:crafting_shaped` | Table d'archerie (`minecraft:fletching_table`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:arrow` | Flèche | Objet | 1 |
+| `minecraft:bow` | Arc | Objet | 1 |
+| `minecraft:cobweb` | Toile d'araignée | Bloc et objet | 0 |
+| `minecraft:crossbow` | Arbalète | Objet | 1 |
+| `minecraft:feather` | Plume | Objet | 0 |
+| `minecraft:fletching_table` | Table d'archerie | Bloc et objet | 1 |
+| `minecraft:flint` | Silex | Objet | 0 |
+| `minecraft:string` | Ficelle | Objet | 0 |
+
+## C47
+
+**Explosifs et pyrotechnie · Explosives and Fireworks** — [déclencheurs et règles](collections-minecraft.md#c47).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:fire_charge` | `minecraft:crafting_shapeless` | Charge de feu (`minecraft:fire_charge`) × 3 |
+| `minecraft:firework_rocket` | `minecraft:crafting_special_firework_rocket` | Fusée de feu d'artifice (`minecraft:firework_rocket`) × 3 |
+| `minecraft:firework_rocket_simple` | `minecraft:crafting_shapeless` | Fusée de feu d'artifice (`minecraft:firework_rocket`) × 3 |
+| `minecraft:firework_star` | `minecraft:crafting_special_firework_star` | Étoile de feu d'artifice (`minecraft:firework_star`) |
+| `minecraft:firework_star_fade` | `minecraft:crafting_special_firework_star_fade` | Étoile de feu d'artifice (`minecraft:firework_star`) |
+| `minecraft:tnt` | `minecraft:crafting_shaped` | TNT (`minecraft:tnt`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:fire_charge` | Charge de feu | Objet | 1 |
+| `minecraft:firework_rocket` | Fusée de feu d'artifice | Objet | 2 |
+| `minecraft:firework_star` | Étoile de feu d'artifice | Objet | 2 |
+| `minecraft:gunpowder` | Poudre à canon | Objet | 0 |
+| `minecraft:tnt` | TNT | Bloc et objet | 1 |
+
+## C48
+
+**Chambres des épreuves · Trial Chambers** — [déclencheurs et règles](collections-minecraft.md#c48).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:mace` | `minecraft:crafting_shaped` | Masse (`minecraft:mace`) |
+| `minecraft:wind_charge` | `minecraft:crafting_shapeless` | Charge de vent (`minecraft:wind_charge`) × 4 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:breeze_rod` | Bâton de Breeze | Objet | 0 |
+| `minecraft:heavy_core` | Noyau lourd | Bloc et objet | 0 |
+| `minecraft:mace` | Masse | Objet | 1 |
+| `minecraft:ominous_bottle` | Fiole funeste | Objet | 0 |
+| `minecraft:ominous_trial_key` | Clé des épreuves funeste | Objet | 0 |
+| `minecraft:trial_key` | Clé des épreuves | Objet | 0 |
+| `minecraft:trial_spawner` | Générateur des épreuves | Bloc et objet | 0 |
+| `minecraft:vault` | Coffre-fort | Bloc et objet | 0 |
+| `minecraft:wind_charge` | Charge de vent | Objet | 1 |
+
+## C49
+
+**Musique et Allays · Music and Allays** — [déclencheurs et règles](collections-minecraft.md#c49).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:jukebox` | `minecraft:crafting_shaped` | Jukebox (`minecraft:jukebox`) |
+| `minecraft:music_disc_5` | `minecraft:crafting_shapeless` | Disque (`minecraft:music_disc_5`) |
+| `minecraft:note_block` | `minecraft:crafting_shaped` | Bloc musical (`minecraft:note_block`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:disc_fragment_5` | Fragment de disque | Objet | 0 |
+| `minecraft:goat_horn` | Corne de chèvre | Objet | 0 |
+| `minecraft:jukebox` | Jukebox | Bloc et objet | 1 |
+| `minecraft:music_disc_11` | Disque | Objet | 0 |
+| `minecraft:music_disc_13` | Disque | Objet | 0 |
+| `minecraft:music_disc_5` | Disque | Objet | 1 |
+| `minecraft:music_disc_blocks` | Disque | Objet | 0 |
+| `minecraft:music_disc_bounce` | Disque | Objet | 0 |
+| `minecraft:music_disc_cat` | Disque | Objet | 0 |
+| `minecraft:music_disc_chirp` | Disque | Objet | 0 |
+| `minecraft:music_disc_creator` | Disque | Objet | 0 |
+| `minecraft:music_disc_creator_music_box` | Disque | Objet | 0 |
+| `minecraft:music_disc_far` | Disque | Objet | 0 |
+| `minecraft:music_disc_lava_chicken` | Disque | Objet | 0 |
+| `minecraft:music_disc_mall` | Disque | Objet | 0 |
+| `minecraft:music_disc_mellohi` | Disque | Objet | 0 |
+| `minecraft:music_disc_otherside` | Disque | Objet | 0 |
+| `minecraft:music_disc_pigstep` | Disque | Objet | 0 |
+| `minecraft:music_disc_precipice` | Disque | Objet | 0 |
+| `minecraft:music_disc_relic` | Disque | Objet | 0 |
+| `minecraft:music_disc_stal` | Disque | Objet | 0 |
+| `minecraft:music_disc_strad` | Disque | Objet | 0 |
+| `minecraft:music_disc_tears` | Disque | Objet | 0 |
+| `minecraft:music_disc_wait` | Disque | Objet | 0 |
+| `minecraft:music_disc_ward` | Disque | Objet | 0 |
+| `minecraft:note_block` | Bloc musical | Bloc et objet | 1 |
+
+## C50
+
+**Sculk et mémoire · Sculk and Echoes** — [déclencheurs et règles](collections-minecraft.md#c50).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:calibrated_sculk_sensor` | `minecraft:crafting_shaped` | Capteur sculk calibré (`minecraft:calibrated_sculk_sensor`) |
+| `minecraft:recovery_compass` | `minecraft:crafting_shaped` | Boussole de récupération (`minecraft:recovery_compass`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:calibrated_sculk_sensor` | Capteur sculk calibré | Bloc et objet | 1 |
+| `minecraft:echo_shard` | Éclat d'écho | Objet | 0 |
+| `minecraft:recovery_compass` | Boussole de récupération | Objet | 1 |
+| `minecraft:sculk` | Sculk | Bloc et objet | 0 |
+| `minecraft:sculk_catalyst` | Catalyseur de sculk | Bloc et objet | 0 |
+| `minecraft:sculk_sensor` | Capteur sculk | Bloc et objet | 0 |
+| `minecraft:sculk_shrieker` | Hurleur sculk | Bloc et objet | 0 |
+| `minecraft:sculk_vein` | Veine de sculk | Bloc et objet | 0 |
+
+## C51
+
+**Jardin pâle et résine · Pale Garden and Resin** — [déclencheurs et règles](collections-minecraft.md#c51).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:chiseled_resin_bricks` | `minecraft:crafting_shaped` | Briques de résine sculptées (`minecraft:chiseled_resin_bricks`) |
+| `minecraft:chiseled_resin_bricks_from_resin_bricks_stonecutting` | `minecraft:stonecutting` | Briques de résine sculptées (`minecraft:chiseled_resin_bricks`) |
+| `minecraft:creaking_heart` | `minecraft:crafting_shaped` | Coeur de grinceur (`minecraft:creaking_heart`) |
+| `minecraft:resin_block` | `minecraft:crafting_shaped` | Bloc de résine (`minecraft:resin_block`) |
+| `minecraft:resin_brick` | `minecraft:smelting` | Brique de résine (`minecraft:resin_brick`) |
+| `minecraft:resin_brick_slab` | `minecraft:crafting_shaped` | Dalle de briques de résine (`minecraft:resin_brick_slab`) × 6 |
+| `minecraft:resin_brick_slab_from_resin_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de briques de résine (`minecraft:resin_brick_slab`) × 2 |
+| `minecraft:resin_brick_stairs` | `minecraft:crafting_shaped` | Escalier en briques de résine (`minecraft:resin_brick_stairs`) × 4 |
+| `minecraft:resin_brick_stairs_from_resin_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en briques de résine (`minecraft:resin_brick_stairs`) |
+| `minecraft:resin_brick_wall` | `minecraft:crafting_shaped` | Muret de briques de résine (`minecraft:resin_brick_wall`) × 6 |
+| `minecraft:resin_brick_wall_from_resin_bricks_stonecutting` | `minecraft:stonecutting` | Muret de briques de résine (`minecraft:resin_brick_wall`) |
+| `minecraft:resin_bricks` | `minecraft:crafting_shaped` | Briques de résine (`minecraft:resin_bricks`) |
+| `minecraft:resin_clump` | `minecraft:crafting_shapeless` | Amas de résine (`minecraft:resin_clump`) × 9 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chiseled_resin_bricks` | Briques de résine sculptées | Bloc et objet | 2 |
+| `minecraft:creaking_heart` | Coeur de grinceur | Bloc et objet | 1 |
+| `minecraft:resin_block` | Bloc de résine | Bloc et objet | 1 |
+| `minecraft:resin_brick` | Brique de résine | Objet | 1 |
+| `minecraft:resin_brick_slab` | Dalle de briques de résine | Bloc et objet | 2 |
+| `minecraft:resin_brick_stairs` | Escalier en briques de résine | Bloc et objet | 2 |
+| `minecraft:resin_brick_wall` | Muret de briques de résine | Bloc et objet | 2 |
+| `minecraft:resin_bricks` | Briques de résine | Bloc et objet | 1 |
+| `minecraft:resin_clump` | Amas de résine | Bloc et objet | 1 |
+
+## C52
+
+**Architecture du Nether · Nether Masonry** — [déclencheurs et règles](collections-minecraft.md#c52).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:blackstone_slab` | `minecraft:crafting_shaped` | Dalle de roche noire (`minecraft:blackstone_slab`) × 6 |
+| `minecraft:blackstone_slab_from_blackstone_stonecutting` | `minecraft:stonecutting` | Dalle de roche noire (`minecraft:blackstone_slab`) × 2 |
+| `minecraft:blackstone_stairs` | `minecraft:crafting_shaped` | Escalier en roche noire (`minecraft:blackstone_stairs`) × 4 |
+| `minecraft:blackstone_stairs_from_blackstone_stonecutting` | `minecraft:stonecutting` | Escalier en roche noire (`minecraft:blackstone_stairs`) |
+| `minecraft:blackstone_wall` | `minecraft:crafting_shaped` | Muret de roche noire (`minecraft:blackstone_wall`) × 6 |
+| `minecraft:blackstone_wall_from_blackstone_stonecutting` | `minecraft:stonecutting` | Muret de roche noire (`minecraft:blackstone_wall`) |
+| `minecraft:chiseled_nether_bricks` | `minecraft:crafting_shaped` | Briques du Nether sculptées (`minecraft:chiseled_nether_bricks`) |
+| `minecraft:chiseled_nether_bricks_from_nether_bricks_stonecutting` | `minecraft:stonecutting` | Briques du Nether sculptées (`minecraft:chiseled_nether_bricks`) |
+| `minecraft:chiseled_polished_blackstone` | `minecraft:crafting_shaped` | Pierre noire sculptée (`minecraft:chiseled_polished_blackstone`) |
+| `minecraft:chiseled_polished_blackstone_from_blackstone_stonecutting` | `minecraft:stonecutting` | Pierre noire sculptée (`minecraft:chiseled_polished_blackstone`) |
+| `minecraft:chiseled_polished_blackstone_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Pierre noire sculptée (`minecraft:chiseled_polished_blackstone`) |
+| `minecraft:chiseled_quartz_block` | `minecraft:crafting_shaped` | Bloc de quartz sculpté (`minecraft:chiseled_quartz_block`) |
+| `minecraft:chiseled_quartz_block_from_quartz_block_stonecutting` | `minecraft:stonecutting` | Bloc de quartz sculpté (`minecraft:chiseled_quartz_block`) |
+| `minecraft:cracked_nether_bricks` | `minecraft:smelting` | Briques du Nether craquelées (`minecraft:cracked_nether_bricks`) |
+| `minecraft:cracked_polished_blackstone_bricks` | `minecraft:smelting` | Pierre noire taillée craquelée (`minecraft:cracked_polished_blackstone_bricks`) |
+| `minecraft:glowstone` | `minecraft:crafting_shaped` | Pierre lumineuse (`minecraft:glowstone`) |
+| `minecraft:magma_block` | `minecraft:crafting_shaped` | Bloc de magma (`minecraft:magma_block`) |
+| `minecraft:nether_brick` | `minecraft:smelting` | Brique du Nether (`minecraft:nether_brick`) |
+| `minecraft:nether_brick_fence` | `minecraft:crafting_shaped` | Barrière en briques du Nether (`minecraft:nether_brick_fence`) × 6 |
+| `minecraft:nether_brick_slab` | `minecraft:crafting_shaped` | Dalle de briques du Nether (`minecraft:nether_brick_slab`) × 6 |
+| `minecraft:nether_brick_slab_from_nether_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de briques du Nether (`minecraft:nether_brick_slab`) × 2 |
+| `minecraft:nether_brick_stairs` | `minecraft:crafting_shaped` | Escalier en briques du Nether (`minecraft:nether_brick_stairs`) × 4 |
+| `minecraft:nether_brick_stairs_from_nether_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en briques du Nether (`minecraft:nether_brick_stairs`) |
+| `minecraft:nether_brick_wall` | `minecraft:crafting_shaped` | Muret de briques du Nether (`minecraft:nether_brick_wall`) × 6 |
+| `minecraft:nether_brick_wall_from_nether_bricks_stonecutting` | `minecraft:stonecutting` | Muret de briques du Nether (`minecraft:nether_brick_wall`) |
+| `minecraft:nether_bricks` | `minecraft:crafting_shaped` | Briques du Nether (`minecraft:nether_bricks`) |
+| `minecraft:nether_wart_block` | `minecraft:crafting_shapeless` | Bloc de verrues du Nether (`minecraft:nether_wart_block`) |
+| `minecraft:polished_basalt` | `minecraft:crafting_shaped` | Basalte poli (`minecraft:polished_basalt`) × 4 |
+| `minecraft:polished_basalt_from_basalt_stonecutting` | `minecraft:stonecutting` | Basalte poli (`minecraft:polished_basalt`) |
+| `minecraft:polished_blackstone` | `minecraft:crafting_shaped` | Pierre noire (`minecraft:polished_blackstone`) × 4 |
+| `minecraft:polished_blackstone_brick_slab` | `minecraft:crafting_shaped` | Dalle de pierre noire taillée (`minecraft:polished_blackstone_brick_slab`) × 6 |
+| `minecraft:polished_blackstone_brick_slab_from_blackstone_stonecutting` | `minecraft:stonecutting` | Dalle de pierre noire taillée (`minecraft:polished_blackstone_brick_slab`) × 2 |
+| `minecraft:polished_blackstone_brick_slab_from_polished_blackstone_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de pierre noire taillée (`minecraft:polished_blackstone_brick_slab`) × 2 |
+| `minecraft:polished_blackstone_brick_slab_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Dalle de pierre noire taillée (`minecraft:polished_blackstone_brick_slab`) × 2 |
+| `minecraft:polished_blackstone_brick_stairs` | `minecraft:crafting_shaped` | Escalier en pierre noire taillée (`minecraft:polished_blackstone_brick_stairs`) × 4 |
+| `minecraft:polished_blackstone_brick_stairs_from_blackstone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre noire taillée (`minecraft:polished_blackstone_brick_stairs`) |
+| `minecraft:polished_blackstone_brick_stairs_from_polished_blackstone_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en pierre noire taillée (`minecraft:polished_blackstone_brick_stairs`) |
+| `minecraft:polished_blackstone_brick_stairs_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre noire taillée (`minecraft:polished_blackstone_brick_stairs`) |
+| `minecraft:polished_blackstone_brick_wall` | `minecraft:crafting_shaped` | Muret de pierre noire taillée (`minecraft:polished_blackstone_brick_wall`) × 6 |
+| `minecraft:polished_blackstone_brick_wall_from_blackstone_stonecutting` | `minecraft:stonecutting` | Muret de pierre noire taillée (`minecraft:polished_blackstone_brick_wall`) |
+| `minecraft:polished_blackstone_brick_wall_from_polished_blackstone_bricks_stonecutting` | `minecraft:stonecutting` | Muret de pierre noire taillée (`minecraft:polished_blackstone_brick_wall`) |
+| `minecraft:polished_blackstone_brick_wall_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Muret de pierre noire taillée (`minecraft:polished_blackstone_brick_wall`) |
+| `minecraft:polished_blackstone_bricks` | `minecraft:crafting_shaped` | Pierre noire taillée (`minecraft:polished_blackstone_bricks`) × 4 |
+| `minecraft:polished_blackstone_bricks_from_blackstone_stonecutting` | `minecraft:stonecutting` | Pierre noire taillée (`minecraft:polished_blackstone_bricks`) |
+| `minecraft:polished_blackstone_bricks_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Pierre noire taillée (`minecraft:polished_blackstone_bricks`) |
+| `minecraft:polished_blackstone_from_blackstone_stonecutting` | `minecraft:stonecutting` | Pierre noire (`minecraft:polished_blackstone`) |
+| `minecraft:polished_blackstone_slab` | `minecraft:crafting_shaped` | Dalle de pierre noire (`minecraft:polished_blackstone_slab`) × 6 |
+| `minecraft:polished_blackstone_slab_from_blackstone_stonecutting` | `minecraft:stonecutting` | Dalle de pierre noire (`minecraft:polished_blackstone_slab`) × 2 |
+| `minecraft:polished_blackstone_slab_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Dalle de pierre noire (`minecraft:polished_blackstone_slab`) × 2 |
+| `minecraft:polished_blackstone_stairs` | `minecraft:crafting_shaped` | Escalier en pierre noire (`minecraft:polished_blackstone_stairs`) × 4 |
+| `minecraft:polished_blackstone_stairs_from_blackstone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre noire (`minecraft:polished_blackstone_stairs`) |
+| `minecraft:polished_blackstone_stairs_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre noire (`minecraft:polished_blackstone_stairs`) |
+| `minecraft:polished_blackstone_wall` | `minecraft:crafting_shaped` | Muret de pierre noire (`minecraft:polished_blackstone_wall`) × 6 |
+| `minecraft:polished_blackstone_wall_from_blackstone_stonecutting` | `minecraft:stonecutting` | Muret de pierre noire (`minecraft:polished_blackstone_wall`) |
+| `minecraft:polished_blackstone_wall_from_polished_blackstone_stonecutting` | `minecraft:stonecutting` | Muret de pierre noire (`minecraft:polished_blackstone_wall`) |
+| `minecraft:quartz` | `minecraft:smelting` | Quartz du Nether (`minecraft:quartz`) |
+| `minecraft:quartz_block` | `minecraft:crafting_shaped` | Bloc de quartz (`minecraft:quartz_block`) |
+| `minecraft:quartz_bricks` | `minecraft:crafting_shaped` | Briques de quartz (`minecraft:quartz_bricks`) × 4 |
+| `minecraft:quartz_bricks_from_quartz_block_stonecutting` | `minecraft:stonecutting` | Briques de quartz (`minecraft:quartz_bricks`) |
+| `minecraft:quartz_from_blasting` | `minecraft:blasting` | Quartz du Nether (`minecraft:quartz`) |
+| `minecraft:quartz_pillar` | `minecraft:crafting_shaped` | Pilier de quartz (`minecraft:quartz_pillar`) × 2 |
+| `minecraft:quartz_pillar_from_quartz_block_stonecutting` | `minecraft:stonecutting` | Pilier de quartz (`minecraft:quartz_pillar`) |
+| `minecraft:quartz_slab` | `minecraft:crafting_shaped` | Dalle de quartz (`minecraft:quartz_slab`) × 6 |
+| `minecraft:quartz_slab_from_quartz_block_stonecutting` | `minecraft:stonecutting` | Dalle de quartz (`minecraft:quartz_slab`) × 2 |
+| `minecraft:quartz_stairs` | `minecraft:crafting_shaped` | Escalier en quartz (`minecraft:quartz_stairs`) × 4 |
+| `minecraft:quartz_stairs_from_quartz_block_stonecutting` | `minecraft:stonecutting` | Escalier en quartz (`minecraft:quartz_stairs`) |
+| `minecraft:red_nether_brick_slab` | `minecraft:crafting_shaped` | Dalle de briques rouges du Nether (`minecraft:red_nether_brick_slab`) × 6 |
+| `minecraft:red_nether_brick_slab_from_red_nether_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de briques rouges du Nether (`minecraft:red_nether_brick_slab`) × 2 |
+| `minecraft:red_nether_brick_stairs` | `minecraft:crafting_shaped` | Escalier en briques rouges du Nether (`minecraft:red_nether_brick_stairs`) × 4 |
+| `minecraft:red_nether_brick_stairs_from_red_nether_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en briques rouges du Nether (`minecraft:red_nether_brick_stairs`) |
+| `minecraft:red_nether_brick_wall` | `minecraft:crafting_shaped` | Muret de briques rouges du Nether (`minecraft:red_nether_brick_wall`) × 6 |
+| `minecraft:red_nether_brick_wall_from_red_nether_bricks_stonecutting` | `minecraft:stonecutting` | Muret de briques rouges du Nether (`minecraft:red_nether_brick_wall`) |
+| `minecraft:red_nether_bricks` | `minecraft:crafting_shaped` | Briques rouges du Nether (`minecraft:red_nether_bricks`) |
+| `minecraft:smooth_basalt` | `minecraft:smelting` | Basalte lisse (`minecraft:smooth_basalt`) |
+| `minecraft:smooth_quartz` | `minecraft:smelting` | Bloc de quartz lisse (`minecraft:smooth_quartz`) |
+| `minecraft:smooth_quartz_slab` | `minecraft:crafting_shaped` | Dalle de quartz lisse (`minecraft:smooth_quartz_slab`) × 6 |
+| `minecraft:smooth_quartz_slab_from_smooth_quartz_stonecutting` | `minecraft:stonecutting` | Dalle de quartz lisse (`minecraft:smooth_quartz_slab`) × 2 |
+| `minecraft:smooth_quartz_stairs` | `minecraft:crafting_shaped` | Escalier en quartz lisse (`minecraft:smooth_quartz_stairs`) × 4 |
+| `minecraft:smooth_quartz_stairs_from_smooth_quartz_stonecutting` | `minecraft:stonecutting` | Escalier en quartz lisse (`minecraft:smooth_quartz_stairs`) |
+| `minecraft:soul_campfire` | `minecraft:crafting_shaped` | Feu de camp des âmes (`minecraft:soul_campfire`) |
+| `minecraft:soul_lantern` | `minecraft:crafting_shaped` | Lanterne des âmes (`minecraft:soul_lantern`) |
+| `minecraft:soul_torch` | `minecraft:crafting_shaped` | Torche des âmes (`minecraft:soul_torch`) × 4 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:basalt` | Basalte | Bloc et objet | 0 |
+| `minecraft:blackstone` | Roche noire | Bloc et objet | 0 |
+| `minecraft:blackstone_slab` | Dalle de roche noire | Bloc et objet | 2 |
+| `minecraft:blackstone_stairs` | Escalier en roche noire | Bloc et objet | 2 |
+| `minecraft:blackstone_wall` | Muret de roche noire | Bloc et objet | 2 |
+| `minecraft:chiseled_nether_bricks` | Briques du Nether sculptées | Bloc et objet | 2 |
+| `minecraft:chiseled_polished_blackstone` | Pierre noire sculptée | Bloc et objet | 3 |
+| `minecraft:chiseled_quartz_block` | Bloc de quartz sculpté | Bloc et objet | 2 |
+| `minecraft:cracked_nether_bricks` | Briques du Nether craquelées | Bloc et objet | 1 |
+| `minecraft:cracked_polished_blackstone_bricks` | Pierre noire taillée craquelée | Bloc et objet | 1 |
+| `minecraft:gilded_blackstone` | Roche noire dorée | Bloc et objet | 0 |
+| `minecraft:glowstone` | Pierre lumineuse | Bloc et objet | 1 |
+| `minecraft:glowstone_dust` | Poudre lumineuse | Objet | 0 |
+| `minecraft:magma_block` | Bloc de magma | Bloc et objet | 1 |
+| `minecraft:nether_brick` | Brique du Nether | Objet | 1 |
+| `minecraft:nether_brick_fence` | Barrière en briques du Nether | Bloc et objet | 1 |
+| `minecraft:nether_brick_slab` | Dalle de briques du Nether | Bloc et objet | 2 |
+| `minecraft:nether_brick_stairs` | Escalier en briques du Nether | Bloc et objet | 2 |
+| `minecraft:nether_brick_wall` | Muret de briques du Nether | Bloc et objet | 2 |
+| `minecraft:nether_bricks` | Briques du Nether | Bloc et objet | 1 |
+| `minecraft:nether_gold_ore` | Minerai d'or du Nether | Bloc et objet | 0 |
+| `minecraft:nether_quartz_ore` | Minerai de quartz du Nether | Bloc et objet | 0 |
+| `minecraft:nether_wart` | Verrues du Nether | Bloc et objet | 0 |
+| `minecraft:nether_wart_block` | Bloc de verrues du Nether | Bloc et objet | 1 |
+| `minecraft:netherrack` | Netherrack | Bloc et objet | 0 |
+| `minecraft:polished_basalt` | Basalte poli | Bloc et objet | 2 |
+| `minecraft:polished_blackstone` | Pierre noire | Bloc et objet | 2 |
+| `minecraft:polished_blackstone_brick_slab` | Dalle de pierre noire taillée | Bloc et objet | 4 |
+| `minecraft:polished_blackstone_brick_stairs` | Escalier en pierre noire taillée | Bloc et objet | 4 |
+| `minecraft:polished_blackstone_brick_wall` | Muret de pierre noire taillée | Bloc et objet | 4 |
+| `minecraft:polished_blackstone_bricks` | Pierre noire taillée | Bloc et objet | 3 |
+| `minecraft:polished_blackstone_slab` | Dalle de pierre noire | Bloc et objet | 3 |
+| `minecraft:polished_blackstone_stairs` | Escalier en pierre noire | Bloc et objet | 3 |
+| `minecraft:polished_blackstone_wall` | Muret de pierre noire | Bloc et objet | 3 |
+| `minecraft:quartz` | Quartz du Nether | Objet | 2 |
+| `minecraft:quartz_block` | Bloc de quartz | Bloc et objet | 1 |
+| `minecraft:quartz_bricks` | Briques de quartz | Bloc et objet | 2 |
+| `minecraft:quartz_pillar` | Pilier de quartz | Bloc et objet | 2 |
+| `minecraft:quartz_slab` | Dalle de quartz | Bloc et objet | 2 |
+| `minecraft:quartz_stairs` | Escalier en quartz | Bloc et objet | 2 |
+| `minecraft:red_nether_brick_slab` | Dalle de briques rouges du Nether | Bloc et objet | 2 |
+| `minecraft:red_nether_brick_stairs` | Escalier en briques rouges du Nether | Bloc et objet | 2 |
+| `minecraft:red_nether_brick_wall` | Muret de briques rouges du Nether | Bloc et objet | 2 |
+| `minecraft:red_nether_bricks` | Briques rouges du Nether | Bloc et objet | 1 |
+| `minecraft:shroomlight` | Champilampe | Bloc et objet | 0 |
+| `minecraft:smooth_basalt` | Basalte lisse | Bloc et objet | 1 |
+| `minecraft:smooth_quartz` | Bloc de quartz lisse | Bloc et objet | 1 |
+| `minecraft:smooth_quartz_slab` | Dalle de quartz lisse | Bloc et objet | 2 |
+| `minecraft:smooth_quartz_stairs` | Escalier en quartz lisse | Bloc et objet | 2 |
+| `minecraft:soul_campfire` | Feu de camp des âmes | Bloc et objet | 1 |
+| `minecraft:soul_fire` | Feu des âmes | Bloc sans objet homonyme | 0 |
+| `minecraft:soul_lantern` | Lanterne des âmes | Bloc et objet | 1 |
+| `minecraft:soul_sand` | Sable des âmes | Bloc et objet | 0 |
+| `minecraft:soul_soil` | Terre des âmes | Bloc et objet | 0 |
+| `minecraft:soul_torch` | Torche des âmes | Bloc et objet | 1 |
+| `minecraft:soul_wall_torch` | Torche des âmes murale | Bloc sans objet homonyme | 0 |
+
+## C53
+
+**Bois carmin et biscornu · Nether Woodworking** — [déclencheurs et règles](collections-minecraft.md#c53).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:crimson_button` | `minecraft:crafting_shapeless` | Bouton carmin (`minecraft:crimson_button`) |
+| `minecraft:crimson_door` | `minecraft:crafting_shaped` | Porte carmin (`minecraft:crimson_door`) × 3 |
+| `minecraft:crimson_fence` | `minecraft:crafting_shaped` | Barrière carmin (`minecraft:crimson_fence`) × 3 |
+| `minecraft:crimson_fence_gate` | `minecraft:crafting_shaped` | Portillon carmin (`minecraft:crimson_fence_gate`) |
+| `minecraft:crimson_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue carmin (`minecraft:crimson_hanging_sign`) × 6 |
+| `minecraft:crimson_hyphae` | `minecraft:crafting_shaped` | Hyphes carmin (`minecraft:crimson_hyphae`) × 3 |
+| `minecraft:crimson_planks` | `minecraft:crafting_shapeless` | Planches carmin (`minecraft:crimson_planks`) × 4 |
+| `minecraft:crimson_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression carmin (`minecraft:crimson_pressure_plate`) |
+| `minecraft:crimson_shelf` | `minecraft:crafting_shaped` | Étagère carmin (`minecraft:crimson_shelf`) × 6 |
+| `minecraft:crimson_sign` | `minecraft:crafting_shaped` | Pancarte carmin (`minecraft:crimson_sign`) × 3 |
+| `minecraft:crimson_slab` | `minecraft:crafting_shaped` | Dalle carmin (`minecraft:crimson_slab`) × 6 |
+| `minecraft:crimson_stairs` | `minecraft:crafting_shaped` | Escalier carmin (`minecraft:crimson_stairs`) × 4 |
+| `minecraft:crimson_trapdoor` | `minecraft:crafting_shaped` | Trappe carmin (`minecraft:crimson_trapdoor`) × 2 |
+| `minecraft:stripped_crimson_hyphae` | `minecraft:crafting_shaped` | Hyphes carmin dénudées (`minecraft:stripped_crimson_hyphae`) × 3 |
+| `minecraft:stripped_warped_hyphae` | `minecraft:crafting_shaped` | Hyphes biscornues dénudées (`minecraft:stripped_warped_hyphae`) × 3 |
+| `minecraft:warped_button` | `minecraft:crafting_shapeless` | Bouton biscornu (`minecraft:warped_button`) |
+| `minecraft:warped_door` | `minecraft:crafting_shaped` | Porte biscornue (`minecraft:warped_door`) × 3 |
+| `minecraft:warped_fence` | `minecraft:crafting_shaped` | Barrière biscornue (`minecraft:warped_fence`) × 3 |
+| `minecraft:warped_fence_gate` | `minecraft:crafting_shaped` | Portillon biscornu (`minecraft:warped_fence_gate`) |
+| `minecraft:warped_hanging_sign` | `minecraft:crafting_shaped` | Pancarte suspendue biscornue (`minecraft:warped_hanging_sign`) × 6 |
+| `minecraft:warped_hyphae` | `minecraft:crafting_shaped` | Hyphes biscornues (`minecraft:warped_hyphae`) × 3 |
+| `minecraft:warped_planks` | `minecraft:crafting_shapeless` | Planches biscornues (`minecraft:warped_planks`) × 4 |
+| `minecraft:warped_pressure_plate` | `minecraft:crafting_shaped` | Plaque de pression biscornue (`minecraft:warped_pressure_plate`) |
+| `minecraft:warped_shelf` | `minecraft:crafting_shaped` | Étagère biscornue (`minecraft:warped_shelf`) × 6 |
+| `minecraft:warped_sign` | `minecraft:crafting_shaped` | Pancarte biscornue (`minecraft:warped_sign`) × 3 |
+| `minecraft:warped_slab` | `minecraft:crafting_shaped` | Dalle biscornue (`minecraft:warped_slab`) × 6 |
+| `minecraft:warped_stairs` | `minecraft:crafting_shaped` | Escalier biscornu (`minecraft:warped_stairs`) × 4 |
+| `minecraft:warped_trapdoor` | `minecraft:crafting_shaped` | Trappe biscornue (`minecraft:warped_trapdoor`) × 2 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:crimson_button` | Bouton carmin | Bloc et objet | 1 |
+| `minecraft:crimson_door` | Porte carmin | Bloc et objet | 1 |
+| `minecraft:crimson_fence` | Barrière carmin | Bloc et objet | 1 |
+| `minecraft:crimson_fence_gate` | Portillon carmin | Bloc et objet | 1 |
+| `minecraft:crimson_fungus` | Champignon carmin | Bloc et objet | 0 |
+| `minecraft:crimson_hanging_sign` | Pancarte suspendue carmin | Bloc et objet | 1 |
+| `minecraft:crimson_hyphae` | Hyphes carmin | Bloc et objet | 1 |
+| `minecraft:crimson_nylium` | Nylium carmin | Bloc et objet | 0 |
+| `minecraft:crimson_planks` | Planches carmin | Bloc et objet | 1 |
+| `minecraft:crimson_pressure_plate` | Plaque de pression carmin | Bloc et objet | 1 |
+| `minecraft:crimson_roots` | Racines carmin | Bloc et objet | 0 |
+| `minecraft:crimson_shelf` | Étagère carmin | Bloc et objet | 1 |
+| `minecraft:crimson_sign` | Pancarte carmin | Bloc et objet | 1 |
+| `minecraft:crimson_slab` | Dalle carmin | Bloc et objet | 1 |
+| `minecraft:crimson_stairs` | Escalier carmin | Bloc et objet | 1 |
+| `minecraft:crimson_stem` | Tige carmin | Bloc et objet | 0 |
+| `minecraft:crimson_trapdoor` | Trappe carmin | Bloc et objet | 1 |
+| `minecraft:crimson_wall_hanging_sign` | Pancarte suspendue carmin murale | Bloc sans objet homonyme | 0 |
+| `minecraft:crimson_wall_sign` | Pancarte carmin murale | Bloc sans objet homonyme | 0 |
+| `minecraft:nether_sprouts` | Germes du Nether | Bloc et objet | 0 |
+| `minecraft:stripped_crimson_hyphae` | Hyphes carmin dénudées | Bloc et objet | 1 |
+| `minecraft:stripped_crimson_stem` | Tige carmin dénudée | Bloc et objet | 0 |
+| `minecraft:stripped_warped_hyphae` | Hyphes biscornues dénudées | Bloc et objet | 1 |
+| `minecraft:stripped_warped_stem` | Tige biscornue dénudée | Bloc et objet | 0 |
+| `minecraft:twisting_vines` | Lianes tortueuses | Bloc et objet | 0 |
+| `minecraft:twisting_vines_plant` | Plante de lianes tortueuses | Bloc sans objet homonyme | 0 |
+| `minecraft:warped_button` | Bouton biscornu | Bloc et objet | 1 |
+| `minecraft:warped_door` | Porte biscornue | Bloc et objet | 1 |
+| `minecraft:warped_fence` | Barrière biscornue | Bloc et objet | 1 |
+| `minecraft:warped_fence_gate` | Portillon biscornu | Bloc et objet | 1 |
+| `minecraft:warped_fungus` | Champignon biscornu | Bloc et objet | 0 |
+| `minecraft:warped_hanging_sign` | Pancarte suspendue biscornue | Bloc et objet | 1 |
+| `minecraft:warped_hyphae` | Hyphes biscornues | Bloc et objet | 1 |
+| `minecraft:warped_nylium` | Nylium biscornu | Bloc et objet | 0 |
+| `minecraft:warped_planks` | Planches biscornues | Bloc et objet | 1 |
+| `minecraft:warped_pressure_plate` | Plaque de pression biscornue | Bloc et objet | 1 |
+| `minecraft:warped_roots` | Racines biscornues | Bloc et objet | 0 |
+| `minecraft:warped_shelf` | Étagère biscornue | Bloc et objet | 1 |
+| `minecraft:warped_sign` | Pancarte biscornue | Bloc et objet | 1 |
+| `minecraft:warped_slab` | Dalle biscornue | Bloc et objet | 1 |
+| `minecraft:warped_stairs` | Escalier biscornu | Bloc et objet | 1 |
+| `minecraft:warped_stem` | Tige biscornue | Bloc et objet | 0 |
+| `minecraft:warped_trapdoor` | Trappe biscornue | Bloc et objet | 1 |
+| `minecraft:warped_wall_hanging_sign` | Pancarte suspendue biscornue murale | Bloc sans objet homonyme | 0 |
+| `minecraft:warped_wall_sign` | Pancarte biscornue murale | Bloc sans objet homonyme | 0 |
+| `minecraft:warped_wart_block` | Bloc de verrues biscornues | Bloc et objet | 0 |
+| `minecraft:weeping_vines` | Lianes pleureuses | Bloc et objet | 0 |
+| `minecraft:weeping_vines_plant` | Plante de lianes pleureuses | Bloc sans objet homonyme | 0 |
+
+## C54
+
+**Blazes et préparation · Blaze Preparations** — [déclencheurs et règles](collections-minecraft.md#c54).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:blaze_powder` | `minecraft:crafting_shapeless` | Poudre de Blaze (`minecraft:blaze_powder`) × 2 |
+| `minecraft:ender_eye` | `minecraft:crafting_shapeless` | Oeil de l'Ender (`minecraft:ender_eye`) |
+| `minecraft:magma_cream` | `minecraft:crafting_shapeless` | Crème de magma (`minecraft:magma_cream`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:blaze_powder` | Poudre de Blaze | Objet | 1 |
+| `minecraft:blaze_rod` | Bâton de Blaze | Objet | 0 |
+| `minecraft:ender_eye` | Oeil de l'Ender | Objet | 1 |
+| `minecraft:ender_pearl` | Perle de l'Ender | Objet | 0 |
+| `minecraft:magma_cream` | Crème de magma | Objet | 1 |
+
+## C55
+
+**Alchimie · Brewing** — [déclencheurs et règles](collections-minecraft.md#c55).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:brewing/lingering_potion_awkward_blaze_powder` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strength"}}` |
+| `minecraft:brewing/lingering_potion_awkward_breeze_rod` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:wind_charged"}}` |
+| `minecraft:brewing/lingering_potion_awkward_cobweb` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weaving"}}` |
+| `minecraft:brewing/lingering_potion_awkward_ghast_tear` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:regeneration"}}` |
+| `minecraft:brewing/lingering_potion_awkward_glistering_melon_slice` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:healing"}}` |
+| `minecraft:brewing/lingering_potion_awkward_golden_carrot` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:night_vision"}}` |
+| `minecraft:brewing/lingering_potion_awkward_magma_cream` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:fire_resistance"}}` |
+| `minecraft:brewing/lingering_potion_awkward_phantom_membrane` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slow_falling"}}` |
+| `minecraft:brewing/lingering_potion_awkward_pufferfish` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water_breathing"}}` |
+| `minecraft:brewing/lingering_potion_awkward_rabbit_foot` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:leaping"}}` |
+| `minecraft:brewing/lingering_potion_awkward_slime_block` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:oozing"}}` |
+| `minecraft:brewing/lingering_potion_awkward_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:poison"}}` |
+| `minecraft:brewing/lingering_potion_awkward_stone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:infested"}}` |
+| `minecraft:brewing/lingering_potion_awkward_sugar` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:swiftness"}}` |
+| `minecraft:brewing/lingering_potion_awkward_turtle_helmet` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:turtle_master"}}` |
+| `minecraft:brewing/lingering_potion_fire_resistance_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_fire_resistance"}}` |
+| `minecraft:brewing/lingering_potion_harming_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/lingering_potion_healing_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/lingering_potion_healing_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_healing"}}` |
+| `minecraft:brewing/lingering_potion_invisibility_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/lingering_potion_leaping_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/lingering_potion_leaping_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_leaping"}}` |
+| `minecraft:brewing/lingering_potion_leaping_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_leaping"}}` |
+| `minecraft:brewing/lingering_potion_long_leaping_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/lingering_potion_long_night_vision_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/lingering_potion_long_poison_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/lingering_potion_long_swiftness_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/lingering_potion_night_vision_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:invisibility"}}` |
+| `minecraft:brewing/lingering_potion_night_vision_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_night_vision"}}` |
+| `minecraft:brewing/lingering_potion_poison_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/lingering_potion_poison_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_poison"}}` |
+| `minecraft:brewing/lingering_potion_poison_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_poison"}}` |
+| `minecraft:brewing/lingering_potion_regeneration_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_regeneration"}}` |
+| `minecraft:brewing/lingering_potion_regeneration_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_regeneration"}}` |
+| `minecraft:brewing/lingering_potion_slow_falling_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slow_falling"}}` |
+| `minecraft:brewing/lingering_potion_slowness_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_slowness"}}` |
+| `minecraft:brewing/lingering_potion_slowness_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/lingering_potion_strength_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_strength"}}` |
+| `minecraft:brewing/lingering_potion_strength_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_strength"}}` |
+| `minecraft:brewing/lingering_potion_strong_healing_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/lingering_potion_strong_poison_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/lingering_potion_swiftness_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/lingering_potion_swiftness_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_swiftness"}}` |
+| `minecraft:brewing/lingering_potion_swiftness_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_swiftness"}}` |
+| `minecraft:brewing/lingering_potion_turtle_master_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_turtle_master"}}` |
+| `minecraft:brewing/lingering_potion_turtle_master_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_turtle_master"}}` |
+| `minecraft:brewing/lingering_potion_water_blaze_powder` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_breathing_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_water_breathing"}}` |
+| `minecraft:brewing/lingering_potion_water_breeze_rod` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_cobweb` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_fermented_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weakness"}}` |
+| `minecraft:brewing/lingering_potion_water_ghast_tear` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_glistering_melon_slice` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_glowstone_dust` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:thick"}}` |
+| `minecraft:brewing/lingering_potion_water_magma_cream` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_nether_wart` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:awkward"}}` |
+| `minecraft:brewing/lingering_potion_water_rabbit_foot` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_slime_block` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_spider_eye` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_stone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_water_sugar` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/lingering_potion_weakness_redstone` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_weakness"}}` |
+| `minecraft:brewing/potion_awkward_blaze_powder` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strength"}}` |
+| `minecraft:brewing/potion_awkward_breeze_rod` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:wind_charged"}}` |
+| `minecraft:brewing/potion_awkward_cobweb` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weaving"}}` |
+| `minecraft:brewing/potion_awkward_ghast_tear` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:regeneration"}}` |
+| `minecraft:brewing/potion_awkward_glistering_melon_slice` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:healing"}}` |
+| `minecraft:brewing/potion_awkward_golden_carrot` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:night_vision"}}` |
+| `minecraft:brewing/potion_awkward_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:awkward"}}` |
+| `minecraft:brewing/potion_awkward_magma_cream` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:fire_resistance"}}` |
+| `minecraft:brewing/potion_awkward_phantom_membrane` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slow_falling"}}` |
+| `minecraft:brewing/potion_awkward_pufferfish` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water_breathing"}}` |
+| `minecraft:brewing/potion_awkward_rabbit_foot` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:leaping"}}` |
+| `minecraft:brewing/potion_awkward_slime_block` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:oozing"}}` |
+| `minecraft:brewing/potion_awkward_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:poison"}}` |
+| `minecraft:brewing/potion_awkward_stone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:infested"}}` |
+| `minecraft:brewing/potion_awkward_sugar` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:swiftness"}}` |
+| `minecraft:brewing/potion_awkward_turtle_helmet` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:turtle_master"}}` |
+| `minecraft:brewing/potion_fire_resistance_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:fire_resistance"}}` |
+| `minecraft:brewing/potion_fire_resistance_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_fire_resistance"}}` |
+| `minecraft:brewing/potion_harming_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/potion_harming_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/potion_healing_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/potion_healing_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_healing"}}` |
+| `minecraft:brewing/potion_healing_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:healing"}}` |
+| `minecraft:brewing/potion_infested_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:infested"}}` |
+| `minecraft:brewing/potion_invisibility_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:invisibility"}}` |
+| `minecraft:brewing/potion_invisibility_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/potion_leaping_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/potion_leaping_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_leaping"}}` |
+| `minecraft:brewing/potion_leaping_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:leaping"}}` |
+| `minecraft:brewing/potion_leaping_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_leaping"}}` |
+| `minecraft:brewing/potion_long_fire_resistance_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_fire_resistance"}}` |
+| `minecraft:brewing/potion_long_invisibility_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/potion_long_leaping_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/potion_long_leaping_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_leaping"}}` |
+| `minecraft:brewing/potion_long_night_vision_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/potion_long_night_vision_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_night_vision"}}` |
+| `minecraft:brewing/potion_long_poison_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/potion_long_poison_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_poison"}}` |
+| `minecraft:brewing/potion_long_regeneration_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_regeneration"}}` |
+| `minecraft:brewing/potion_long_slow_falling_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slow_falling"}}` |
+| `minecraft:brewing/potion_long_slowness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/potion_long_strength_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_strength"}}` |
+| `minecraft:brewing/potion_long_swiftness_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/potion_long_swiftness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_swiftness"}}` |
+| `minecraft:brewing/potion_long_turtle_master_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_turtle_master"}}` |
+| `minecraft:brewing/potion_long_water_breathing_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_water_breathing"}}` |
+| `minecraft:brewing/potion_long_weakness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_weakness"}}` |
+| `minecraft:brewing/potion_mundane_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_night_vision_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:invisibility"}}` |
+| `minecraft:brewing/potion_night_vision_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:night_vision"}}` |
+| `minecraft:brewing/potion_night_vision_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_night_vision"}}` |
+| `minecraft:brewing/potion_oozing_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:oozing"}}` |
+| `minecraft:brewing/potion_poison_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/potion_poison_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_poison"}}` |
+| `minecraft:brewing/potion_poison_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:poison"}}` |
+| `minecraft:brewing/potion_poison_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_poison"}}` |
+| `minecraft:brewing/potion_regeneration_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_regeneration"}}` |
+| `minecraft:brewing/potion_regeneration_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:regeneration"}}` |
+| `minecraft:brewing/potion_regeneration_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_regeneration"}}` |
+| `minecraft:brewing/potion_slow_falling_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slow_falling"}}` |
+| `minecraft:brewing/potion_slow_falling_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slow_falling"}}` |
+| `minecraft:brewing/potion_slowness_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_slowness"}}` |
+| `minecraft:brewing/potion_slowness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/potion_slowness_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/potion_strength_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_strength"}}` |
+| `minecraft:brewing/potion_strength_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strength"}}` |
+| `minecraft:brewing/potion_strength_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_strength"}}` |
+| `minecraft:brewing/potion_strong_harming_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/potion_strong_healing_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/potion_strong_healing_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_healing"}}` |
+| `minecraft:brewing/potion_strong_leaping_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_leaping"}}` |
+| `minecraft:brewing/potion_strong_poison_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/potion_strong_poison_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_poison"}}` |
+| `minecraft:brewing/potion_strong_regeneration_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_regeneration"}}` |
+| `minecraft:brewing/potion_strong_slowness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_slowness"}}` |
+| `minecraft:brewing/potion_strong_strength_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_strength"}}` |
+| `minecraft:brewing/potion_strong_swiftness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_swiftness"}}` |
+| `minecraft:brewing/potion_strong_turtle_master_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_turtle_master"}}` |
+| `minecraft:brewing/potion_swiftness_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/potion_swiftness_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_swiftness"}}` |
+| `minecraft:brewing/potion_swiftness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:swiftness"}}` |
+| `minecraft:brewing/potion_swiftness_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_swiftness"}}` |
+| `minecraft:brewing/potion_thick_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:thick"}}` |
+| `minecraft:brewing/potion_turtle_master_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_turtle_master"}}` |
+| `minecraft:brewing/potion_turtle_master_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:turtle_master"}}` |
+| `minecraft:brewing/potion_turtle_master_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_turtle_master"}}` |
+| `minecraft:brewing/potion_water_blaze_powder` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_breathing_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water_breathing"}}` |
+| `minecraft:brewing/potion_water_breathing_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_water_breathing"}}` |
+| `minecraft:brewing/potion_water_breeze_rod` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_cobweb` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_fermented_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weakness"}}` |
+| `minecraft:brewing/potion_water_ghast_tear` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_glistering_melon_slice` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_glowstone_dust` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:thick"}}` |
+| `minecraft:brewing/potion_water_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water"}}` |
+| `minecraft:brewing/potion_water_magma_cream` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_nether_wart` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:awkward"}}` |
+| `minecraft:brewing/potion_water_rabbit_foot` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_slime_block` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_spider_eye` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_stone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_water_sugar` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/potion_weakness_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weakness"}}` |
+| `minecraft:brewing/potion_weakness_redstone` | `minecraft:brewing` | Potion (`minecraft:potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_weakness"}}` |
+| `minecraft:brewing/potion_weaving_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weaving"}}` |
+| `minecraft:brewing/potion_wind_charged_gunpowder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:wind_charged"}}` |
+| `minecraft:brewing/splash_potion_awkward_blaze_powder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strength"}}` |
+| `minecraft:brewing/splash_potion_awkward_breeze_rod` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:wind_charged"}}` |
+| `minecraft:brewing/splash_potion_awkward_cobweb` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weaving"}}` |
+| `minecraft:brewing/splash_potion_awkward_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:awkward"}}` |
+| `minecraft:brewing/splash_potion_awkward_ghast_tear` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:regeneration"}}` |
+| `minecraft:brewing/splash_potion_awkward_glistering_melon_slice` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:healing"}}` |
+| `minecraft:brewing/splash_potion_awkward_golden_carrot` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:night_vision"}}` |
+| `minecraft:brewing/splash_potion_awkward_magma_cream` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:fire_resistance"}}` |
+| `minecraft:brewing/splash_potion_awkward_phantom_membrane` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slow_falling"}}` |
+| `minecraft:brewing/splash_potion_awkward_pufferfish` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water_breathing"}}` |
+| `minecraft:brewing/splash_potion_awkward_rabbit_foot` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:leaping"}}` |
+| `minecraft:brewing/splash_potion_awkward_slime_block` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:oozing"}}` |
+| `minecraft:brewing/splash_potion_awkward_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:poison"}}` |
+| `minecraft:brewing/splash_potion_awkward_stone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:infested"}}` |
+| `minecraft:brewing/splash_potion_awkward_sugar` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:swiftness"}}` |
+| `minecraft:brewing/splash_potion_awkward_turtle_helmet` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:turtle_master"}}` |
+| `minecraft:brewing/splash_potion_fire_resistance_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:fire_resistance"}}` |
+| `minecraft:brewing/splash_potion_fire_resistance_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_fire_resistance"}}` |
+| `minecraft:brewing/splash_potion_harming_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/splash_potion_harming_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/splash_potion_healing_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:healing"}}` |
+| `minecraft:brewing/splash_potion_healing_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/splash_potion_healing_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_healing"}}` |
+| `minecraft:brewing/splash_potion_infested_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:infested"}}` |
+| `minecraft:brewing/splash_potion_invisibility_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:invisibility"}}` |
+| `minecraft:brewing/splash_potion_invisibility_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/splash_potion_leaping_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:leaping"}}` |
+| `minecraft:brewing/splash_potion_leaping_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/splash_potion_leaping_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_leaping"}}` |
+| `minecraft:brewing/splash_potion_leaping_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_leaping"}}` |
+| `minecraft:brewing/splash_potion_long_fire_resistance_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_fire_resistance"}}` |
+| `minecraft:brewing/splash_potion_long_invisibility_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/splash_potion_long_leaping_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_leaping"}}` |
+| `minecraft:brewing/splash_potion_long_leaping_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/splash_potion_long_night_vision_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_night_vision"}}` |
+| `minecraft:brewing/splash_potion_long_night_vision_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_invisibility"}}` |
+| `minecraft:brewing/splash_potion_long_poison_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_poison"}}` |
+| `minecraft:brewing/splash_potion_long_poison_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/splash_potion_long_regeneration_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_regeneration"}}` |
+| `minecraft:brewing/splash_potion_long_slow_falling_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slow_falling"}}` |
+| `minecraft:brewing/splash_potion_long_slowness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/splash_potion_long_strength_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_strength"}}` |
+| `minecraft:brewing/splash_potion_long_swiftness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_swiftness"}}` |
+| `minecraft:brewing/splash_potion_long_swiftness_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/splash_potion_long_turtle_master_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_turtle_master"}}` |
+| `minecraft:brewing/splash_potion_long_water_breathing_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_water_breathing"}}` |
+| `minecraft:brewing/splash_potion_long_weakness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_weakness"}}` |
+| `minecraft:brewing/splash_potion_mundane_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_night_vision_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:night_vision"}}` |
+| `minecraft:brewing/splash_potion_night_vision_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:invisibility"}}` |
+| `minecraft:brewing/splash_potion_night_vision_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_night_vision"}}` |
+| `minecraft:brewing/splash_potion_oozing_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:oozing"}}` |
+| `minecraft:brewing/splash_potion_poison_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:poison"}}` |
+| `minecraft:brewing/splash_potion_poison_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:harming"}}` |
+| `minecraft:brewing/splash_potion_poison_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_poison"}}` |
+| `minecraft:brewing/splash_potion_poison_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_poison"}}` |
+| `minecraft:brewing/splash_potion_regeneration_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:regeneration"}}` |
+| `minecraft:brewing/splash_potion_regeneration_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_regeneration"}}` |
+| `minecraft:brewing/splash_potion_regeneration_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_regeneration"}}` |
+| `minecraft:brewing/splash_potion_slow_falling_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slow_falling"}}` |
+| `minecraft:brewing/splash_potion_slow_falling_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slow_falling"}}` |
+| `minecraft:brewing/splash_potion_slowness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/splash_potion_slowness_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_slowness"}}` |
+| `minecraft:brewing/splash_potion_slowness_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_slowness"}}` |
+| `minecraft:brewing/splash_potion_strength_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strength"}}` |
+| `minecraft:brewing/splash_potion_strength_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_strength"}}` |
+| `minecraft:brewing/splash_potion_strength_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_strength"}}` |
+| `minecraft:brewing/splash_potion_strong_harming_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/splash_potion_strong_healing_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_healing"}}` |
+| `minecraft:brewing/splash_potion_strong_healing_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/splash_potion_strong_leaping_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_leaping"}}` |
+| `minecraft:brewing/splash_potion_strong_poison_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_poison"}}` |
+| `minecraft:brewing/splash_potion_strong_poison_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_harming"}}` |
+| `minecraft:brewing/splash_potion_strong_regeneration_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_regeneration"}}` |
+| `minecraft:brewing/splash_potion_strong_slowness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_slowness"}}` |
+| `minecraft:brewing/splash_potion_strong_strength_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_strength"}}` |
+| `minecraft:brewing/splash_potion_strong_swiftness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_swiftness"}}` |
+| `minecraft:brewing/splash_potion_strong_turtle_master_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_turtle_master"}}` |
+| `minecraft:brewing/splash_potion_swiftness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:swiftness"}}` |
+| `minecraft:brewing/splash_potion_swiftness_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:slowness"}}` |
+| `minecraft:brewing/splash_potion_swiftness_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_swiftness"}}` |
+| `minecraft:brewing/splash_potion_swiftness_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_swiftness"}}` |
+| `minecraft:brewing/splash_potion_thick_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:thick"}}` |
+| `minecraft:brewing/splash_potion_turtle_master_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:turtle_master"}}` |
+| `minecraft:brewing/splash_potion_turtle_master_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:strong_turtle_master"}}` |
+| `minecraft:brewing/splash_potion_turtle_master_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_turtle_master"}}` |
+| `minecraft:brewing/splash_potion_water_blaze_powder` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_breathing_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water_breathing"}}` |
+| `minecraft:brewing/splash_potion_water_breathing_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_water_breathing"}}` |
+| `minecraft:brewing/splash_potion_water_breeze_rod` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_cobweb` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:water"}}` |
+| `minecraft:brewing/splash_potion_water_fermented_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weakness"}}` |
+| `minecraft:brewing/splash_potion_water_ghast_tear` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_glistering_melon_slice` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_glowstone_dust` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:thick"}}` |
+| `minecraft:brewing/splash_potion_water_magma_cream` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_nether_wart` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:awkward"}}` |
+| `minecraft:brewing/splash_potion_water_rabbit_foot` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_slime_block` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_spider_eye` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_stone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_water_sugar` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:mundane"}}` |
+| `minecraft:brewing/splash_potion_weakness_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weakness"}}` |
+| `minecraft:brewing/splash_potion_weakness_redstone` | `minecraft:brewing` | Potion jetable (`minecraft:splash_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:long_weakness"}}` |
+| `minecraft:brewing/splash_potion_weaving_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:weaving"}}` |
+| `minecraft:brewing/splash_potion_wind_charged_dragon_breath` | `minecraft:brewing` | Potion persistante (`minecraft:lingering_potion`) ; composants : `{"minecraft:potion_contents":{"potion":"minecraft:wind_charged"}}` |
+| `minecraft:brewing_stand` | `minecraft:crafting_shaped` | Alambic (`minecraft:brewing_stand`) |
+| `minecraft:cauldron` | `minecraft:crafting_shaped` | Chaudron (`minecraft:cauldron`) |
+| `minecraft:fermented_spider_eye` | `minecraft:crafting_shapeless` | Oeil d'araignée fermenté (`minecraft:fermented_spider_eye`) |
+| `minecraft:glass_bottle` | `minecraft:crafting_shaped` | Fiole (`minecraft:glass_bottle`) × 3 |
+| `minecraft:glistering_melon_slice` | `minecraft:crafting_shaped` | Tranche de pastèque scintillante (`minecraft:glistering_melon_slice`) |
+| `minecraft:spectral_arrow` | `minecraft:crafting_shaped` | Flèche spectrale (`minecraft:spectral_arrow`) × 2 |
+| `minecraft:tipped_arrow` | `minecraft:crafting_imbue` | Flèche à effet (`minecraft:tipped_arrow`) × 8 |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:brewing_stand` | Alambic | Bloc et objet | 1 |
+| `minecraft:cauldron` | Chaudron | Bloc et objet | 1 |
+| `minecraft:dragon_breath` | Souffle de dragon | Objet | 0 |
+| `minecraft:fermented_spider_eye` | Oeil d'araignée fermenté | Objet | 1 |
+| `minecraft:glass_bottle` | Fiole | Objet | 1 |
+| `minecraft:glistering_melon_slice` | Tranche de pastèque scintillante | Objet | 1 |
+| `minecraft:lava_cauldron` | Chaudron de lave | Bloc sans objet homonyme | 0 |
+| `minecraft:lingering_potion` | Potion persistante | Objet | 108 |
+| `minecraft:potion` | Potion | Objet | 63 |
+| `minecraft:powder_snow_cauldron` | Chaudron de neige poudreuse | Bloc sans objet homonyme | 0 |
+| `minecraft:rabbit_foot` | Patte de lapin | Objet | 0 |
+| `minecraft:spectral_arrow` | Flèche spectrale | Objet | 1 |
+| `minecraft:spider_eye` | Oeil d'araignée | Objet | 0 |
+| `minecraft:splash_potion` | Potion jetable | Objet | 108 |
+| `minecraft:tipped_arrow` | Flèche à effet | Objet | 1 |
+| `minecraft:water_cauldron` | Chaudron d'eau | Bloc sans objet homonyme | 0 |
+
+## C56
+
+**Wither et balises · Wither and Beacons** — [déclencheurs et règles](collections-minecraft.md#c56).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:beacon` | `minecraft:crafting_shaped` | Balise (`minecraft:beacon`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:beacon` | Balise | Bloc et objet | 1 |
+| `minecraft:nether_star` | Étoile du Nether | Objet | 0 |
+| `minecraft:wither_rose` | Rose de Wither | Bloc et objet | 0 |
+| `minecraft:wither_skeleton_skull` | Crâne de Wither squelette | Bloc et objet | 0 |
+| `minecraft:wither_skeleton_wall_skull` | Crâne de Wither squelette mural | Bloc sans objet homonyme | 0 |
+
+## C57
+
+**Obsidiennes et réapparition · Obsidian and Respawning** — [déclencheurs et règles](collections-minecraft.md#c57).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:respawn_anchor` | `minecraft:crafting_shaped` | Ancre de réapparition (`minecraft:respawn_anchor`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:crying_obsidian` | Obsidienne pleureuse | Bloc et objet | 0 |
+| `minecraft:obsidian` | Obsidienne | Bloc et objet | 0 |
+| `minecraft:respawn_anchor` | Ancre de réapparition | Bloc et objet | 1 |
+
+## C58
+
+**Arpenteurs · Strider Riding** — [déclencheurs et règles](collections-minecraft.md#c58).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:warped_fungus_on_a_stick` | `minecraft:crafting_shaped` | Champignon biscornu sur un bâton (`minecraft:warped_fungus_on_a_stick`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:warped_fungus_on_a_stick` | Champignon biscornu sur un bâton | Objet | 1 |
+
+## C59
+
+**Ghasts joyeux · Happy Ghasts** — [déclencheurs et règles](collections-minecraft.md#c59).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_harness` | `minecraft:crafting_shaped` | Harnais noir (`minecraft:black_harness`) |
+| `minecraft:blue_harness` | `minecraft:crafting_shaped` | Harnais bleu (`minecraft:blue_harness`) |
+| `minecraft:brown_harness` | `minecraft:crafting_shaped` | Harnais marron (`minecraft:brown_harness`) |
+| `minecraft:cyan_harness` | `minecraft:crafting_shaped` | Harnais cyan (`minecraft:cyan_harness`) |
+| `minecraft:dried_ghast` | `minecraft:crafting_shaped` | Ghast desséché (`minecraft:dried_ghast`) |
+| `minecraft:dye_black_harness` | `minecraft:crafting_shapeless` | Harnais noir (`minecraft:black_harness`) |
+| `minecraft:dye_blue_harness` | `minecraft:crafting_shapeless` | Harnais bleu (`minecraft:blue_harness`) |
+| `minecraft:dye_brown_harness` | `minecraft:crafting_shapeless` | Harnais marron (`minecraft:brown_harness`) |
+| `minecraft:dye_cyan_harness` | `minecraft:crafting_shapeless` | Harnais cyan (`minecraft:cyan_harness`) |
+| `minecraft:dye_gray_harness` | `minecraft:crafting_shapeless` | Harnais gris (`minecraft:gray_harness`) |
+| `minecraft:dye_green_harness` | `minecraft:crafting_shapeless` | Harnais vert (`minecraft:green_harness`) |
+| `minecraft:dye_light_blue_harness` | `minecraft:crafting_shapeless` | Harnais bleu clair (`minecraft:light_blue_harness`) |
+| `minecraft:dye_light_gray_harness` | `minecraft:crafting_shapeless` | Harnais gris clair (`minecraft:light_gray_harness`) |
+| `minecraft:dye_lime_harness` | `minecraft:crafting_shapeless` | Harnais vert clair (`minecraft:lime_harness`) |
+| `minecraft:dye_magenta_harness` | `minecraft:crafting_shapeless` | Harnais magenta (`minecraft:magenta_harness`) |
+| `minecraft:dye_orange_harness` | `minecraft:crafting_shapeless` | Harnais orange (`minecraft:orange_harness`) |
+| `minecraft:dye_pink_harness` | `minecraft:crafting_shapeless` | Harnais rose (`minecraft:pink_harness`) |
+| `minecraft:dye_purple_harness` | `minecraft:crafting_shapeless` | Harnais violet (`minecraft:purple_harness`) |
+| `minecraft:dye_red_harness` | `minecraft:crafting_shapeless` | Harnais rouge (`minecraft:red_harness`) |
+| `minecraft:dye_white_harness` | `minecraft:crafting_shapeless` | Harnais blanc (`minecraft:white_harness`) |
+| `minecraft:dye_yellow_harness` | `minecraft:crafting_shapeless` | Harnais jaune (`minecraft:yellow_harness`) |
+| `minecraft:gray_harness` | `minecraft:crafting_shaped` | Harnais gris (`minecraft:gray_harness`) |
+| `minecraft:green_harness` | `minecraft:crafting_shaped` | Harnais vert (`minecraft:green_harness`) |
+| `minecraft:light_blue_harness` | `minecraft:crafting_shaped` | Harnais bleu clair (`minecraft:light_blue_harness`) |
+| `minecraft:light_gray_harness` | `minecraft:crafting_shaped` | Harnais gris clair (`minecraft:light_gray_harness`) |
+| `minecraft:lime_harness` | `minecraft:crafting_shaped` | Harnais vert clair (`minecraft:lime_harness`) |
+| `minecraft:magenta_harness` | `minecraft:crafting_shaped` | Harnais magenta (`minecraft:magenta_harness`) |
+| `minecraft:orange_harness` | `minecraft:crafting_shaped` | Harnais orange (`minecraft:orange_harness`) |
+| `minecraft:pink_harness` | `minecraft:crafting_shaped` | Harnais rose (`minecraft:pink_harness`) |
+| `minecraft:purple_harness` | `minecraft:crafting_shaped` | Harnais violet (`minecraft:purple_harness`) |
+| `minecraft:red_harness` | `minecraft:crafting_shaped` | Harnais rouge (`minecraft:red_harness`) |
+| `minecraft:white_harness` | `minecraft:crafting_shaped` | Harnais blanc (`minecraft:white_harness`) |
+| `minecraft:yellow_harness` | `minecraft:crafting_shaped` | Harnais jaune (`minecraft:yellow_harness`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_harness` | Harnais noir | Objet | 2 |
+| `minecraft:blue_harness` | Harnais bleu | Objet | 2 |
+| `minecraft:brown_harness` | Harnais marron | Objet | 2 |
+| `minecraft:cyan_harness` | Harnais cyan | Objet | 2 |
+| `minecraft:dried_ghast` | Ghast desséché | Bloc et objet | 1 |
+| `minecraft:ghast_tear` | Larme de Ghast | Objet | 0 |
+| `minecraft:gray_harness` | Harnais gris | Objet | 2 |
+| `minecraft:green_harness` | Harnais vert | Objet | 2 |
+| `minecraft:light_blue_harness` | Harnais bleu clair | Objet | 2 |
+| `minecraft:light_gray_harness` | Harnais gris clair | Objet | 2 |
+| `minecraft:lime_harness` | Harnais vert clair | Objet | 2 |
+| `minecraft:magenta_harness` | Harnais magenta | Objet | 2 |
+| `minecraft:orange_harness` | Harnais orange | Objet | 2 |
+| `minecraft:pink_harness` | Harnais rose | Objet | 2 |
+| `minecraft:purple_harness` | Harnais violet | Objet | 2 |
+| `minecraft:red_harness` | Harnais rouge | Objet | 2 |
+| `minecraft:white_harness` | Harnais blanc | Objet | 2 |
+| `minecraft:yellow_harness` | Harnais jaune | Objet | 2 |
+
+## C60
+
+**Architecture de l’End · End Architecture** — [déclencheurs et règles](collections-minecraft.md#c60).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:end_rod` | `minecraft:crafting_shaped` | Barre de l'End (`minecraft:end_rod`) × 4 |
+| `minecraft:end_stone_brick_slab` | `minecraft:crafting_shaped` | Dalle de pierre taillée de l'End (`minecraft:end_stone_brick_slab`) × 6 |
+| `minecraft:end_stone_brick_slab_from_end_stone_bricks_stonecutting` | `minecraft:stonecutting` | Dalle de pierre taillée de l'End (`minecraft:end_stone_brick_slab`) × 2 |
+| `minecraft:end_stone_brick_slab_from_end_stone_stonecutting` | `minecraft:stonecutting` | Dalle de pierre taillée de l'End (`minecraft:end_stone_brick_slab`) × 2 |
+| `minecraft:end_stone_brick_stairs` | `minecraft:crafting_shaped` | Escalier en pierre taillée de l'End (`minecraft:end_stone_brick_stairs`) × 4 |
+| `minecraft:end_stone_brick_stairs_from_end_stone_bricks_stonecutting` | `minecraft:stonecutting` | Escalier en pierre taillée de l'End (`minecraft:end_stone_brick_stairs`) |
+| `minecraft:end_stone_brick_stairs_from_end_stone_stonecutting` | `minecraft:stonecutting` | Escalier en pierre taillée de l'End (`minecraft:end_stone_brick_stairs`) |
+| `minecraft:end_stone_brick_wall` | `minecraft:crafting_shaped` | Muret de pierre taillée de l'End (`minecraft:end_stone_brick_wall`) × 6 |
+| `minecraft:end_stone_brick_wall_from_end_stone_bricks_stonecutting` | `minecraft:stonecutting` | Muret de pierre taillée de l'End (`minecraft:end_stone_brick_wall`) |
+| `minecraft:end_stone_brick_wall_from_end_stone_stonecutting` | `minecraft:stonecutting` | Muret de pierre taillée de l'End (`minecraft:end_stone_brick_wall`) |
+| `minecraft:end_stone_bricks` | `minecraft:crafting_shaped` | Pierre taillée de l'End (`minecraft:end_stone_bricks`) × 4 |
+| `minecraft:end_stone_bricks_from_end_stone_stonecutting` | `minecraft:stonecutting` | Pierre taillée de l'End (`minecraft:end_stone_bricks`) |
+| `minecraft:popped_chorus_fruit` | `minecraft:smelting` | Chorus éclaté (`minecraft:popped_chorus_fruit`) |
+| `minecraft:purpur_block` | `minecraft:crafting_shaped` | Bloc de purpur (`minecraft:purpur_block`) × 4 |
+| `minecraft:purpur_pillar` | `minecraft:crafting_shaped` | Pilier de purpur (`minecraft:purpur_pillar`) |
+| `minecraft:purpur_pillar_from_purpur_block_stonecutting` | `minecraft:stonecutting` | Pilier de purpur (`minecraft:purpur_pillar`) |
+| `minecraft:purpur_slab` | `minecraft:crafting_shaped` | Dalle de purpur (`minecraft:purpur_slab`) × 6 |
+| `minecraft:purpur_slab_from_purpur_block_stonecutting` | `minecraft:stonecutting` | Dalle de purpur (`minecraft:purpur_slab`) × 2 |
+| `minecraft:purpur_stairs` | `minecraft:crafting_shaped` | Escalier en purpur (`minecraft:purpur_stairs`) × 4 |
+| `minecraft:purpur_stairs_from_purpur_block_stonecutting` | `minecraft:stonecutting` | Escalier en purpur (`minecraft:purpur_stairs`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:chorus_flower` | Fleur de chorus | Bloc et objet | 0 |
+| `minecraft:chorus_fruit` | Chorus | Objet | 0 |
+| `minecraft:chorus_plant` | Plante de chorus | Bloc et objet | 0 |
+| `minecraft:end_rod` | Barre de l'End | Bloc et objet | 1 |
+| `minecraft:end_stone` | Pierre de l'End | Bloc et objet | 0 |
+| `minecraft:end_stone_brick_slab` | Dalle de pierre taillée de l'End | Bloc et objet | 3 |
+| `minecraft:end_stone_brick_stairs` | Escalier en pierre taillée de l'End | Bloc et objet | 3 |
+| `minecraft:end_stone_brick_wall` | Muret de pierre taillée de l'End | Bloc et objet | 3 |
+| `minecraft:end_stone_bricks` | Pierre taillée de l'End | Bloc et objet | 2 |
+| `minecraft:popped_chorus_fruit` | Chorus éclaté | Objet | 1 |
+| `minecraft:purpur_block` | Bloc de purpur | Bloc et objet | 1 |
+| `minecraft:purpur_pillar` | Pilier de purpur | Bloc et objet | 2 |
+| `minecraft:purpur_slab` | Dalle de purpur | Bloc et objet | 2 |
+| `minecraft:purpur_stairs` | Escalier en purpur | Bloc et objet | 2 |
+
+## C61
+
+**Boîtes de Shulker · Shulker Storage** — [déclencheurs et règles](collections-minecraft.md#c61).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:black_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker noire (`minecraft:black_shulker_box`) |
+| `minecraft:blue_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker bleue (`minecraft:blue_shulker_box`) |
+| `minecraft:brown_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker marron (`minecraft:brown_shulker_box`) |
+| `minecraft:cyan_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker cyan (`minecraft:cyan_shulker_box`) |
+| `minecraft:gray_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker grise (`minecraft:gray_shulker_box`) |
+| `minecraft:green_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker verte (`minecraft:green_shulker_box`) |
+| `minecraft:light_blue_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker bleu clair (`minecraft:light_blue_shulker_box`) |
+| `minecraft:light_gray_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker gris clair (`minecraft:light_gray_shulker_box`) |
+| `minecraft:lime_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker vert clair (`minecraft:lime_shulker_box`) |
+| `minecraft:magenta_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker magenta (`minecraft:magenta_shulker_box`) |
+| `minecraft:orange_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker orange (`minecraft:orange_shulker_box`) |
+| `minecraft:pink_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker rose (`minecraft:pink_shulker_box`) |
+| `minecraft:purple_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker violette (`minecraft:purple_shulker_box`) |
+| `minecraft:red_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker rouge (`minecraft:red_shulker_box`) |
+| `minecraft:shulker_box` | `minecraft:crafting_shaped` | Boîte de Shulker (`minecraft:shulker_box`) |
+| `minecraft:white_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker blanche (`minecraft:white_shulker_box`) |
+| `minecraft:yellow_shulker_box` | `minecraft:crafting_transmute` | Boîte de Shulker jaune (`minecraft:yellow_shulker_box`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:black_shulker_box` | Boîte de Shulker noire | Bloc et objet | 1 |
+| `minecraft:blue_shulker_box` | Boîte de Shulker bleue | Bloc et objet | 1 |
+| `minecraft:brown_shulker_box` | Boîte de Shulker marron | Bloc et objet | 1 |
+| `minecraft:cyan_shulker_box` | Boîte de Shulker cyan | Bloc et objet | 1 |
+| `minecraft:gray_shulker_box` | Boîte de Shulker grise | Bloc et objet | 1 |
+| `minecraft:green_shulker_box` | Boîte de Shulker verte | Bloc et objet | 1 |
+| `minecraft:light_blue_shulker_box` | Boîte de Shulker bleu clair | Bloc et objet | 1 |
+| `minecraft:light_gray_shulker_box` | Boîte de Shulker gris clair | Bloc et objet | 1 |
+| `minecraft:lime_shulker_box` | Boîte de Shulker vert clair | Bloc et objet | 1 |
+| `minecraft:magenta_shulker_box` | Boîte de Shulker magenta | Bloc et objet | 1 |
+| `minecraft:orange_shulker_box` | Boîte de Shulker orange | Bloc et objet | 1 |
+| `minecraft:pink_shulker_box` | Boîte de Shulker rose | Bloc et objet | 1 |
+| `minecraft:purple_shulker_box` | Boîte de Shulker violette | Bloc et objet | 1 |
+| `minecraft:red_shulker_box` | Boîte de Shulker rouge | Bloc et objet | 1 |
+| `minecraft:shulker_box` | Boîte de Shulker | Bloc et objet | 1 |
+| `minecraft:shulker_shell` | Carapace de Shulker | Objet | 0 |
+| `minecraft:white_shulker_box` | Boîte de Shulker blanche | Bloc et objet | 1 |
+| `minecraft:yellow_shulker_box` | Boîte de Shulker jaune | Bloc et objet | 1 |
+
+## C62
+
+**Dragon et cristaux · Dragon and Crystals** — [déclencheurs et règles](collections-minecraft.md#c62).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:end_crystal` | `minecraft:crafting_shaped` | Cristal de l'End (`minecraft:end_crystal`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:dragon_egg` | Oeuf de dragon | Bloc et objet | 0 |
+| `minecraft:dragon_head` | Tête de dragon | Bloc et objet | 0 |
+| `minecraft:dragon_wall_head` | Tête de dragon murale | Bloc sans objet homonyme | 0 |
+| `minecraft:end_crystal` | Cristal de l'End | Objet | 1 |
+
+## C63
+
+**Élytres et grand voyage · Elytra Flight** — [déclencheurs et règles](collections-minecraft.md#c63).
+
+### Recettes
+
+Aucune recette JSON primaire. Cette collection présente des fiches ou des renvois ; elle ne crée pas de recette.
+
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:elytra` | Élytres | Objet | 0 |
+| `minecraft:phantom_membrane` | Membrane de Phantom | Objet | 0 |
+
+## C64
+
+**Trophées et butins · Trophies and Loot** — [déclencheurs et règles](collections-minecraft.md#c64).
+
+### Recettes
+
+Aucune recette JSON primaire. Cette collection présente des fiches ou des renvois ; elle ne crée pas de recette.
+
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:creeper_head` | Tête de Creeper | Bloc et objet | 0 |
+| `minecraft:creeper_wall_head` | Tête de Creeper murale | Bloc sans objet homonyme | 0 |
+| `minecraft:piglin_head` | Tête de Piglin | Bloc et objet | 0 |
+| `minecraft:piglin_wall_head` | Tête de Piglin murale | Bloc sans objet homonyme | 0 |
+| `minecraft:player_head` | Tête de joueur | Bloc et objet | 0 |
+| `minecraft:player_wall_head` | Tête de joueur murale | Bloc sans objet homonyme | 0 |
+| `minecraft:skeleton_skull` | Crâne de squelette | Bloc et objet | 0 |
+| `minecraft:skeleton_wall_skull` | Crâne de squelette mural | Bloc sans objet homonyme | 0 |
+| `minecraft:totem_of_undying` | Totem d'immortalité | Objet | 0 |
+| `minecraft:zombie_head` | Tête de zombie | Bloc et objet | 0 |
+| `minecraft:zombie_wall_head` | Tête de zombie murale | Bloc sans objet homonyme | 0 |
+
+## C65
+
+**Blocs et objets techniques · Technical Catalogue** — [déclencheurs et règles](collections-minecraft.md#c65).
+
+### Recettes
+
+Aucune recette JSON primaire. Cette collection présente des fiches ou des renvois ; elle ne crée pas de recette.
+
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:air` | Air | Bloc et objet | 0 |
+| `minecraft:allay_spawn_egg` | Oeuf d'apparition d'Allay | Objet | 0 |
+| `minecraft:armadillo_spawn_egg` | Oeuf d'apparition de tatou | Objet | 0 |
+| `minecraft:axolotl_spawn_egg` | Oeuf d'apparition d'axolotl | Objet | 0 |
+| `minecraft:barrier` | Barrière invisible | Bloc et objet | 0 |
+| `minecraft:bat_spawn_egg` | Oeuf d'apparition de chauve-souris | Objet | 0 |
+| `minecraft:bedrock` | Bedrock | Bloc et objet | 0 |
+| `minecraft:bee_spawn_egg` | Oeuf d'apparition d'abeille | Objet | 0 |
+| `minecraft:blaze_spawn_egg` | Oeuf d'apparition de Blaze | Objet | 0 |
+| `minecraft:bogged_spawn_egg` | Oeuf d'apparition d'embourbé | Objet | 0 |
+| `minecraft:breeze_spawn_egg` | Oeuf d'apparition de Breeze | Objet | 0 |
+| `minecraft:bubble_column` | Colonne de bulles | Bloc sans objet homonyme | 0 |
+| `minecraft:camel_husk_spawn_egg` | Oeuf d'apparition de dromadaire momifié | Objet | 0 |
+| `minecraft:camel_spawn_egg` | Oeuf d'apparition de dromadaire | Objet | 0 |
+| `minecraft:cat_spawn_egg` | Oeuf d'apparition de chat | Objet | 0 |
+| `minecraft:cave_air` | Air de caverne | Bloc sans objet homonyme | 0 |
+| `minecraft:cave_spider_spawn_egg` | Oeuf d'apparition d'araignée venimeuse | Objet | 0 |
+| `minecraft:chain_command_block` | Bloc de commande en chaîne | Bloc et objet | 0 |
+| `minecraft:chicken_spawn_egg` | Oeuf d'apparition de poule | Objet | 0 |
+| `minecraft:cod_spawn_egg` | Oeuf d'apparition de morue | Objet | 0 |
+| `minecraft:command_block` | Bloc de commande | Bloc et objet | 0 |
+| `minecraft:copper_golem_spawn_egg` | Oeuf d'apparition de golem de cuivre | Objet | 0 |
+| `minecraft:cow_spawn_egg` | Oeuf d'apparition de vache | Objet | 0 |
+| `minecraft:creaking_spawn_egg` | Oeuf d'apparition de grinceur | Objet | 0 |
+| `minecraft:creeper_spawn_egg` | Oeuf d'apparition de Creeper | Objet | 0 |
+| `minecraft:debug_stick` | Bâton de débogage | Objet | 0 |
+| `minecraft:dolphin_spawn_egg` | Oeuf d'apparition de dauphin | Objet | 0 |
+| `minecraft:donkey_spawn_egg` | Oeuf d'apparition d'âne | Objet | 0 |
+| `minecraft:drowned_spawn_egg` | Oeuf d'apparition de noyé | Objet | 0 |
+| `minecraft:elder_guardian_spawn_egg` | Oeuf d'apparition de grand gardien | Objet | 0 |
+| `minecraft:end_gateway` | Passerelle de l'End | Bloc sans objet homonyme | 0 |
+| `minecraft:end_portal` | Portail de l'End | Bloc sans objet homonyme | 0 |
+| `minecraft:end_portal_frame` | Cadre de portail de l'End | Bloc et objet | 0 |
+| `minecraft:ender_dragon_spawn_egg` | Oeuf d'apparition d'Ender Dragon | Objet | 0 |
+| `minecraft:enderman_spawn_egg` | Oeuf d'apparition d'Enderman | Objet | 0 |
+| `minecraft:endermite_spawn_egg` | Oeuf d'apparition d'Endermite | Objet | 0 |
+| `minecraft:evoker_spawn_egg` | Oeuf d'apparition d'évocateur | Objet | 0 |
+| `minecraft:fox_spawn_egg` | Oeuf d'apparition de renard | Objet | 0 |
+| `minecraft:frog_spawn_egg` | Oeuf d'apparition de grenouille | Objet | 0 |
+| `minecraft:ghast_spawn_egg` | Oeuf d'apparition de Ghast | Objet | 0 |
+| `minecraft:glow_squid_spawn_egg` | Oeuf d'apparition de poulpe luisant | Objet | 0 |
+| `minecraft:goat_spawn_egg` | Oeuf d'apparition de chèvre | Objet | 0 |
+| `minecraft:guardian_spawn_egg` | Oeuf d'apparition de gardien | Objet | 0 |
+| `minecraft:happy_ghast_spawn_egg` | Oeuf d'apparition de Ghast joyeux | Objet | 0 |
+| `minecraft:hoglin_spawn_egg` | Oeuf d'apparition de Hoglin | Objet | 0 |
+| `minecraft:horse_spawn_egg` | Oeuf d'apparition de cheval | Objet | 0 |
+| `minecraft:husk_spawn_egg` | Oeuf d'apparition de momifié | Objet | 0 |
+| `minecraft:iron_golem_spawn_egg` | Oeuf d'apparition de golem de fer | Objet | 0 |
+| `minecraft:jigsaw` | Bloc de puzzle | Bloc et objet | 0 |
+| `minecraft:knowledge_book` | Livre des connaissances | Objet | 0 |
+| `minecraft:lava` | Lave | Bloc sans objet homonyme | 0 |
+| `minecraft:light` | Lumière | Bloc et objet | 0 |
+| `minecraft:llama_spawn_egg` | Oeuf d'apparition de lama | Objet | 0 |
+| `minecraft:magma_cube_spawn_egg` | Oeuf d'apparition de cube de magma | Objet | 0 |
+| `minecraft:mooshroom_spawn_egg` | Oeuf d'apparition de Mooshroom | Objet | 0 |
+| `minecraft:mule_spawn_egg` | Oeuf d'apparition de mule | Objet | 0 |
+| `minecraft:nautilus_spawn_egg` | Oeuf d'apparition de nautile | Objet | 0 |
+| `minecraft:nether_portal` | Portail du Nether | Bloc sans objet homonyme | 0 |
+| `minecraft:ocelot_spawn_egg` | Oeuf d'apparition d'ocelot | Objet | 0 |
+| `minecraft:panda_spawn_egg` | Oeuf d'apparition de panda | Objet | 0 |
+| `minecraft:parched_spawn_egg` | Oeuf d'apparition de desséché | Objet | 0 |
+| `minecraft:parrot_spawn_egg` | Oeuf d'apparition de perroquet | Objet | 0 |
+| `minecraft:phantom_spawn_egg` | Oeuf d'apparition de Phantom | Objet | 0 |
+| `minecraft:pig_spawn_egg` | Oeuf d'apparition de cochon | Objet | 0 |
+| `minecraft:piglin_brute_spawn_egg` | Oeuf d'apparition de Piglin barbare | Objet | 0 |
+| `minecraft:piglin_spawn_egg` | Oeuf d'apparition de Piglin | Objet | 0 |
+| `minecraft:pillager_spawn_egg` | Oeuf d'apparition de pillard | Objet | 0 |
+| `minecraft:polar_bear_spawn_egg` | Oeuf d'apparition d'ours blanc | Objet | 0 |
+| `minecraft:pufferfish_spawn_egg` | Oeuf d'apparition de poisson-globe | Objet | 0 |
+| `minecraft:rabbit_spawn_egg` | Oeuf d'apparition de lapin | Objet | 0 |
+| `minecraft:ravager_spawn_egg` | Oeuf d'apparition de ravageur | Objet | 0 |
+| `minecraft:repeating_command_block` | Bloc de commande à répétition | Bloc et objet | 0 |
+| `minecraft:salmon_spawn_egg` | Oeuf d'apparition de saumon | Objet | 0 |
+| `minecraft:sheep_spawn_egg` | Oeuf d'apparition de mouton | Objet | 0 |
+| `minecraft:shulker_spawn_egg` | Oeuf d'apparition de Shulker | Objet | 0 |
+| `minecraft:silverfish_spawn_egg` | Oeuf d'apparition de poisson d'argent | Objet | 0 |
+| `minecraft:skeleton_horse_spawn_egg` | Oeuf d'apparition de cheval-squelette | Objet | 0 |
+| `minecraft:skeleton_spawn_egg` | Oeuf d'apparition de squelette | Objet | 0 |
+| `minecraft:slime_spawn_egg` | Oeuf d'apparition de Slime | Objet | 0 |
+| `minecraft:sniffer_spawn_egg` | Oeuf d'apparition de renifleur | Objet | 0 |
+| `minecraft:snow_golem_spawn_egg` | Oeuf d'apparition de golem de neige | Objet | 0 |
+| `minecraft:spawner` | Générateur de créatures | Bloc et objet | 0 |
+| `minecraft:spider_spawn_egg` | Oeuf d'apparition d'araignée | Objet | 0 |
+| `minecraft:squid_spawn_egg` | Oeuf d'apparition de poulpe | Objet | 0 |
+| `minecraft:stray_spawn_egg` | Oeuf d'apparition de vagabond | Objet | 0 |
+| `minecraft:strider_spawn_egg` | Oeuf d'apparition d'arpenteur | Objet | 0 |
+| `minecraft:structure_block` | Bloc de structure | Bloc et objet | 0 |
+| `minecraft:structure_void` | Vide de structure | Bloc et objet | 0 |
+| `minecraft:sulfur_cube_spawn_egg` | Oeuf d'apparition de cube de soufre | Objet | 0 |
+| `minecraft:tadpole_spawn_egg` | Oeuf d'apparition de têtard | Objet | 0 |
+| `minecraft:test_block` | Bloc de test | Bloc et objet | 0 |
+| `minecraft:test_instance_block` | Bloc d'instance de test | Bloc et objet | 0 |
+| `minecraft:trader_llama_spawn_egg` | Oeuf d'apparition de lama de marchand | Objet | 0 |
+| `minecraft:tropical_fish_spawn_egg` | Oeuf d'apparition de poisson tropical | Objet | 0 |
+| `minecraft:turtle_spawn_egg` | Oeuf d'apparition de tortue | Objet | 0 |
+| `minecraft:vex_spawn_egg` | Oeuf d'apparition de Vex | Objet | 0 |
+| `minecraft:villager_spawn_egg` | Oeuf d'apparition de villageois | Objet | 0 |
+| `minecraft:vindicator_spawn_egg` | Oeuf d'apparition de vindicateur | Objet | 0 |
+| `minecraft:void_air` | Air du vide | Bloc sans objet homonyme | 0 |
+| `minecraft:wandering_trader_spawn_egg` | Oeuf d'apparition de marchand ambulant | Objet | 0 |
+| `minecraft:warden_spawn_egg` | Oeuf d'apparition de Warden | Objet | 0 |
+| `minecraft:water` | Eau | Bloc sans objet homonyme | 0 |
+| `minecraft:witch_spawn_egg` | Oeuf d'apparition de sorcière | Objet | 0 |
+| `minecraft:wither_skeleton_spawn_egg` | Oeuf d'apparition de Wither squelette | Objet | 0 |
+| `minecraft:wither_spawn_egg` | Oeuf d'apparition de Wither | Objet | 0 |
+| `minecraft:wolf_spawn_egg` | Oeuf d'apparition de loup | Objet | 0 |
+| `minecraft:zoglin_spawn_egg` | Oeuf d'apparition de Zoglin | Objet | 0 |
+| `minecraft:zombie_horse_spawn_egg` | Oeuf d'apparition de cheval-zombie | Objet | 0 |
+| `minecraft:zombie_nautilus_spawn_egg` | Oeuf d'apparition de nautile-zombie | Objet | 0 |
+| `minecraft:zombie_spawn_egg` | Oeuf d'apparition de zombie | Objet | 0 |
+| `minecraft:zombie_villager_spawn_egg` | Oeuf d'apparition de zombie-villageois | Objet | 0 |
+| `minecraft:zombified_piglin_spawn_egg` | Oeuf d'apparition de Piglin zombifié | Objet | 0 |
+
+## C66
+
+**Villages et échanges · Villages and Trading** — [déclencheurs et règles](collections-minecraft.md#c66).
+
+### Recettes
+
+| Recette | Type / sérialiseur | Résultat |
+| --- | --- | --- |
+| `minecraft:emerald` | `minecraft:crafting_shapeless` | Émeraude (`minecraft:emerald`) × 9 |
+| `minecraft:emerald_block` | `minecraft:crafting_shaped` | Bloc d'émeraude (`minecraft:emerald_block`) |
+| `minecraft:emerald_from_blasting_deepslate_emerald_ore` | `minecraft:blasting` | Émeraude (`minecraft:emerald`) |
+| `minecraft:emerald_from_blasting_emerald_ore` | `minecraft:blasting` | Émeraude (`minecraft:emerald`) |
+| `minecraft:emerald_from_smelting_deepslate_emerald_ore` | `minecraft:smelting` | Émeraude (`minecraft:emerald`) |
+| `minecraft:emerald_from_smelting_emerald_ore` | `minecraft:smelting` | Émeraude (`minecraft:emerald`) |
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:bell` | Cloche | Bloc et objet | 0 |
+| `minecraft:deepslate_emerald_ore` | Minerai d'émeraude des abîmes | Bloc et objet | 0 |
+| `minecraft:emerald` | Émeraude | Objet | 5 |
+| `minecraft:emerald_block` | Bloc d'émeraude | Bloc et objet | 1 |
+| `minecraft:emerald_ore` | Minerai d'émeraude | Bloc et objet | 0 |
+
+## C67
+
+**Grenouilles et grelampes · Frogs and Froglights** — [déclencheurs et règles](collections-minecraft.md#c67).
+
+### Recettes
+
+Aucune recette JSON primaire. Cette collection présente des fiches ou des renvois ; elle ne crée pas de recette.
+
+
+### Blocs et objets
+
+| Identifiant | Nom FR | Présence | Recettes à sortie explicite |
+| --- | --- | --- | ---: |
+| `minecraft:frogspawn` | Oeufs de grenouille | Bloc et objet | 0 |
+| `minecraft:ochre_froglight` | Grelampe ocrée | Bloc et objet | 0 |
+| `minecraft:pearlescent_froglight` | Grelampe nacrée | Bloc et objet | 0 |
+| `minecraft:verdant_froglight` | Grelampe verdoyante | Bloc et objet | 0 |
+
+## Contrôle de couverture
+
+Les totaux de collections comptent exactement 2 042 identifiants de recette, sans doublon, et 1 815 identifiants distincts de blocs/objets, sans oubli. Les 1 129 identifiants présents dans les deux registres sont étiquetés « Bloc et objet ». Les 157 identifiants propres aux blocs et les 529 identifiants propres aux objets complètent cet ensemble.
+
+Les types affichés sont les champs `type` des JSON, donc leurs sérialiseurs. Ils ne doivent pas être recopiés aveuglément dans `recipe_types` de Sanctuary, qui attend le type de recette du registre serveur. Une famille d’artisanat doit utiliser une liste d’identifiants explicites.
+
+[Provenance, empreinte de l’archive et limites de la vérification](collections-minecraft.md#sources-et-vérification).
diff --git a/docs/collections-minecraft.md b/docs/collections-minecraft.md
new file mode 100644
index 0000000..25b6295
--- /dev/null
+++ b/docs/collections-minecraft.md
@@ -0,0 +1,1621 @@
+# Collections Minecraft — advancements, blocs, objets et recettes
+
+**Proposition de conception pour Sanctuary · 14 septembre 2026 · Minecraft Java 26.3-pre-2**
+
+L’idée : **chaque advancement accompli enseigne les recettes d’une ou plusieurs collections thématiques**. Obtenir le progrès du fer ouvre les plans de forge ; découvrir une chambre des épreuves ouvre les plans de la masse et des charges de vent ; atteindre une cité de l’End ouvre les boîtes de Shulker. Le joueur apprend des plans, puis réunit leurs ingrédients pour les fabriquer.
+
+Ce document propose **67 collections**, dont **66 familles de jeu et une collection technique réservée aux opérateurs**. Les collections sont une organisation proposée pour Sanctuary, pas une liste officielle de collections déjà fournie par Minecraft. Les noms FR/EN sont proposés pour leur future interface ; les noms d’advancements viennent des traductions du jeu.
+
+**État : définitions de recettes intégrées en beta.035.** Les 67 familles et leurs déclencheurs sont matérialisés dans les données serveur, sur le mécanisme beta.024. `sanctuary:basic_wood` et `sanctuary:brewing` restent stables. Les fiches détaillées d’obtention et le musée visuel des blocs restent des propositions ; leur présence ici ne les rend pas jouables. Voir le [contrat et les vérifications beta.035](collections-beta035.md).
+
+> **Mise en œuvre beta.035 :** les définitions serveur sont matérialisées depuis
+> ce document et son annexe. Le [ticket d’intégration](collections-beta035.md)
+> précise la reprise des habitants, la compatibilité et les vérifications.
+
+## Comment lire ce document
+
+- [Règles de déblocage](#règles-de-déblocage) : ce que le joueur reçoit et comment éviter les blocages.
+- [Catalogue des 67 collections](#catalogue-des-collections) : déclencheurs, contenu et limites de chaque famille.
+- [Correspondance des 126 advancements](#correspondance-des-advancements) : chaque progrès vanilla a sa ligne.
+- [Variantes et cas particuliers](#variantes-et-cas-particuliers) : essences, couleurs, recettes spéciales et objets à trouver.
+- [Intégration proposée](#intégration-proposée) : articulation avec beta.024 et critères d’acceptation.
+- [Inventaire exhaustif](collections-minecraft-inventaire.md) : chaque recette, chaque identifiant de bloc et d’objet, avec son classement principal.
+- [Sources et vérification](#sources-et-vérification) : version exacte, méthode et empreinte de l’archive étudiée.
+
+## Périmètre vérifié
+
+| Donnée de Minecraft Java 26.3-pre-2 | Nombre | Lecture |
+| --- | ---: | --- |
+| Advancements de progression | 126 | Minecraft 16, Aventure 47, Agriculture 31, Nether 23, End 9 ; racines incluses |
+| Définitions d’advancements sous `recipes/` | 1 740 | Inclut la racine technique ; ce ne sont pas 1 740 exploits visibles supplémentaires |
+| Recettes JSON | 2 042 | Toutes classées ; dont 279 recettes d’alchimie |
+| Identifiants d’objets | 1 658 | Inclut objets techniques, œufs d’apparition et `air` ; pas un nombre d’objets à collectionner en survie |
+| Identifiants de blocs | 1 286 | Inclut fluides, blocs techniques et variantes sans objet distinct |
+| Identifiants distincts blocs/objets réunis | 1 815 | Les 1 129 identifiants communs aux deux registres ne sont comptés qu’une fois |
+
+« Toutes les collections » signifie ici : tous les advancements de progression, toutes les recettes JSON, tous les identifiants de blocs et d’objets de cette version ont une place dans le catalogue. Les états de blocs, enchantements, couleurs de potions, motifs de bannières, contenus de coffres et autres combinaisons de composants ne sont pas énumérés comme des milliers d’objets séparés. Les contenus de mods autonomes auront leur propre catalogue.
+
+La version est volontairement celle du pack, **26.3-pre-2**, sans supposer la compatibilité avec Java 26.2, une autre préversion ou Bedrock. Les mondes Sanctuary peuvent ne pas donner accès à tous les biomes ou dimensions nécessaires ; une collection n’autorise aucune expansion du monde.
+
+## Règles de déblocage
+
+### Le déblocage enseigne une famille de plans
+
+1. Le serveur constate un advancement terminé ou une première découverte admissible.
+2. Il ouvre les collections associées et enseigne leurs recettes présentes sur ce serveur.
+3. Le carnet conserve ces connaissances, même avant l’achat de **Catalogue de recettes**, toujours à **4 niveaux** selon beta.024.
+4. Le catalogue montre le résultat, les ingrédients et le poste de travail. Aucun matériau n’est donné, aucune possession Blocodex n’est simulée.
+5. La mort, le prestige et la reconnexion conservent les connaissances selon le contrat existant.
+
+L’advancement **suffit à lui seul** : posséder ensuite tous les ingrédients n’est pas une seconde condition de révélation. Un plan révélé peut donc demander un matériau encore inconnu ; apprendre le plan ne compte pas comme découvrir ou posséder ce matériau. La fabrication manuelle reste libre et les récompenses natives de recettes continuent de fonctionner.
+
+### Deux portes d’entrée complémentaires
+
+**A — Advancement.** Toute ligne du tableau des 126 advancements déclenche les collections indiquées. Si plusieurs advancements sont associés à une collection, chacun suffit : c’est un **OU**, pas une chaîne de conditions obligatoires. Le parent d’un advancement n’est pas une permission supplémentaire inventée par Sanctuary.
+
+**D — Découverte.** Pour les collections de survie, obtenir un objet de leur famille, ou un objet d’amorce indiqué dans leur fiche, peut également ouvrir leurs plans. « Objet de la famille » désigne son classement principal dans l’inventaire annexe, limité aux objets accessibles au joueur ; les états de blocs sans objet et le catalogue technique ne deviennent pas des amorces de survie. Les exemples d’amorces couvrent notamment les ingrédients nécessaires avant le premier exploit.
+
+Cette seconde porte est nécessaire pour le bois, les colorants, la redstone ou la verrerie, qui ne possèdent pas chacun un advancement précoce dédié. Aucun nouvel advancement Sanctuary n’est requis pour ce catalogue de départ.
+
+### Un exploit tardif complète le carnet
+
+Plusieurs advancements nécessitent déjà l’objet qu’ils mettent à l’honneur. Une première laine enseigne donc le lit **avant** « Bonne nuit les petits » ; un bâton de Blaze enseigne l’alambic **avant** « Apprenti chimiste » ; la redstone enseigne le fabricateur **avant** « Méta-fabrication ».
+
+Si l’exploit arrive ensuite, il ajoute seulement les plans encore inconnus. S’il n’en reste aucun, le jeu peut simplement signaler l’exploit, sans annoncer un faux gain de recettes. Aucun défi long comme « Une alimentation équilibrée » ou « Comment en est-on arrivé là ? » ne doit retenir les plans de base jusqu’à la fin de partie.
+
+### Une collection suit son sujet, pas tous ses ingrédients
+
+Une planche ouvre la menuiserie, pas automatiquement les pistons, boucliers, jukebox et arbalètes. Chaque recette reçoit **un classement principal** dans l’annexe. Un advancement peut ouvrir plusieurs collections et une interface peut ajouter des renvois, mais le nombre de nouvelles recettes compte les identifiants uniques.
+
+Les recettes alternatives d’un même résultat restent distinctes : four, haut fourneau, fumoir, feu de camp, établi, tailleur de pierre, forge et alambic. Le compteur de recettes n’est donc ni un nombre d’objets ni un nombre de combinaisons possibles.
+
+Les collections ne contenant aucune recette primaire servent de fiches de découverte ou de renvois. Elles ne promettent aucun craft inédit. Leur future interface est une fonctionnalité supplémentaire : le chargeur actuel de collections apprend des recettes et ne construit pas à lui seul un musée de fiches.
+
+## Catalogue des collections
+
+Les codes **C01–C67** sont des repères éditoriaux. Chaque fiche donne un nom FR/EN, les voies d’ouverture et le contenu proposé. Les identifiants `sanctuary:*` proposés sont séparés des identifiants vanilla. Les 2 042 recettes détaillées de l’annexe définissent le périmètre primaire de chaque collection ; les résumés ci-dessous expliquent aussi les gestes du monde et les renvois.
+
+
+| Collection | Recettes primaires | Blocs/objets distincts |
+| --- | ---: | ---: |
+| [C01 — Bois et menuiserie](#c01) | 170 | 214 |
+| [C02 — Pierre et premiers outils](#c02) | 57 | 37 |
+| [C03 — Granite, diorite et andésite](#c03) | 45 | 21 |
+| [C04 — Ardoise des profondeurs](#c04) | 73 | 22 |
+| [C05 — Tuf](#c05) | 38 | 14 |
+| [C06 — Argile et briques](#c06) | 10 | 8 |
+| [C07 — Sables et grès](#c07) | 38 | 25 |
+| [C08 — Verre et vitrages](#c08) | 50 | 34 |
+| [C09 — Teintures](#c09) | 50 | 22 |
+| [C10 — Laine, literie et coussins](#c10) | 178 | 97 |
+| [C11 — Terres cuites](#c11) | 33 | 33 |
+| [C12 — Bétons](#c12) | 80 | 64 |
+| [C13 — Sols et mousses](#c13) | 13 | 22 |
+| [C14 — Neige et glace](#c14) | 4 | 9 |
+| [C15 — Améthyste et optique](#c15) | 3 | 10 |
+| [C16 — Spéléothèmes](#c16) | 1 | 2 |
+| [C17 — Soufre](#c17) | 36 | 16 |
+| [C18 — Cinabre](#c18) | 34 | 13 |
+| [C19 — Cuivre et chaudronnerie](#c19) | 89 | 78 |
+| [C20 — Cuivre ciré](#c20) | 116 | 60 |
+| [C21 — Fer et forge de base](#c21) | 34 | 35 |
+| [C22 — Or et dorure](#c22) | 26 | 17 |
+| [C23 — Diamants](#c23) | 16 | 14 |
+| [C24 — Netherite](#c24) | 18 | 17 |
+| [C25 — Charbon, feu et éclairage](#c25) | 28 | 45 |
+| [C26 — Redstone et mesure](#c26) | 20 | 21 |
+| [C27 — Machines et automatisation](#c27) | 11 | 13 |
+| [C28 — Rails et wagonnets](#c28) | 9 | 10 |
+| [C29 — Bateaux et radeaux](#c29) | 22 | 22 |
+| [C30 — Cartographie et orientation](#c30) | 7 | 22 |
+| [C31 — Papier, livres et rangement](#c31) | 28 | 28 |
+| [C32 — Enchantement et entretien](#c32) | 3 | 4 |
+| [C33 — Cultures et compost](#c33) | 9 | 22 |
+| [C34 — Cuisine](#c34) | 59 | 42 |
+| [C35 — Élevage, cuir et montures](#c35) | 15 | 15 |
+| [C36 — Animaux apprivoisés](#c36) | 2 | 2 |
+| [C37 — Abeilles et miel](#c37) | 5 | 6 |
+| [C38 — Pêche et seaux vivants](#c38) | 1 | 11 |
+| [C39 — Océan et monuments](#c39) | 21 | 27 |
+| [C40 — Coraux et récifs](#c40) | 0 | 40 |
+| [C41 — Botanique et renifleurs](#c41) | 1 | 121 |
+| [C42 — Champignons](#c42) | 0 | 6 |
+| [C43 — Archéologie et poterie](#c43) | 3 | 25 |
+| [C44 — Ornements d’armure](#c44) | 37 | 19 |
+| [C45 — Bannières et héraldique](#c45) | 40 | 43 |
+| [C46 — Arc et arbalète](#c46) | 4 | 8 |
+| [C47 — Explosifs et pyrotechnie](#c47) | 6 | 5 |
+| [C48 — Chambres des épreuves](#c48) | 2 | 9 |
+| [C49 — Musique et Allays](#c49) | 3 | 26 |
+| [C50 — Sculk et mémoire](#c50) | 2 | 8 |
+| [C51 — Jardin pâle et résine](#c51) | 13 | 9 |
+| [C52 — Architecture du Nether](#c52) | 82 | 56 |
+| [C53 — Bois carmin et biscornu](#c53) | 28 | 48 |
+| [C54 — Blazes et préparation](#c54) | 3 | 5 |
+| [C55 — Alchimie](#c55) | 286 | 16 |
+| [C56 — Wither et balises](#c56) | 1 | 5 |
+| [C57 — Obsidiennes et réapparition](#c57) | 1 | 3 |
+| [C58 — Arpenteurs](#c58) | 1 | 1 |
+| [C59 — Ghasts joyeux](#c59) | 33 | 18 |
+| [C60 — Architecture de l’End](#c60) | 20 | 14 |
+| [C61 — Boîtes de Shulker](#c61) | 17 | 18 |
+| [C62 — Dragon et cristaux](#c62) | 1 | 4 |
+| [C63 — Élytres et grand voyage](#c63) | 0 | 2 |
+| [C64 — Trophées et butins](#c64) | 0 | 11 |
+| [C65 — Blocs et objets techniques](#c65) | 0 | 112 |
+| [C66 — Villages et échanges](#c66) | 6 | 5 |
+| [C67 — Grenouilles et grelampes](#c67) | 0 | 4 |
+
+### C01
+
+**Bois et menuiserie · Woodworking**
+Identifiant existant : `sanctuary:basic_wood`. **170 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « Une idée brillante ! » (`minecraft:husbandry/make_a_sign_glow`); « Minecraft » (`minecraft:story/root`).
+
+**Amorces de découverte :** Bûche de chêne (`minecraft:oak_log`), Bûche de sapin (`minecraft:spruce_log`), Bambou (`minecraft:bamboo`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Planches, bois assemblés, bâtons, établi, coffres, tonneau, bols, outils en bois, portes, trappes, barrières, dalles, escaliers, pancartes, étagères, échelles, échafaudages.
+
+**Précision :** Conserver sanctuary:basic_wood. Les treize essences sont détaillées plus bas ; bateaux et bois du Nether ont aussi leur page spécialisée.
+
+[Recettes et inventaire exacts de C01](collections-minecraft-inventaire.md#c01).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/basic_wood.json).
+
+
+### C02
+
+**Pierre et premiers outils · Stoneworking**
+Identifiant proposé : `sanctuary:stoneworking`. **57 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « L'âge de pierre » (`minecraft:story/mine_stone`); « Qualité supérieure » (`minecraft:story/upgrade_tools`).
+
+**Amorces de découverte :** Pierres (`minecraft:cobblestone`), Roche (`minecraft:stone`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Pierre, pierre taillée, variantes moussues, dalles, escaliers, murets, outils en pierre, four et tailleur de pierre.
+
+**Précision :** Inclure taille et cuisson ; un bloc de pierre ne révèle pas toutes les machines qui en consomment.
+
+[Recettes et inventaire exacts de C02](collections-minecraft-inventaire.md#c02).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/stoneworking.json).
+
+
+### C03
+
+**Granite, diorite et andésite · Decorative Stone**
+Identifiant proposé : `sanctuary:decorative_stone`. **45 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « L'âge de pierre » (`minecraft:story/mine_stone`).
+
+**Amorces de découverte :** Granite (`minecraft:granite`), Diorite (`minecraft:diorite`), Andésite (`minecraft:andesite`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Trois roches, formes polies, dalles, escaliers et murets existants.
+
+**Précision :** Un même chapitre présente les trois sous-familles.
+
+[Recettes et inventaire exacts de C03](collections-minecraft-inventaire.md#c03).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/decorative_stone.json).
+
+
+### C04
+
+**Ardoise des profondeurs · Deepslate**
+Identifiant proposé : `sanctuary:deepslate`. **73 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Bonne pioche ! » (`minecraft:story/iron_tools`).
+
+**Amorces de découverte :** Pierres des abîmes (`minecraft:cobbled_deepslate`), Ardoise des abîmes (`minecraft:deepslate`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Ardoise taillée, polie, sculptée, briques, carreaux, formes fissurées, dalles, escaliers et murets.
+
+**Précision :** L’ardoise renforcée est une fiche de bloc du monde, sans recette ni obtention ordinaire en survie.
+
+[Recettes et inventaire exacts de C04](collections-minecraft-inventaire.md#c04).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/deepslate.json).
+
+
+### C05
+
+**Tuf · Tuff**
+Identifiant proposé : `sanctuary:tuff`. **38 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Mise à l'épreuve » (`minecraft:adventure/minecraft_trials_edition`).
+
+**Amorces de découverte :** Tuf (`minecraft:tuff`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Tuf brut, poli, sculpté, briques et toutes les formes de construction disponibles.
+
+**Précision :** La découverte du tuf évite d’attendre une chambre des épreuves pour construire.
+
+[Recettes et inventaire exacts de C05](collections-minecraft-inventaire.md#c05).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/tuff.json).
+
+
+### C06
+
+**Argile et briques · Clay and Bricks**
+Identifiant proposé : `sanctuary:clay_and_bricks`. **10 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Sa place est dans un musée » (`minecraft:adventure/salvage_sherd`).
+
+**Amorces de découverte :** Motte d'argile (`minecraft:clay_ball`), Argile (`minecraft:clay`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bloc d’argile, brique, briques, dalles, escaliers, murets et pot de fleurs.
+
+**Précision :** Le vase décoré appartient à Archéologie ; la terre cuite à C11.
+
+[Recettes et inventaire exacts de C06](collections-minecraft-inventaire.md#c06).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/clay_and_bricks.json).
+
+
+### C07
+
+**Sables et grès · Sand and Sandstone**
+Identifiant proposé : `sanctuary:sand_and_sandstone`. **38 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Aventure » (`minecraft:adventure/root`).
+
+**Amorces de découverte :** Sable (`minecraft:sand`), Sable rouge (`minecraft:red_sand`), Grès (`minecraft:sandstone`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Grès et grès rouge : lisses, taillés, sculptés, dalles, escaliers et murets.
+
+**Précision :** Le sable et le gravier sont des ressources ; leur présence ne donne pas de recette de récolte.
+
+[Recettes et inventaire exacts de C07](collections-minecraft-inventaire.md#c07).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/sand_and_sandstone.json).
+
+
+### C08
+
+**Verre et vitrages · Glasswork**
+Identifiant proposé : `sanctuary:glasswork`. **50 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Chaud devant ! » (`minecraft:story/lava_bucket`).
+
+**Amorces de découverte :** Sable (`minecraft:sand`), Verre (`minecraft:glass`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Cuisson du verre, vitres, verre coloré et vitres des seize couleurs.
+
+**Précision :** Le verre teinté à l’améthyste appartient à C15 ; les fioles à C55.
+
+[Recettes et inventaire exacts de C08](collections-minecraft-inventaire.md#c08).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/glasswork.json).
+
+
+### C09
+
+**Teintures · Dyes**
+Identifiant proposé : `sanctuary:dyes`. **50 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une idée brillante ! » (`minecraft:husbandry/make_a_sign_glow`); « Prends-en de la graine ! » (`minecraft:husbandry/plant_seed`).
+
+**Amorces de découverte :** Pissenlit (`minecraft:dandelion`), Poche d'encre (`minecraft:ink_sac`), Lapis-lazuli (`minecraft:lapis_lazuli`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Seize teintures, mélanges et toutes leurs recettes alternatives à partir des plantes ou matériaux.
+
+**Précision :** Les produits teints restent rangés dans leur métier : tissus, verrerie, bannières…
+
+[Recettes et inventaire exacts de C09](collections-minecraft-inventaire.md#c09).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/dyes.json).
+
+
+### C10
+
+**Laine, literie et coussins · Textiles and Bedding**
+Identifiant proposé : `sanctuary:textiles_and_bedding`. **178 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Sans un bruit » (`minecraft:adventure/avoid_vibration`); « Bonne nuit les petits » (`minecraft:adventure/sleep_in_bed`).
+
+**Amorces de découverte :** Laine blanche (`minecraft:white_wool`), Ficelle (`minecraft:string`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Laines, tapis, lits, dalles de laine, escaliers de laine et coussins des seize couleurs.
+
+**Précision :** Laine et ficelle donnent un accès avant le premier sommeil ; ne pas confondre lit et aptitude Sanctuary Se reposer.
+
+[Recettes et inventaire exacts de C10](collections-minecraft-inventaire.md#c10).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/textiles_and_bedding.json).
+
+
+### C11
+
+**Terres cuites · Terracotta**
+Identifiant proposé : `sanctuary:terracotta`. **33 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Sa place est dans un musée » (`minecraft:adventure/salvage_sherd`).
+
+**Amorces de découverte :** Argile (`minecraft:clay`), Terre cuite (`minecraft:terracotta`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Terre cuite, seize teintes et seize terres cuites émaillées, cuisson comprise.
+
+**Précision :** Ne pas inventer de formes absentes du jeu.
+
+[Recettes et inventaire exacts de C11](collections-minecraft-inventaire.md#c11).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/terracotta.json).
+
+
+### C12
+
+**Bétons · Concrete**
+Identifiant proposé : `sanctuary:concrete`. **80 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Aventure » (`minecraft:adventure/root`).
+
+**Amorces de découverte :** Sable (`minecraft:sand`), Gravier (`minecraft:gravel`), Béton blanc en poudre (`minecraft:white_concrete_powder`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Poudres des seize couleurs ; dalles et escaliers de béton de cette version.
+
+**Précision :** Le durcissement dans l’eau est une transformation dans le monde, pas une recette JSON de fabrication.
+
+[Recettes et inventaire exacts de C12](collections-minecraft-inventaire.md#c12).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/concrete.json).
+
+
+### C13
+
+**Sols et mousses · Soils and Moss**
+Identifiant proposé : `sanctuary:soils_and_moss`. **13 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Prends-en de la graine ! » (`minecraft:husbandry/plant_seed`).
+
+**Amorces de découverte :** Terre (`minecraft:dirt`), Bloc de mousse (`minecraft:moss_block`), Boue (`minecraft:mud`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Terre stérile, boue compactée, briques de boue et dérivés, tapis de mousse, tapis de mousse pâle, feuillage au sol.
+
+**Précision :** Herbe, chemins, labours et boue formée à la fiole sont des gestes du monde à expliquer séparément.
+
+[Recettes et inventaire exacts de C13](collections-minecraft-inventaire.md#c13).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/soils_and_moss.json).
+
+
+### C14
+
+**Neige et glace · Snow and Ice**
+Identifiant proposé : `sanctuary:snow_and_ice`. **4 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Tombé du ciel » (`minecraft:adventure/fall_from_world_height`); « Léger comme un lapin » (`minecraft:adventure/walk_on_powder_snow_with_leather_boots`).
+
+**Amorces de découverte :** Boule de neige (`minecraft:snowball`), Glace (`minecraft:ice`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Blocs et couches de neige, glace compactée et glace bleue.
+
+**Précision :** Neige poudreuse : seau et récolte ; bottes en cuir : C35. Aucun craft de neige poudreuse.
+
+[Recettes et inventaire exacts de C14](collections-minecraft-inventaire.md#c14).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/snow_and_ice.json).
+
+
+### C15
+
+**Améthyste et optique · Amethyst and Optics**
+Identifiant proposé : `sanctuary:amethyst_and_optics`. **3 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Est-ce un avion ? » (`minecraft:adventure/spyglass_at_dragon`); « Est-ce une montgolfière ? » (`minecraft:adventure/spyglass_at_ghast`); « Est-ce un oiseau ? » (`minecraft:adventure/spyglass_at_parrot`).
+
+**Amorces de découverte :** Éclat d'améthyste (`minecraft:amethyst_shard`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bloc d’améthyste, longue-vue et verre teinté.
+
+**Précision :** Cristaux et améthyste bourgeonnante sont des ressources/blocs du monde ; la géode ne se fabrique pas.
+
+[Recettes et inventaire exacts de C15](collections-minecraft-inventaire.md#c15).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/amethyst_and_optics.json).
+
+
+### C16
+
+**Spéléothèmes · Dripstone**
+Identifiant proposé : `sanctuary:dripstone`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Bonne pioche ! » (`minecraft:story/iron_tools`).
+
+**Amorces de découverte :** Spéléothème pointu (`minecraft:pointed_dripstone`), Bloc de spéléothème (`minecraft:dripstone_block`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Assemblage du bloc de spéléothème ; fiches des pointes et de leur croissance.
+
+**Précision :** Ne pas transformer la croissance naturelle en recette d’établi.
+
+[Recettes et inventaire exacts de C16](collections-minecraft-inventaire.md#c16).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/dripstone.json).
+
+
+### C17
+
+**Soufre · Sulfur**
+Identifiant proposé : `sanctuary:sulfur`. **36 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Ça va péter ! » (`minecraft:husbandry/uh_oh`).
+
+**Amorces de découverte :** Soufre (`minecraft:sulfur`), Pointe de soufre (`minecraft:sulfur_spike`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Soufre, formes polies, sculptées, briques, dalles, escaliers et murets ; assemblage depuis les pointes.
+
+**Précision :** Soufre puissant, pointes et cube en seau sont documentés selon leurs gestes du monde, sans recette inventée.
+
+[Recettes et inventaire exacts de C17](collections-minecraft-inventaire.md#c17).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/sulfur.json).
+
+
+### C18
+
+**Cinabre · Cinnabar**
+Identifiant proposé : `sanctuary:cinnabar`. **34 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Ça va péter ! » (`minecraft:husbandry/uh_oh`).
+
+**Amorces de découverte :** Cinabre (`minecraft:cinnabar`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Cinabre, formes polies, sculptées, briques, dalles, escaliers et murets.
+
+**Précision :** La découverte de la roche ouvre la collection sans demander d’exploser un cube de soufre.
+
+[Recettes et inventaire exacts de C18](collections-minecraft-inventaire.md#c18).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/cinnabar.json).
+
+
+### C19
+
+**Cuivre et chaudronnerie · Copperworking**
+Identifiant proposé : `sanctuary:copperworking`. **89 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Éclaircir sa lanterne » (`minecraft:adventure/lighten_up`); « Sous haute tension » (`minecraft:adventure/lightning_rod_with_villager_no_fire`); « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « Coup de foudre » (`minecraft:adventure/very_very_frightening`).
+
+**Amorces de découverte :** Cuivre brut (`minecraft:raw_copper`), Lingot de cuivre (`minecraft:copper_ingot`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Fonte, pépites, blocs et dérivés, outils, armes, armure, barres, chaînes, lanternes, torches, coffres, paratonnerres et ampoules.
+
+**Précision :** Inclure les états d’oxydation ayant des recettes. L’oxydation, le décapage et l’assemblage du golem sont des gestes du monde.
+
+[Recettes et inventaire exacts de C19](collections-minecraft-inventaire.md#c19).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/copperworking.json).
+
+
+### C20
+
+**Cuivre ciré · Waxed Copper**
+Identifiant proposé : `sanctuary:waxed_copper`. **116 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Éclaircir sa lanterne » (`minecraft:adventure/lighten_up`); « Frotter » (`minecraft:husbandry/wax_off`); « Lustrer » (`minecraft:husbandry/wax_on`).
+
+**Amorces de découverte :** Rayon de miel (`minecraft:honeycomb`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Cirage et taille des variantes cirées du cuivre, y compris statues, coffres et paratonnerres.
+
+**Précision :** La cire n’ajoute pas de recette pour obtenir une statue de golem non cirée ; conserver sa véritable provenance.
+
+[Recettes et inventaire exacts de C20](collections-minecraft-inventaire.md#c20).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/waxed_copper.json).
+
+
+### C21
+
+**Fer et forge de base · Ironworking**
+Identifiant proposé : `sanctuary:ironworking`. **34 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « Agent de sécurité » (`minecraft:adventure/summon_iron_golem`); « Une coupe au poil » (`minecraft:husbandry/remove_wolf_armor`); « Pas aujourd'hui, merci » (`minecraft:story/deflect_arrow`); « Bonne pioche ! » (`minecraft:story/iron_tools`); « Chaud devant ! » (`minecraft:story/lava_bucket`); « Un corps d'acier » (`minecraft:story/obtain_armor`); « L'âge du fer » (`minecraft:story/smelt_iron`).
+
+**Amorces de découverte :** Fer brut (`minecraft:raw_iron`), Lingot de fer (`minecraft:iron_ingot`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Fonte et recyclage, lingots/pépites/blocs, outils, lances, armure, bouclier, seau, cisailles, enclume, chaînes, barreaux, porte et trappe.
+
+**Précision :** Trémies, rails et machines ont leurs collections ; obtenir du fer n’ouvre pas tout l’automatisme.
+
+[Recettes et inventaire exacts de C21](collections-minecraft-inventaire.md#c21).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/ironworking.json).
+
+
+### C22
+
+**Or et dorure · Goldworking**
+Identifiant proposé : `sanctuary:goldworking`. **26 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « Bling-bling » (`minecraft:nether/distract_piglin`); « De vraies têtes de cochon » (`minecraft:nether/loot_bastion`).
+
+**Amorces de découverte :** Or brut (`minecraft:raw_gold`), Lingot d'or (`minecraft:gold_ingot`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Fonte et recyclage, pépites/lingots/blocs, outils, lances et armure en or.
+
+**Précision :** Pommes dorées, carottes dorées et melon scintillant sont classés selon leur usage. Une pomme dorée enchantée n’a pas de recette.
+
+[Recettes et inventaire exacts de C22](collections-minecraft-inventaire.md#c22).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/goldworking.json).
+
+
+### C23
+
+**Diamants · Diamondworking**
+Identifiant proposé : `sanctuary:diamondworking`. **16 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « Des diamants ! » (`minecraft:story/mine_diamond`); « Couvrez-moi de diamants » (`minecraft:story/shiny_gear`).
+
+**Amorces de découverte :** Diamant (`minecraft:diamond`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bloc et décompression, outils, lance et armure en diamant.
+
+**Précision :** La table d’enchantement a sa collection ; le diamant ne révèle pas automatiquement tout ce qui en contient.
+
+[Recettes et inventaire exacts de C23](collections-minecraft-inventaire.md#c23).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/diamondworking.json).
+
+
+### C24
+
+**Netherite · Netherite Smithing**
+Identifiant proposé : `sanctuary:netherite_smithing`. **18 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une belle brochette » (`minecraft:adventure/spear_many_mobs`); « Sérieux dévouement » (`minecraft:husbandry/obtain_netherite_hoe`); « De vraies têtes de cochon » (`minecraft:nether/loot_bastion`); « Couvrez-moi de débris » (`minecraft:nether/netherite_armor`); « Caché dans les profondeurs » (`minecraft:nether/obtain_ancient_debris`).
+
+**Amorces de découverte :** Débris antiques (`minecraft:ancient_debris`), Fragments de netherite (`minecraft:netherite_scrap`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Cuisson des débris, lingot/bloc, duplication du modèle d’amélioration et douze améliorations de forge disponibles.
+
+**Précision :** Le premier modèle reste à trouver ; sa duplication ne le crée pas à partir de rien. Inclut les équipements de montures prévus par les données.
+
+[Recettes et inventaire exacts de C24](collections-minecraft-inventaire.md#c24).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/netherite_smithing.json).
+
+
+### C25
+
+**Charbon, feu et éclairage · Fire and Light**
+Identifiant proposé : `sanctuary:fire_and_light`. **28 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « J'irai butiner chez vous » (`minecraft:husbandry/safely_harvest_honey`); « L'âge de pierre » (`minecraft:story/mine_stone`); « Minecraft » (`minecraft:story/root`).
+
+**Amorces de découverte :** Charbon (`minecraft:coal`), Charbon de bois (`minecraft:charcoal`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Charbon de bois, bloc de charbon, torches, feu de camp, lanterne, bougies et citrouille-lanterne.
+
+**Précision :** Torches/lanternes de cuivre : C19 ; éclairage des âmes : C52 ; bougies sur gâteau : geste du monde.
+
+[Recettes et inventaire exacts de C25](collections-minecraft-inventaire.md#c25).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/fire_and_light.json).
+
+
+### C26
+
+**Redstone et mesure · Redstone Basics**
+Identifiant proposé : `sanctuary:redstone_basics`. **20 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Dans le mille ! » (`minecraft:adventure/bullseye`); « Le savoir, c'est le pouvoir » (`minecraft:adventure/read_power_of_chiseled_bookshelf`); « Dans la ligne de mire » (`minecraft:adventure/shoot_arrow`).
+
+**Amorces de découverte :** Poudre de redstone (`minecraft:redstone`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Poudre/bloc, torche, répéteur, comparateur, levier, plaques en pierre ou lestées, crochet, capteur solaire, cible et pupitre.
+
+**Précision :** Boutons/plaques en bois restent aussi connus par les bases du bois ; pas de déblocage transitif par ingrédient.
+
+[Recettes et inventaire exacts de C26](collections-minecraft-inventaire.md#c26).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/redstone_basics.json).
+
+
+### C27
+
+**Machines et automatisation · Automation**
+Identifiant proposé : `sanctuary:automation`. **11 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Méta-fabrication » (`minecraft:adventure/crafters_crafting_crafters`).
+
+**Amorces de découverte :** Poudre de redstone (`minecraft:redstone`), Boule de slime (`minecraft:slime_ball`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Pistons, piston collant, distributeur, dropper, trémie, coffre piégé et fabricateur.
+
+**Précision :** Le premier redstone enseigne ces plans avant l’exploit qui demande déjà un fabricateur. Bloc de slime inclus.
+
+[Recettes et inventaire exacts de C27](collections-minecraft-inventaire.md#c27).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/automation.json).
+
+
+### C28
+
+**Rails et wagonnets · Railways**
+Identifiant proposé : `sanctuary:railways`. **9 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Bulle du sous-espace » (`minecraft:nether/fast_travel`).
+
+**Amorces de découverte :** Rails (`minecraft:rail`), Wagonnet (`minecraft:minecart`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Rails ordinaires, de propulsion, détecteurs, activateurs ; wagonnets et variantes.
+
+**Précision :** L’exploit du Nether est une voie supplémentaire ; trouver un rail suffit à apprendre cette famille.
+
+[Recettes et inventaire exacts de C28](collections-minecraft-inventaire.md#c28).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/railways.json).
+
+
+### C29
+
+**Bateaux et radeaux · Boats and Rafts**
+Identifiant proposé : `sanctuary:boats_and_rafts`. **22 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Merci pour le poisson » (`minecraft:husbandry/fishy_business`); « Flottage de chèvre » (`minecraft:husbandry/ride_a_boat_with_a_goat`).
+
+**Amorces de découverte :** Planches de chêne (`minecraft:oak_planks`), Planches de bambou (`minecraft:bamboo_planks`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bateaux et bateaux à coffre des bois compatibles, radeaux de bambou avec ou sans coffre.
+
+**Précision :** Pas de bateaux carmin/biscornu, pas de radeau en chêne : seuls les identifiants présents sont listés.
+
+[Recettes et inventaire exacts de C29](collections-minecraft-inventaire.md#c29).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/boats_and_rafts.json).
+
+
+### C30
+
+**Cartographie et orientation · Maps and Navigation**
+Identifiant proposé : `sanctuary:maps_and_navigation`. **7 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Un commerce à la hauteur » (`minecraft:adventure/trade_at_world_height`); « Le Petit Poucet » (`minecraft:adventure/use_lodestone`); « Bulle du sous-espace » (`minecraft:nether/fast_travel`).
+
+**Amorces de découverte :** Poudre de redstone (`minecraft:redstone`), Papier (`minecraft:paper`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Boussole, montre, carte vierge, table de cartographie, magnétite et opérations de copie/agrandissement de carte.
+
+**Précision :** Boussole de récupération rangée dans C50. Lier une boussole à une magnétite n’est pas une recette d’établi.
+
+[Recettes et inventaire exacts de C30](collections-minecraft-inventaire.md#c30).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/maps_and_navigation.json).
+
+
+### C31
+
+**Papier, livres et rangement · Books and Storage**
+Identifiant proposé : `sanctuary:books_and_storage`. **28 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Le savoir, c'est le pouvoir » (`minecraft:adventure/read_power_of_chiseled_bookshelf`); « Adjugé, vendu ! » (`minecraft:adventure/trade`); « Je suis ton ami » (`minecraft:husbandry/allay_deliver_item_to_player`); « Une idée brillante ! » (`minecraft:husbandry/make_a_sign_glow`).
+
+**Amorces de découverte :** Canne à sucre (`minecraft:sugar_cane`), Papier (`minecraft:paper`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Papier, livre, livre et plume, copie de livre écrit, bibliothèques, bibliothèques sculptées, cadre, cadre luisant, tableau, étiquette et sacs.
+
+**Précision :** L’étiquette a bien une recette dans cette version. Sacs colorés et recettes à composants sont inclus.
+
+[Recettes et inventaire exacts de C31](collections-minecraft-inventaire.md#c31).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/books_and_storage.json).
+
+
+### C32
+
+**Enchantement et entretien · Enchanting and Repair**
+Identifiant proposé : `sanctuary:enchanting_and_repair`. **3 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Vers l'infini et au-delà » (`minecraft:end/elytra`); « Dé-miel-nagement » (`minecraft:husbandry/silk_touch_nest`); « Enchanté ! » (`minecraft:story/enchant_item`); « Ice Bucket Challenge » (`minecraft:story/form_obsidian`).
+
+**Amorces de découverte :** Obsidienne (`minecraft:obsidian`), Lapis-lazuli (`minecraft:lapis_lazuli`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Table d’enchantement, meule et réparation par combinaison dans la grille.
+
+**Précision :** Enchantements, livres enchantés, fusion à l’enclume et réparation des élytres ne deviennent pas des recettes fixes fictives.
+
+[Recettes et inventaire exacts de C32](collections-minecraft-inventaire.md#c32).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/enchanting_and_repair.json).
+
+
+### C33
+
+**Cultures et compost · Farming**
+Identifiant proposé : `sanctuary:farming`. **9 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Agent de sécurité » (`minecraft:adventure/summon_iron_golem`); « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « Il y a de l'amour dans l'air » (`minecraft:husbandry/breed_an_animal`); « Sérieux dévouement » (`minecraft:husbandry/obtain_netherite_hoe`); « Prends-en de la graine ! » (`minecraft:husbandry/plant_seed`).
+
+**Amorces de découverte :** Graines de blé (`minecraft:wheat_seeds`), Blé (`minecraft:wheat`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Graines fabriquables, botte de paille, composteur, poudre d’os, blocs d’os, citrouille et pastèque selon recettes présentes.
+
+**Précision :** Planter, récolter, fertiliser et composter sont des gestes distincts ; la houe suit la collection de son matériau.
+
+[Recettes et inventaire exacts de C33](collections-minecraft-inventaire.md#c33).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/farming.json).
+
+
+### C34
+
+**Cuisine · Cooking**
+Identifiant proposé : `sanctuary:cooking`. **59 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Chanson d'anniversaire » (`minecraft:husbandry/allay_deliver_cake_to_note_block`); « Une alimentation équilibrée » (`minecraft:husbandry/balanced_diet`); « Agriculture » (`minecraft:husbandry/root`); « Zombiologue » (`minecraft:story/cure_zombie_villager`).
+
+**Amorces de découverte :** Blé (`minecraft:wheat`), Oeuf (`minecraft:egg`), Champignon brun (`minecraft:brown_mushroom`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Pain, biscuit, gâteau, tarte, soupes/ragoûts, ragoûts suspects par fleur, sucres, aliments cuits, séchage du varech et aliments dorés.
+
+**Précision :** Four, fumoir et feu de camp : conserver chaque voie de cuisson. Le premier ingrédient ouvre les plans avant le défi alimentaire.
+
+[Recettes et inventaire exacts de C34](collections-minecraft-inventaire.md#c34).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/cooking.json).
+
+
+### C35
+
+**Élevage, cuir et montures · Husbandry and Tack**
+Identifiant proposé : `sanctuary:husbandry_and_tack`. **15 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Léger comme un lapin » (`minecraft:adventure/walk_on_powder_snow_with_leather_boots`); « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « Il y a de l'amour dans l'air » (`minecraft:husbandry/breed_an_animal`); « Un chatalogue complet » (`minecraft:husbandry/complete_catalogue`); « Les trois moustiquaires » (`minecraft:husbandry/leash_all_frog_variants`); « Meilleurs amis pour la vie » (`minecraft:husbandry/tame_an_animal`); « Chef de meute » (`minecraft:husbandry/whole_pack`); « Bateau sur pattes » (`minecraft:nether/ride_strider`); « Comme chez soi » (`minecraft:nether/ride_strider_in_overworld_lava`).
+
+**Amorces de découverte :** Cuir (`minecraft:leather`), Ficelle (`minecraft:string`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Cuir fabriquable depuis les peaux, armure en cuir, selle, laisse, armure de cheval en cuir, carotte sur un bâton et support d’armure.
+
+**Précision :** La selle se fabrique et la laisse utilise la recette exacte de cette version. Les autres armures de monture sans craft sont à trouver.
+
+[Recettes et inventaire exacts de C35](collections-minecraft-inventaire.md#c35).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/husbandry_and_tack.json).
+
+
+### C36
+
+**Animaux apprivoisés · Companion Care**
+Identifiant proposé : `sanctuary:companion_care`. **2 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Tatou compris ! » (`minecraft:adventure/brush_armadillo`); « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « Un chatalogue complet » (`minecraft:husbandry/complete_catalogue`); « Une coupe au poil » (`minecraft:husbandry/remove_wolf_armor`); « Comme neuf » (`minecraft:husbandry/repair_wolf_armor`); « Meilleurs amis pour la vie » (`minecraft:husbandry/tame_an_animal`); « Chef de meute » (`minecraft:husbandry/whole_pack`).
+
+**Amorces de découverte :** Écaille de tatou (`minecraft:armadillo_scute`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Armure pour loup et sa teinture ; fiches de soin, apprivoisement et réparation sur l’animal.
+
+**Précision :** La brosse appartient à Archéologie et doit être connue avant de brosser un tatou ; aucun œuf d’apparition vanilla fabriqué.
+
+[Recettes et inventaire exacts de C36](collections-minecraft-inventaire.md#c36).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/companion_care.json).
+
+
+### C37
+
+**Abeilles et miel · Beekeeping**
+Identifiant proposé : `sanctuary:beekeeping`. **5 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Tombé du ciel » (`minecraft:adventure/fall_from_world_height`); « Atterrissage en douceur » (`minecraft:adventure/honey_block_slide`); « J'irai butiner chez vous » (`minecraft:husbandry/safely_harvest_honey`); « Dé-miel-nagement » (`minecraft:husbandry/silk_touch_nest`).
+
+**Amorces de découverte :** Rayon de miel (`minecraft:honeycomb`), Fiole de miel (`minecraft:honey_bottle`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Ruche, bloc de rayons, bloc de miel, conversions de fioles et sucre de miel.
+
+**Précision :** Feu de camp/fioles sont enseignés avant la récolte ; les nids naturels se récoltent selon les règles du monde.
+
+[Recettes et inventaire exacts de C37](collections-minecraft-inventaire.md#c37).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/beekeeping.json).
+
+
+### C38
+
+**Pêche et seaux vivants · Fishing**
+Identifiant proposé : `sanctuary:fishing`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Le plus mignon des prédateurs » (`minecraft:husbandry/axolotl_in_a_bucket`); « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « Un chatalogue complet » (`minecraft:husbandry/complete_catalogue`); « Merci pour le poisson » (`minecraft:husbandry/fishy_business`); « L'union fait la force ! » (`minecraft:husbandry/kill_axolotl_target`); « Pêche tactique » (`minecraft:husbandry/tactical_fishing`); « Adopte un têtard » (`minecraft:husbandry/tadpole_in_a_bucket`).
+
+**Amorces de découverte :** Ficelle (`minecraft:string`), Morue crue (`minecraft:cod`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Canne à pêche ; fiches poissons, poissons en seau, axolotl et têtard.
+
+**Précision :** Les poissons cuits sont dans C34 ; remplir un seau de créature n’est pas un craft.
+
+[Recettes et inventaire exacts de C38](collections-minecraft-inventaire.md#c38).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/fishing.json).
+
+
+### C39
+
+**Océan et monuments · Ocean Monuments**
+Identifiant proposé : `sanctuary:ocean_monuments`. **21 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une blague tombée à l'eau » (`minecraft:adventure/throw_trident`); « Coup de foudre » (`minecraft:adventure/very_very_frightening`); « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « L'union fait la force ! » (`minecraft:husbandry/kill_axolotl_target`).
+
+**Amorces de découverte :** Éclat de prismarine (`minecraft:prismarine_shard`), Coquille de nautile (`minecraft:nautilus_shell`), Coeur de la mer (`minecraft:heart_of_the_sea`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Prismarine, briques de prismarine, prismarine sombre, dalles, escaliers, murets, lanterne aquatique, conduit et séchage d’éponge.
+
+**Précision :** Trident, cœur de la mer et armures de nautilus non fabriquables restent des trouvailles. Le casque de tortue a sa recette.
+
+[Recettes et inventaire exacts de C39](collections-minecraft-inventaire.md#c39).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/ocean_monuments.json).
+
+
+### C40
+
+**Coraux et récifs · Coral Reefs**
+Identifiant proposé : `sanctuary:coral_reefs`. **0 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Pêche tactique » (`minecraft:husbandry/tactical_fishing`).
+
+**Amorces de découverte :** Corail tubulaire (`minecraft:tube_coral`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Fiches des cinq familles de coraux : blocs, coraux, gorgones, variantes mortes et murales.
+
+**Précision :** Aucune recette JSON attribuée : découverte et récolte des coraux, sans craft inventé.
+
+[Recettes et inventaire exacts de C40](collections-minecraft-inventaire.md#c40).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/coral_reefs.json).
+
+
+### C41
+
+**Botanique et renifleurs · Botany and Sniffers**
+Identifiant proposé : `sanctuary:botany_and_sniffers`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Partons à l'aventure ! » (`minecraft:adventure/adventuring_time`); « Petit casse-graine » (`minecraft:husbandry/feed_snifflet`); « Découverte olfactive » (`minecraft:husbandry/obtain_sniffer_egg`); « Paléobotaniste » (`minecraft:husbandry/plant_any_sniffer_seed`).
+
+**Amorces de découverte :** Graines de torche-fleur (`minecraft:torchflower_seeds`), Pissenlit (`minecraft:dandelion`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Fleurs, pousses, feuilles, plantes aquatiques, fleurs anciennes et pissenlit doré ; recettes existantes de ces objets.
+
+**Précision :** Ne pas fabriquer l’œuf de renifleur, ses graines, les propagules ou les pousses sans recette ; croissance et récolte à documenter.
+
+[Recettes et inventaire exacts de C41](collections-minecraft-inventaire.md#c41).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/botany_and_sniffers.json).
+
+
+### C42
+
+**Champignons · Mushrooms**
+Identifiant proposé : `sanctuary:mushrooms`. **0 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Prends-en de la graine ! » (`minecraft:husbandry/plant_seed`).
+
+**Amorces de découverte :** Champignon brun (`minecraft:brown_mushroom`), Champignon rouge (`minecraft:red_mushroom`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Fiches des deux champignons, blocs de champignons et tige.
+
+**Précision :** Ragoûts dans Cuisine. Les blocs de champignon ne reçoivent pas de recette inventée.
+
+[Recettes et inventaire exacts de C42](collections-minecraft-inventaire.md#c42).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/mushrooms.json).
+
+
+### C43
+
+**Archéologie et poterie · Archaeology**
+Identifiant proposé : `sanctuary:archaeology`. **3 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Tatou compris ! » (`minecraft:adventure/brush_armadillo`); « Le passé recomposé » (`minecraft:adventure/craft_decorated_pot_using_only_sherds`); « Sa place est dans un musée » (`minecraft:adventure/salvage_sherd`).
+
+**Amorces de découverte :** Motte d'argile (`minecraft:clay_ball`), Plume (`minecraft:feather`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Brosse et vase décoré à composants ; collection des vingt-trois tessons.
+
+**Précision :** Les tessons et blocs suspects sont à trouver ; les motifs du vase dépendent des tessons utilisés.
+
+[Recettes et inventaire exacts de C43](collections-minecraft-inventaire.md#c43).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/archaeology.json).
+
+
+### C44
+
+**Ornements d’armure · Armor Trims**
+Identifiant proposé : `sanctuary:armor_trims`. **37 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « La classe de la cuirasse » (`minecraft:adventure/trim_with_all_exclusive_armor_patterns`); « Motif de jalousie » (`minecraft:adventure/trim_with_any_armor_pattern`); « Le bon vieux temps » (`minecraft:nether/find_bastion`); « De vraies têtes de cochon » (`minecraft:nether/loot_bastion`).
+
+**Amorces de découverte :** Modèle de forge (`minecraft:sentry_armor_trim_smithing_template`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Duplication des dix-huit modèles de garniture et dix-huit applications à la forge ; table de forge.
+
+**Précision :** Un modèle découvert ouvre les méthodes, sans donner les autres modèles. Les motifs/coûts sont ceux de la recette réelle.
+
+[Recettes et inventaire exacts de C44](collections-minecraft-inventaire.md#c44).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/armor_trims.json).
+
+
+### C45
+
+**Bannières et héraldique · Banners and Heraldry**
+Identifiant proposé : `sanctuary:banners_and_heraldry`. **40 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Exil volontaire » (`minecraft:adventure/voluntary_exile`); « C'est qui le pillard maintenant ? » (`minecraft:adventure/whos_the_pillager_now`); « Pas aujourd'hui, merci » (`minecraft:story/deflect_arrow`).
+
+**Amorces de découverte :** Laine blanche (`minecraft:white_wool`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bannières, duplications, métier à tisser, motifs fabriquables et décoration du bouclier.
+
+**Précision :** Le métier à tisser offre des opérations propres ; les motifs de butin ne deviennent pas fabriquables.
+
+[Recettes et inventaire exacts de C45](collections-minecraft-inventaire.md#c45).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/banners_and_heraldry.json).
+
+
+### C46
+
+**Arc et arbalète · Archery**
+Identifiant proposé : `sanctuary:archery`. **4 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Arbalistique » (`minecraft:adventure/arbalistic`); « Dans le mille ! » (`minecraft:adventure/bullseye`); « Chasseur de monstres » (`minecraft:adventure/kill_a_mob`); « Monstres chassés » (`minecraft:adventure/kill_all_mobs`); « La vieille Bertha » (`minecraft:adventure/ol_betsy`); « Dans la ligne de mire » (`minecraft:adventure/shoot_arrow`); « Duel de snipers » (`minecraft:adventure/sniper_duel`); « D'une flèche deux coups » (`minecraft:adventure/two_birds_one_arrow`); « Exil volontaire » (`minecraft:adventure/voluntary_exile`); « C'est qui le pillard maintenant ? » (`minecraft:adventure/whos_the_pillager_now`).
+
+**Amorces de découverte :** Ficelle (`minecraft:string`), Silex (`minecraft:flint`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Arc, arbalète, flèches et table d’archerie.
+
+**Précision :** Le trident est une trouvaille. Les flèches spectrales/à effet ont une recette spécifique dans C55 ; aucun craft d’enchantement Infinité.
+
+[Recettes et inventaire exacts de C46](collections-minecraft-inventaire.md#c46).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/archery.json).
+
+
+### C47
+
+**Explosifs et pyrotechnie · Explosives and Fireworks**
+Identifiant proposé : `sanctuary:explosives_and_fireworks`. **6 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « La vieille Bertha » (`minecraft:adventure/ol_betsy`); « Vers l'infini et au-delà » (`minecraft:end/elytra`); « Retour à l'envoyeur » (`minecraft:nether/return_to_sender`); « Alliance instable » (`minecraft:nether/uneasy_alliance`).
+
+**Amorces de découverte :** Poudre à canon (`minecraft:gunpowder`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** TNT, boule de feu, étoiles de feu d’artifice, dégradés et fusées à composants.
+
+**Précision :** Puissance, couleurs, formes et durées sont des variantes d’une recette, pas un identifiant par combinaison.
+
+[Recettes et inventaire exacts de C47](collections-minecraft-inventaire.md#c47).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/explosives_and_fireworks.json).
+
+
+### C48
+
+**Chambres des épreuves · Trial Chambers**
+Identifiant proposé : `sanctuary:trial_chambers`. **2 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Œil pour œil, vent pour vent » (`minecraft:adventure/blowback`); « Mise à l'épreuve » (`minecraft:adventure/minecraft_trials_edition`); « Masse-âcre » (`minecraft:adventure/overoverkill`); « Coffre-très-fort » (`minecraft:adventure/revaulting`); « La clé du succès » (`minecraft:adventure/under_lock_and_key`); « Salto à vent » (`minecraft:adventure/who_needs_rockets`).
+
+**Amorces de découverte :** Bâton de Breeze (`minecraft:breeze_rod`), Noyau lourd (`minecraft:heavy_core`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Charges de vent et masse ; fiches du Breeze, noyau lourd, clés et coffres-forts.
+
+**Précision :** Tuf, cuivre et modèles renvoient aux autres collections. Clés, coffres-forts, générateurs d’épreuves et fioles funestes ne se fabriquent pas.
+
+[Recettes et inventaire exacts de C48](collections-minecraft-inventaire.md#c48).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/trial_chambers.json).
+
+
+### C49
+
+**Musique et Allays · Music and Allays**
+Identifiant proposé : `sanctuary:music_and_allays`. **3 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « La mélodie du bonheur » (`minecraft:adventure/play_jukebox_in_meadows`); « Chanson d'anniversaire » (`minecraft:husbandry/allay_deliver_cake_to_note_block`); « Je suis ton ami » (`minecraft:husbandry/allay_deliver_item_to_player`); « Flottage de chèvre » (`minecraft:husbandry/ride_a_boat_with_a_goat`); « De vraies têtes de cochon » (`minecraft:nether/loot_bastion`).
+
+**Amorces de découverte :** Diamant (`minecraft:diamond`), Fragment de disque (`minecraft:disc_fragment_5`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bloc musical, jukebox et reconstitution du disque 5 ; vingt-deux disques à découvrir.
+
+**Précision :** Les vingt-et-un autres disques n’ont pas de recette JSON. Cornes de chèvre et duplication d’Allay relèvent du monde.
+
+[Recettes et inventaire exacts de C49](collections-minecraft-inventaire.md#c49).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/music_and_allays.json).
+
+
+### C50
+
+**Sculk et mémoire · Sculk and Echoes**
+Identifiant proposé : `sanctuary:sculk_and_echoes`. **2 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Sans un bruit » (`minecraft:adventure/avoid_vibration`); « Propagation » (`minecraft:adventure/kill_mob_near_sculk_catalyst`).
+
+**Amorces de découverte :** Éclat d'écho (`minecraft:echo_shard`), Capteur sculk (`minecraft:sculk_sensor`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Capteur sculk calibré et boussole de récupération ; fiches sculk, catalyseur, hurleur et éclats d’écho.
+
+**Précision :** Le capteur ordinaire et les éclats sont à obtenir. La laine de C10 enseigne la technique de déplacement silencieux.
+
+[Recettes et inventaire exacts de C50](collections-minecraft-inventaire.md#c50).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/sculk_and_echoes.json).
+
+
+### C51
+
+**Jardin pâle et résine · Pale Garden and Resin**
+Identifiant proposé : `sanctuary:pale_garden_and_resin`. **13 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Greffeur de cœur » (`minecraft:adventure/heart_transplanter`).
+
+**Amorces de découverte :** Amas de résine (`minecraft:resin_clump`), Bûche de chêne pâle (`minecraft:pale_oak_log`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Bloc de résine, briques et dérivés, cœur de grinceur.
+
+**Précision :** Le chêne pâle est aussi du bois de C01 ; mousse pâle et botanique ont leurs fiches. Placer le cœur est distinct de son craft.
+
+[Recettes et inventaire exacts de C51](collections-minecraft-inventaire.md#c51).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/pale_garden_and_resin.json).
+
+
+### C52
+
+**Architecture du Nether · Nether Masonry**
+Identifiant proposé : `sanctuary:nether_masonry`. **82 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Pas tout à fait "neuf" vies » (`minecraft:nether/charge_respawn_anchor`); « Voyage au bout de l'enfer » (`minecraft:nether/explore_nether`); « Le bon vieux temps » (`minecraft:nether/find_bastion`); « Une terrible forteresse » (`minecraft:nether/find_fortress`); « De vraies têtes de cochon » (`minecraft:nether/loot_bastion`); « Nether » (`minecraft:nether/root`); « Aller au fond des choses » (`minecraft:story/enter_the_nether`).
+
+**Amorces de découverte :** Netherrack (`minecraft:netherrack`), Roche noire (`minecraft:blackstone`), Quartz du Nether (`minecraft:quartz`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Briques du Nether et rouges, quartz, roche noire, basalte, pierre lumineuse, blocs de magma, éclairage des âmes.
+
+**Précision :** Sable/terre des âmes, minerais, obsidienne et certains blocs naturels se récoltent ; pas de craft de portail complet.
+
+[Recettes et inventaire exacts de C52](collections-minecraft-inventaire.md#c52).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/nether_masonry.json).
+
+
+### C53
+
+**Bois carmin et biscornu · Nether Woodworking**
+Identifiant proposé : `sanctuary:nether_woodworking`. **28 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Voyage au bout de l'enfer » (`minecraft:nether/explore_nether`); « Nether » (`minecraft:nether/root`); « Aller au fond des choses » (`minecraft:story/enter_the_nether`).
+
+**Amorces de découverte :** Tige carmin (`minecraft:crimson_stem`), Tige biscornue (`minecraft:warped_stem`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Planches et menuiserie des deux familles du Nether, étagères comprises.
+
+**Précision :** Aucun bateau ni radeau. Végétation du Nether et blocs de verrues sont inclus comme fiches/recettes selon le cas.
+
+[Recettes et inventaire exacts de C53](collections-minecraft-inventaire.md#c53).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/nether_woodworking.json).
+
+
+### C54
+
+**Blazes et préparation · Blaze Preparations**
+Identifiant proposé : `sanctuary:blaze_preparations`. **3 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Une terrible forteresse » (`minecraft:nether/find_fortress`); « Dans le feu de l'action » (`minecraft:nether/obtain_blaze_rod`); « À suivre... » (`minecraft:story/follow_ender_eye`).
+
+**Amorces de découverte :** Bâton de Blaze (`minecraft:blaze_rod`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Poudre de Blaze, œil de l’Ender, crème de magma et préparation de l’alchimie.
+
+**Précision :** L’alambic et les fioles sont enseignés par C55, également liée au bâton de Blaze, avant la première potion.
+
+[Recettes et inventaire exacts de C54](collections-minecraft-inventaire.md#c54).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/blaze_preparations.json).
+
+
+### C55
+
+**Alchimie · Brewing**
+Identifiant existant : `sanctuary:brewing`. **286 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « À bout de souffle » (`minecraft:end/dragon_breath`); « Comment en est-on arrivé là ? » (`minecraft:nether/all_effects`); « Mélanges dangereux » (`minecraft:nether/all_potions`); « Apprenti chimiste » (`minecraft:nether/brew_potion`); « Une terrible forteresse » (`minecraft:nether/find_fortress`); « Dans le feu de l'action » (`minecraft:nether/obtain_blaze_rod`); « Zombiologue » (`minecraft:story/cure_zombie_villager`).
+
+**Amorces de découverte :** Bâton de Blaze (`minecraft:blaze_rod`), Alambic (`minecraft:brewing_stand`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Alambic, fiole, chaudron, ingrédients préparés, 279 recettes d’alchimie, flèches spectrales et flèches à effet.
+
+**Précision :** Conserver sanctuary:brewing. Les 279 mélanges incluent les contenants buvables, jetables et persistants ; le souffle de dragon reste à récolter.
+
+[Recettes et inventaire exacts de C55](collections-minecraft-inventaire.md#c55).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/brewing.json).
+
+
+### C56
+
+**Wither et balises · Wither and Beacons**
+Identifiant proposé : `sanctuary:wither_and_beacons`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Fais ta balise » (`minecraft:nether/create_beacon`); « Phare à On » (`minecraft:nether/create_full_beacon`); « Qu'on lui coupe la tête ! » (`minecraft:nether/get_wither_skull`); « Les Witherables » (`minecraft:nether/summon_wither`).
+
+**Amorces de découverte :** Étoile du Nether (`minecraft:nether_star`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Balise ; fiche d’invocation du Wither, étoile et pyramides de puissance.
+
+**Précision :** Invoquer le Wither n’accorde ni étoile ni recette de crâne. Les blocs de pyramide appartiennent aux collections de métaux.
+
+[Recettes et inventaire exacts de C56](collections-minecraft-inventaire.md#c56).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/wither_and_beacons.json).
+
+
+### C57
+
+**Obsidiennes et réapparition · Obsidian and Respawning**
+Identifiant proposé : `sanctuary:obsidian_and_respawning`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Pas tout à fait "neuf" vies » (`minecraft:nether/charge_respawn_anchor`); « Voyage au bout de l'enfer » (`minecraft:nether/explore_nether`); « Bulle du sous-espace » (`minecraft:nether/fast_travel`); « Qui a coupé un oignon ? » (`minecraft:nether/obtain_crying_obsidian`); « Aller au fond des choses » (`minecraft:story/enter_the_nether`); « Ice Bucket Challenge » (`minecraft:story/form_obsidian`).
+
+**Amorces de découverte :** Obsidienne pleureuse (`minecraft:crying_obsidian`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Ancre de réapparition ; fiches obsidienne et obsidienne pleureuse.
+
+**Précision :** Ni obsidienne ni obsidienne pleureuse ne se fabriquent à l’établi ; charger l’ancre est un geste du monde.
+
+[Recettes et inventaire exacts de C57](collections-minecraft-inventaire.md#c57).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/obsidian_and_respawning.json).
+
+
+### C58
+
+**Arpenteurs · Strider Riding**
+Identifiant proposé : `sanctuary:strider_riding`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Voyage au bout de l'enfer » (`minecraft:nether/explore_nether`); « Bateau sur pattes » (`minecraft:nether/ride_strider`); « Comme chez soi » (`minecraft:nether/ride_strider_in_overworld_lava`).
+
+**Amorces de découverte :** Champignon biscornu (`minecraft:warped_fungus`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Champignon biscornu sur un bâton ; renvoi aux selles et laisses de C35.
+
+**Précision :** Le plan doit pouvoir être appris avant de monter l’arpenteur.
+
+[Recettes et inventaire exacts de C58](collections-minecraft-inventaire.md#c58).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/strider_riding.json).
+
+
+### C59
+
+**Ghasts joyeux · Happy Ghasts**
+Identifiant proposé : `sanctuary:happy_ghasts`. **33 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Est-ce une montgolfière ? » (`minecraft:adventure/spyglass_at_ghast`); « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « Trempé jusqu'aux os » (`minecraft:husbandry/place_dried_ghast_in_water`); « Retour à l'envoyeur » (`minecraft:nether/return_to_sender`); « Alliance instable » (`minecraft:nether/uneasy_alliance`).
+
+**Amorces de découverte :** Larme de Ghast (`minecraft:ghast_tear`), Ghast desséché (`minecraft:dried_ghast`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Ghast desséché et seize harnais, teintures comprises.
+
+**Précision :** Hydratation et croissance sont des étapes du monde ; aucune recette d’œuf d’apparition.
+
+[Recettes et inventaire exacts de C59](collections-minecraft-inventaire.md#c59).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/happy_ghasts.json).
+
+
+### C60
+
+**Architecture de l’End · End Architecture**
+Identifiant proposé : `sanctuary:end_architecture`. **20 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Escapade à distance » (`minecraft:end/enter_end_gateway`); « Les mystérieuses cités de l'End » (`minecraft:end/find_end_city`); « Libérez l'End ! » (`minecraft:end/kill_dragon`); « L'End » (`minecraft:end/root`); « Fin ? » (`minecraft:story/enter_the_end`).
+
+**Amorces de découverte :** Pierre de l'End (`minecraft:end_stone`), Chorus (`minecraft:chorus_fruit`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Briques de pierre de l’End, chorus éclaté, purpur et dérivés, barre de l’End.
+
+**Précision :** La plante/fleur de chorus se découvre et se cultive ; la pierre de l’End se mine.
+
+[Recettes et inventaire exacts de C60](collections-minecraft-inventaire.md#c60).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/end_architecture.json).
+
+
+### C61
+
+**Boîtes de Shulker · Shulker Storage**
+Identifiant proposé : `sanctuary:shulker_storage`. **17 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Les mystérieuses cités de l'End » (`minecraft:end/find_end_city`); « Superbe panorama » (`minecraft:end/levitate`).
+
+**Amorces de découverte :** Carapace de Shulker (`minecraft:shulker_shell`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Boîte de Shulker et seize couleurs, avec préservation des composants prévue par les recettes.
+
+**Précision :** La carapace se trouve ; aucun craft de Shulker ni d’œuf d’apparition.
+
+[Recettes et inventaire exacts de C61](collections-minecraft-inventaire.md#c61).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/shulker_storage.json).
+
+
+### C62
+
+**Dragon et cristaux · Dragon and Crystals**
+Identifiant proposé : `sanctuary:dragon_and_crystals`. **1 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Est-ce un avion ? » (`minecraft:adventure/spyglass_at_dragon`); « À bout de souffle » (`minecraft:end/dragon_breath`); « La nouvelle génération » (`minecraft:end/dragon_egg`); « Libérez l'End ! » (`minecraft:end/kill_dragon`); « Un air de déjà vu... » (`minecraft:end/respawn_dragon`); « Fin ? » (`minecraft:story/enter_the_end`); « À suivre... » (`minecraft:story/follow_ender_eye`).
+
+**Amorces de découverte :** Perle de l'Ender (`minecraft:ender_pearl`), Larme de Ghast (`minecraft:ghast_tear`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Cristal de l’End ; fiches œuf, tête, souffle et invocation du dragon.
+
+**Précision :** Œuf, tête et souffle ne se fabriquent pas. Les potions persistantes renvoient à C55, déjà apprises si la collection complète est ouverte.
+
+[Recettes et inventaire exacts de C62](collections-minecraft-inventaire.md#c62).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/dragon_and_crystals.json).
+
+
+### C63
+
+**Élytres et grand voyage · Elytra Flight**
+Identifiant proposé : `sanctuary:elytra_flight`. **0 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Tombé du ciel » (`minecraft:adventure/fall_from_world_height`); « D'une flèche deux coups » (`minecraft:adventure/two_birds_one_arrow`); « Vers l'infini et au-delà » (`minecraft:end/elytra`).
+
+**Amorces de découverte :** Élytres (`minecraft:elytra`), Membrane de Phantom (`minecraft:phantom_membrane`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Renvoi aux fusées de C47 et à la réparation de C32 ; fiche de vol.
+
+**Précision :** Aucune recette primaire de fabrication d’élytres. Les membranes doivent être obtenues normalement.
+
+[Recettes et inventaire exacts de C63](collections-minecraft-inventaire.md#c63).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/elytra_flight.json).
+
+
+### C64
+
+**Trophées et butins · Trophies and Loot**
+Identifiant proposé : `sanctuary:trophies_and_loot`. **0 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Héros du village » (`minecraft:adventure/hero_of_the_village`); « Monstres chassés » (`minecraft:adventure/kill_all_mobs`); « Aux frontières de la mort » (`minecraft:adventure/totem_of_undying`); « Qu'on lui coupe la tête ! » (`minecraft:nether/get_wither_skull`).
+
+**Amorces de découverte :** Totem d'immortalité (`minecraft:totem_of_undying`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Têtes, crânes, totem et objets de butin sans recette, selon les renvois des autres collections.
+
+**Précision :** Collection de fiches, sans création de ressource ni faux craft de totem.
+
+[Recettes et inventaire exacts de C64](collections-minecraft-inventaire.md#c64).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/trophies_and_loot.json).
+
+
+### C65
+
+**Blocs et objets techniques · Technical Catalogue**
+Identifiant proposé : `sanctuary:technical_catalogue`. **0 recettes primaires.**
+
+**Accès :** vue opérateur ; aucun advancement de survie.
+
+**Contenu :** Air, barrières, blocs de commande, blocs de structure, jigsaw, blocs de test, lumière, œufs d’apparition et états techniques internes.
+
+**Précision :** Hors progression survie ; consultation opérateur. Les blocs du monde non récupérables restent signalés dans leur famille quand elle est identifiable.
+
+[Recettes et inventaire exacts de C65](collections-minecraft-inventaire.md#c65).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/technical_catalogue.json).
+
+
+### C66
+
+**Villages et échanges · Villages and Trading**
+Identifiant proposé : `sanctuary:villages_and_trading`. **6 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Héros du village » (`minecraft:adventure/hero_of_the_village`); « Agent de sécurité » (`minecraft:adventure/summon_iron_golem`); « Adjugé, vendu ! » (`minecraft:adventure/trade`); « Un commerce à la hauteur » (`minecraft:adventure/trade_at_world_height`); « Coup de foudre » (`minecraft:adventure/very_very_frightening`); « Exil volontaire » (`minecraft:adventure/voluntary_exile`); « Zombiologue » (`minecraft:story/cure_zombie_villager`).
+
+**Amorces de découverte :** Émeraude (`minecraft:emerald`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Émeraudes : blocs, décompression et traitement du minerai ; fiches de cloches, cartes d’exploration et postes de métier.
+
+**Précision :** Les offres sont du commerce, pas des recettes. Les postes de métier renvoient aux collections qui les fabriquent.
+
+[Recettes et inventaire exacts de C66](collections-minecraft-inventaire.md#c66).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/villages_and_trading.json).
+
+
+### C67
+
+**Grenouilles et grelampes · Frogs and Froglights**
+Identifiant proposé : `sanctuary:frogs_and_froglights`. **0 recettes primaires.**
+
+**Advancements qui ouvrent cette collection :** « Deux par deux » (`minecraft:husbandry/bred_all_animals`); « Par nos pouvoirs combinés ! » (`minecraft:husbandry/froglights`); « Les trois moustiquaires » (`minecraft:husbandry/leash_all_frog_variants`); « Adopte un têtard » (`minecraft:husbandry/tadpole_in_a_bucket`).
+
+**Amorces de découverte :** Têtard dans un seau (`minecraft:tadpole_bucket`), Grelampe ocrée (`minecraft:ochre_froglight`), ou un objet de cette famille selon la règle D.
+
+**Contenu :** Œufs de grenouille, têtards et trois grelampes ; méthode d’obtention liée aux grenouilles.
+
+**Précision :** Aucune recette JSON : reproduction, croissance et production de grelampes sont des gestes du monde. La laisse vient de C35.
+
+[Recettes et inventaire exacts de C67](collections-minecraft-inventaire.md#c67).
+
+[Définition serveur beta.035](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/frogs_and_froglights.json).
+
+
+## Correspondance des advancements
+
+Les **126** lignes suivantes couvrent les cinq arbres de progression de la version. Chaque collection indiquée est enseignée en entier ; si elle est déjà connue, seuls les plans manquants sont ajoutés. Les collections à zéro recette ouvrent des fiches ou renvois proposés, sans nouvelle recette primaire.
+
+
+### Minecraft — 16 advancements
+
+| Advancement FR / EN | Identifiant vanilla | Objectif vanilla | Collections proposées |
+| --- | --- | --- | --- |
+| Zombiologue / Zombie Doctor | `minecraft:story/cure_zombie_villager` | Affaiblissez et soignez un zombie-villageois. | [C34](#c34), [C55](#c55), [C66](#c66) |
+| Pas aujourd'hui, merci / Not Today, Thank You | `minecraft:story/deflect_arrow` | Parez un projectile avec un bouclier. | [C21](#c21), [C45](#c45) |
+| Enchanté ! / Enchanter | `minecraft:story/enchant_item` | Enchantez un objet dans la table d'enchantement. | [C32](#c32) |
+| Fin ? / The End? | `minecraft:story/enter_the_end` | Entrez dans le portail de l'End. | [C60](#c60), [C62](#c62) |
+| Aller au fond des choses / We Need to Go Deeper | `minecraft:story/enter_the_nether` | Construisez, allumez et entrez dans un portail du Nether. | [C52](#c52), [C53](#c53), [C57](#c57) |
+| À suivre... / Eye Spy | `minecraft:story/follow_ender_eye` | Suivez un œil de l'Ender. | [C54](#c54), [C62](#c62) |
+| Ice Bucket Challenge / Ice Bucket Challenge | `minecraft:story/form_obsidian` | Obtenez un bloc d'obsidienne. | [C32](#c32), [C57](#c57) |
+| Bonne pioche ! / Isn't It Iron Pick? | `minecraft:story/iron_tools` | Fabriquez une pioche en fer. | [C04](#c04), [C16](#c16), [C21](#c21) |
+| Chaud devant ! / Hot Stuff | `minecraft:story/lava_bucket` | Remplissez un seau de lave. | [C08](#c08), [C21](#c21) |
+| Des diamants ! / Diamonds! | `minecraft:story/mine_diamond` | Obtenez des diamants. | [C23](#c23) |
+| L'âge de pierre / Stone Age | `minecraft:story/mine_stone` | Minez de la roche avec votre nouvelle pioche. | [C02](#c02), [C03](#c03), [C25](#c25) |
+| Un corps d'acier / Suit Up | `minecraft:story/obtain_armor` | Fabriquez une pièce d'armure en fer. | [C21](#c21) |
+| Minecraft / Minecraft | `minecraft:story/root` | Le cœur et l'histoire du jeu | [C01](#c01), [C25](#c25) |
+| Couvrez-moi de diamants / Cover Me with Diamonds | `minecraft:story/shiny_gear` | Une armure en diamant peut sauver la vie. | [C23](#c23) |
+| L'âge du fer / Acquire Hardware | `minecraft:story/smelt_iron` | Faites fondre du fer. | [C21](#c21) |
+| Qualité supérieure / Getting an Upgrade | `minecraft:story/upgrade_tools` | Fabriquez une meilleure pioche. | [C02](#c02) |
+
+### Aventure — 47 advancements
+
+| Advancement FR / EN | Identifiant vanilla | Objectif vanilla | Collections proposées |
+| --- | --- | --- | --- |
+| Partons à l'aventure ! / Adventuring Time | `minecraft:adventure/adventuring_time` | Découvrez tous les biomes. | [C03](#c03), [C05](#c05), [C07](#c07), [C13](#c13), [C14](#c14), [C16](#c16), [C17](#c17), [C18](#c18), [C40](#c40), [C41](#c41) |
+| Arbalistique / Arbalistic | `minecraft:adventure/arbalistic` | Tuez cinq créatures de types différents d'un seul tir d'arbalète. | [C46](#c46) |
+| Sans un bruit / Sneak 100 | `minecraft:adventure/avoid_vibration` | Faufilez-vous près d'un capteur sculk ou d'un Warden pour éviter qu'il ne vous détecte. | [C10](#c10), [C50](#c50) |
+| Œil pour œil, vent pour vent / Blowback | `minecraft:adventure/blowback` | Tuez un Breeze en lui renvoyant une charge de vent. | [C48](#c48) |
+| Tatou compris ! / Isn't It Scute? | `minecraft:adventure/brush_armadillo` | Obtenez des écailles d'un tatou en le brossant. | [C36](#c36), [C43](#c43) |
+| Dans le mille ! / Bullseye | `minecraft:adventure/bullseye` | Touchez le centre d'une cible à au moins 30 mètres de distance. | [C26](#c26), [C46](#c46) |
+| Le passé recomposé / Careful Restoration | `minecraft:adventure/craft_decorated_pot_using_only_sherds` | Fabriquez un vase décoré à partir de quatre tessons de poterie. | [C43](#c43) |
+| Méta-fabrication / Crafters Crafting Crafters | `minecraft:adventure/crafters_crafting_crafters` | Assistez à la fabrication d'un fabricateur par un fabricateur. | [C27](#c27) |
+| Tombé du ciel / Caves & Cliffs | `minecraft:adventure/fall_from_world_height` | Survivez à une chute du point le plus haut du monde (à la limite de construction) au point le plus bas. | [C14](#c14), [C37](#c37), [C63](#c63) |
+| Greffeur de cœur / Heart Transplanter | `minecraft:adventure/heart_transplanter` | Placez un cœur de grinceur avec le bon alignement entre deux bûches de chêne pâle. | [C51](#c51) |
+| Héros du village / Hero of the Village | `minecraft:adventure/hero_of_the_village` | Protégez un village d'une invasion. | [C64](#c64), [C66](#c66) |
+| Atterrissage en douceur / Sticky Situation | `minecraft:adventure/honey_block_slide` | Sautez contre un bloc de miel pour ralentir votre chute. | [C37](#c37) |
+| Chasseur de monstres / Monster Hunter | `minecraft:adventure/kill_a_mob` | Tuez n'importe quelle créature hostile. | [C46](#c46) |
+| Monstres chassés / Monsters Hunted | `minecraft:adventure/kill_all_mobs` | Tuez au moins une fois chaque créature hostile. | [C46](#c46), [C64](#c64) |
+| Propagation / It Spreads | `minecraft:adventure/kill_mob_near_sculk_catalyst` | Tuez une créature à proximité d'un catalyseur de sculk. | [C50](#c50) |
+| Éclaircir sa lanterne / Lighten Up | `minecraft:adventure/lighten_up` | Décapez une ampoule en cuivre avec une hache pour la rendre plus lumineuse. | [C19](#c19), [C20](#c20) |
+| Sous haute tension / Surge Protector | `minecraft:adventure/lightning_rod_with_villager_no_fire` | Protégez un villageois d'une décharge électrique indésirable sans déclencher d'incendie. | [C19](#c19) |
+| Mise à l'épreuve / Minecraft: Trial(s) Edition | `minecraft:adventure/minecraft_trials_edition` | Mettez les pieds dans une chambre des épreuves. | [C05](#c05), [C48](#c48) |
+| La vieille Bertha / Ol' Betsy | `minecraft:adventure/ol_betsy` | Tirez à l'arbalète. | [C46](#c46), [C47](#c47) |
+| Masse-âcre / Over-Overkill | `minecraft:adventure/overoverkill` | Infligez 50 cœurs de dégâts avec un seul coup de masse. | [C48](#c48) |
+| La mélodie du bonheur / Sound of Music | `minecraft:adventure/play_jukebox_in_meadows` | Donnez vie aux prairies avec la mélodie d'un jukebox. | [C49](#c49) |
+| Le savoir, c'est le pouvoir / The Power of Books | `minecraft:adventure/read_power_of_chiseled_bookshelf` | Captez le signal de puissance d'une bibliothèque sculptée en utilisant un comparateur. | [C26](#c26), [C31](#c31) |
+| Coffre-très-fort / Revaulting | `minecraft:adventure/revaulting` | Déverrouillez un coffre-fort funeste avec une clé des épreuves funeste. | [C48](#c48) |
+| Aventure / Adventure | `minecraft:adventure/root` | Aventure, exploration et combat | [C07](#c07), [C12](#c12) |
+| Sa place est dans un musée / Respecting the Remnants | `minecraft:adventure/salvage_sherd` | Brossez un bloc suspect pour obtenir un tesson de poterie. | [C06](#c06), [C11](#c11), [C43](#c43) |
+| Dans la ligne de mire / Take Aim | `minecraft:adventure/shoot_arrow` | Tirez sur quelque chose avec une flèche. | [C26](#c26), [C46](#c46) |
+| Bonne nuit les petits / Sweet Dreams | `minecraft:adventure/sleep_in_bed` | Dormez dans un lit pour changer votre point de réapparition. | [C10](#c10) |
+| Duel de snipers / Sniper Duel | `minecraft:adventure/sniper_duel` | Tuez un squelette à au moins 50 mètres de distance. | [C46](#c46) |
+| Une belle brochette / Mob Kabob | `minecraft:adventure/spear_many_mobs` | Touchez cinq créatures avec une lance lors d'une même attaque chargée. | [C01](#c01), [C02](#c02), [C19](#c19), [C21](#c21), [C22](#c22), [C23](#c23), [C24](#c24) |
+| Est-ce un avion ? / Is It a Plane? | `minecraft:adventure/spyglass_at_dragon` | Regardez l'Ender Dragon à travers une longue-vue. | [C15](#c15), [C62](#c62) |
+| Est-ce une montgolfière ? / Is It a Balloon? | `minecraft:adventure/spyglass_at_ghast` | Regardez un Ghast à travers une longue-vue. | [C15](#c15), [C59](#c59) |
+| Est-ce un oiseau ? / Is It a Bird? | `minecraft:adventure/spyglass_at_parrot` | Regardez un perroquet à travers une longue-vue. | [C15](#c15) |
+| Agent de sécurité / Hired Help | `minecraft:adventure/summon_iron_golem` | Invoquez un golem de fer pour défendre un village. | [C21](#c21), [C33](#c33), [C66](#c66) |
+| Une blague tombée à l'eau / A Throwaway Joke | `minecraft:adventure/throw_trident` | Lancez un trident sur quelque chose. Remarque : lancer votre seule arme n'est pas une bonne idée. | [C39](#c39) |
+| Aux frontières de la mort / Postmortal | `minecraft:adventure/totem_of_undying` | Utilisez un totem d'immortalité pour tromper la mort. | [C64](#c64) |
+| Adjugé, vendu ! / What a Deal! | `minecraft:adventure/trade` | Commercez avec un villageois. | [C31](#c31), [C66](#c66) |
+| Un commerce à la hauteur / Star Trader | `minecraft:adventure/trade_at_world_height` | Commercez avec un villageois à la hauteur maximale de construction. | [C30](#c30), [C66](#c66) |
+| La classe de la cuirasse / Smithing with Style | `minecraft:adventure/trim_with_all_exclusive_armor_patterns` | Appliquez ces modèles de forge au moins une fois : des tours, des groins, des côtes, des abîmes, du silence, des Vex, des marées et des éclaireurs. | [C44](#c44) |
+| Motif de jalousie / Crafting a New Look | `minecraft:adventure/trim_with_any_armor_pattern` | Fabriquez une pièce d'armure ornée en utilisant une table de forge. | [C44](#c44) |
+| D'une flèche deux coups / Two Birds, One Arrow | `minecraft:adventure/two_birds_one_arrow` | Tuez deux Phantoms avec une flèche perforante. | [C46](#c46), [C63](#c63) |
+| La clé du succès / Under Lock and Key | `minecraft:adventure/under_lock_and_key` | Déverrouillez un coffre-fort avec une clé des épreuves. | [C48](#c48) |
+| Le Petit Poucet / Country Lode, Take Me Home | `minecraft:adventure/use_lodestone` | Utilisez une boussole sur un bloc de magnétite. | [C30](#c30) |
+| Coup de foudre / Very Very Frightening | `minecraft:adventure/very_very_frightening` | Frappez un villageois avec la foudre. | [C19](#c19), [C39](#c39), [C66](#c66) |
+| Exil volontaire / Voluntary Exile | `minecraft:adventure/voluntary_exile` | Tuez un capitaine de pillards. Songez peut-être à rester à l'écart des villages pour le moment... | [C45](#c45), [C46](#c46), [C66](#c66) |
+| Léger comme un lapin / Light as a Rabbit | `minecraft:adventure/walk_on_powder_snow_with_leather_boots` | Marchez sur de la neige poudreuse... sans vous y enfoncer. | [C14](#c14), [C35](#c35) |
+| Salto à vent / Who Needs Rockets? | `minecraft:adventure/who_needs_rockets` | Utilisez une charge de vent pour vous propulser de 8 blocs vers le haut. | [C48](#c48) |
+| C'est qui le pillard maintenant ? / Who's the Pillager Now? | `minecraft:adventure/whos_the_pillager_now` | Rendez à un pillard la monnaie de sa pièce. | [C45](#c45), [C46](#c46) |
+
+### Agriculture — 31 advancements
+
+| Advancement FR / EN | Identifiant vanilla | Objectif vanilla | Collections proposées |
+| --- | --- | --- | --- |
+| Chanson d'anniversaire / Birthday Song | `minecraft:husbandry/allay_deliver_cake_to_note_block` | Faites en sorte qu'un Allay jette un gâteau vers un bloc musical. | [C34](#c34), [C49](#c49) |
+| Je suis ton ami / You've Got a Friend in Me | `minecraft:husbandry/allay_deliver_item_to_player` | Recevez un objet apporté par un Allay. | [C31](#c31), [C49](#c49) |
+| Le plus mignon des prédateurs / The Cutest Predator | `minecraft:husbandry/axolotl_in_a_bucket` | Capturez un axolotl avec un seau. | [C38](#c38) |
+| Une alimentation équilibrée / A Balanced Diet | `minecraft:husbandry/balanced_diet` | Mangez tout ce qui est comestible, même si ce n'est pas bon pour vous. | [C34](#c34) |
+| Deux par deux / Two by Two | `minecraft:husbandry/bred_all_animals` | Faites se reproduire au moins une fois chaque animal. | [C33](#c33), [C35](#c35), [C36](#c36), [C38](#c38), [C39](#c39), [C59](#c59), [C67](#c67) |
+| Il y a de l'amour dans l'air / The Parrots and the Bats | `minecraft:husbandry/breed_an_animal` | Faites se reproduire deux animaux ensemble. | [C33](#c33), [C35](#c35) |
+| Un chatalogue complet / A Complete Catalogue | `minecraft:husbandry/complete_catalogue` | Apprivoisez toutes les variantes de chat ! | [C35](#c35), [C36](#c36), [C38](#c38) |
+| Petit casse-graine / Little Sniffs | `minecraft:husbandry/feed_snifflet` | Nourrissez un reniflet. | [C41](#c41) |
+| Merci pour le poisson / Fishy Business | `minecraft:husbandry/fishy_business` | Pêchez un poisson. | [C29](#c29), [C38](#c38) |
+| Par nos pouvoirs combinés ! / With Our Powers Combined! | `minecraft:husbandry/froglights` | Obtenez tous les types de grelampe dans votre inventaire. | [C67](#c67) |
+| L'union fait la force ! / The Healing Power of Friendship! | `minecraft:husbandry/kill_axolotl_target` | Faites équipe avec un axolotl et gagnez un combat. | [C38](#c38), [C39](#c39) |
+| Les trois moustiquaires / When the Squad Hops into Town | `minecraft:husbandry/leash_all_frog_variants` | Tenez chaque variante de grenouille en laisse. | [C35](#c35), [C67](#c67) |
+| Une idée brillante ! / Glow and Behold! | `minecraft:husbandry/make_a_sign_glow` | Faites briller le texte de n'importe quel type de pancarte. | [C01](#c01), [C09](#c09), [C31](#c31) |
+| Sérieux dévouement / Serious Dedication | `minecraft:husbandry/obtain_netherite_hoe` | Utilisez un lingot de netherite pour améliorer une houe, puis réévaluez vos choix de vie. | [C24](#c24), [C33](#c33) |
+| Découverte olfactive / Smells Interesting | `minecraft:husbandry/obtain_sniffer_egg` | Obtenez un œuf de renifleur. | [C41](#c41) |
+| Trempé jusqu'aux os / Stay Hydrated! | `minecraft:husbandry/place_dried_ghast_in_water` | Placez un Ghast desséché dans de l'eau. | [C59](#c59) |
+| Paléobotaniste / Planting the Past | `minecraft:husbandry/plant_any_sniffer_seed` | Plantez une graine déterrée par un renifleur. | [C41](#c41) |
+| Prends-en de la graine ! / A Seedy Place | `minecraft:husbandry/plant_seed` | Plantez une graine et regardez-la pousser. | [C09](#c09), [C13](#c13), [C33](#c33), [C42](#c42) |
+| Une coupe au poil / Shear Brilliance | `minecraft:husbandry/remove_wolf_armor` | Retirez l'armure d'un loup en utilisant des cisailles. | [C21](#c21), [C36](#c36) |
+| Comme neuf / Good as New | `minecraft:husbandry/repair_wolf_armor` | Réparez complètement une armure pour loup endommagée avec des écailles de tatou. | [C36](#c36) |
+| Flottage de chèvre / Whatever Floats Your Goat! | `minecraft:husbandry/ride_a_boat_with_a_goat` | Montez dans un bateau avec une chèvre. | [C29](#c29), [C49](#c49) |
+| Agriculture / Husbandry | `minecraft:husbandry/root` | Le monde est plein d'amis et de nourriture | [C34](#c34) |
+| J'irai butiner chez vous / Bee Our Guest | `minecraft:husbandry/safely_harvest_honey` | Utilisez un feu de camp et une fiole pour récolter le miel d'une ruche sans provoquer les abeilles. | [C25](#c25), [C37](#c37) |
+| Dé-miel-nagement / Total Beelocation | `minecraft:husbandry/silk_touch_nest` | Déplacez un nid d'abeilles ou une ruche avec trois abeilles à l'intérieur en utilisant Toucher de soie. | [C32](#c32), [C37](#c37) |
+| Pêche tactique / Tactical Fishing | `minecraft:husbandry/tactical_fishing` | Pêchez un poisson... sans canne à pêche ! | [C38](#c38), [C40](#c40) |
+| Adopte un têtard / Bukkit Bukkit | `minecraft:husbandry/tadpole_in_a_bucket` | Attrapez un têtard avec un seau. | [C38](#c38), [C67](#c67) |
+| Meilleurs amis pour la vie / Best Friends Forever | `minecraft:husbandry/tame_an_animal` | Apprivoisez un animal. | [C35](#c35), [C36](#c36) |
+| Ça va péter ! / Uh Oh | `minecraft:husbandry/uh_oh` | Faites absorber un bloc de TNT à un cube de soufre. | [C17](#c17), [C18](#c18) |
+| Frotter / Wax Off | `minecraft:husbandry/wax_off` | Retirez la cire d'un bloc de cuivre. | [C20](#c20) |
+| Lustrer / Wax On | `minecraft:husbandry/wax_on` | Cirez un bloc de cuivre avec un rayon de miel. | [C20](#c20) |
+| Chef de meute / The Whole Pack | `minecraft:husbandry/whole_pack` | Apprivoisez toutes les variantes de loup. | [C35](#c35), [C36](#c36) |
+
+### Nether — 23 advancements
+
+| Advancement FR / EN | Identifiant vanilla | Objectif vanilla | Collections proposées |
+| --- | --- | --- | --- |
+| Comment en est-on arrivé là ? / How Did We Get Here? | `minecraft:nether/all_effects` | Appliquez sur vous-même tous les effets en même temps. | [C55](#c55) |
+| Mélanges dangereux / A Furious Cocktail | `minecraft:nether/all_potions` | Appliquez sur vous-même tous les effets de potions en même temps. | [C55](#c55) |
+| Apprenti chimiste / Local Brewery | `minecraft:nether/brew_potion` | Concoctez une potion. | [C55](#c55) |
+| Pas tout à fait "neuf" vies / Not Quite "Nine" Lives | `minecraft:nether/charge_respawn_anchor` | Chargez une ancre de réapparition à son maximum. | [C52](#c52), [C57](#c57) |
+| Fais ta balise / Bring Home the Beacon | `minecraft:nether/create_beacon` | Construisez et placez une balise. | [C56](#c56) |
+| Phare à On / Beaconator | `minecraft:nether/create_full_beacon` | Portez une balise à sa pleine puissance. | [C56](#c56) |
+| Bling-bling / Oh Shiny | `minecraft:nether/distract_piglin` | Distrayez un Piglin avec de l'or. | [C22](#c22) |
+| Voyage au bout de l'enfer / Hot Tourist Destinations | `minecraft:nether/explore_nether` | Explorez tous les biomes du Nether. | [C52](#c52), [C53](#c53), [C57](#c57), [C58](#c58) |
+| Bulle du sous-espace / Subspace Bubble | `minecraft:nether/fast_travel` | Utilisez le Nether pour parcourir 7 kilomètres à la Surface. | [C28](#c28), [C30](#c30), [C57](#c57) |
+| Le bon vieux temps / Those Were the Days | `minecraft:nether/find_bastion` | Entrez dans les vestiges d'un bastion. | [C44](#c44), [C52](#c52) |
+| Une terrible forteresse / A Terrible Fortress | `minecraft:nether/find_fortress` | Découvrez une forteresse du Nether. | [C52](#c52), [C54](#c54), [C55](#c55) |
+| Qu'on lui coupe la tête ! / Spooky Scary Skeleton | `minecraft:nether/get_wither_skull` | Obtenez un crâne de Wither squelette. | [C56](#c56), [C64](#c64) |
+| De vraies têtes de cochon / War Pigs | `minecraft:nether/loot_bastion` | Pillez un coffre dans les vestiges d'un bastion. | [C22](#c22), [C24](#c24), [C44](#c44), [C49](#c49), [C52](#c52) |
+| Couvrez-moi de débris / Cover Me in Debris | `minecraft:nether/netherite_armor` | Obtenez une armure complète en netherite. | [C24](#c24) |
+| Caché dans les profondeurs / Hidden in the Depths | `minecraft:nether/obtain_ancient_debris` | Obtenez des débris antiques. | [C24](#c24) |
+| Dans le feu de l'action / Into Fire | `minecraft:nether/obtain_blaze_rod` | Récupérez un bâton de Blaze. | [C54](#c54), [C55](#c55) |
+| Qui a coupé un oignon ? / Who is Cutting Onions? | `minecraft:nether/obtain_crying_obsidian` | Obtenez un bloc d'obsidienne pleureuse. | [C57](#c57) |
+| Retour à l'envoyeur / Return to Sender | `minecraft:nether/return_to_sender` | Détruisez un Ghast avec une boule de feu. | [C47](#c47), [C59](#c59) |
+| Bateau sur pattes / This Boat Has Legs | `minecraft:nether/ride_strider` | Chevauchez et dirigez un arpenteur avec un champignon biscornu sur un bâton. | [C35](#c35), [C58](#c58) |
+| Comme chez soi / Feels Like Home | `minecraft:nether/ride_strider_in_overworld_lava` | Emmenez un arpenteur faire une loooongue balade sur un lac de lave de la Surface. | [C35](#c35), [C58](#c58) |
+| Nether / Nether | `minecraft:nether/root` | Bienvenue en enfer | [C52](#c52), [C53](#c53) |
+| Les Witherables / Withering Heights | `minecraft:nether/summon_wither` | Invoquez le Wither. | [C56](#c56) |
+| Alliance instable / Uneasy Alliance | `minecraft:nether/uneasy_alliance` | Sauvez un Ghast du Nether, amenez-le en toute sécurité à la Surface... puis tuez-le. | [C47](#c47), [C59](#c59) |
+
+### End — 9 advancements
+
+| Advancement FR / EN | Identifiant vanilla | Objectif vanilla | Collections proposées |
+| --- | --- | --- | --- |
+| À bout de souffle / You Need a Mint | `minecraft:end/dragon_breath` | Récupérez du souffle de dragon dans une fiole. | [C55](#c55), [C62](#c62) |
+| La nouvelle génération / The Next Generation | `minecraft:end/dragon_egg` | Tenez l'œuf de dragon. | [C62](#c62) |
+| Vers l'infini et au-delà / Sky's the Limit | `minecraft:end/elytra` | Trouvez des élytres. | [C32](#c32), [C47](#c47), [C63](#c63) |
+| Escapade à distance / Remote Getaway | `minecraft:end/enter_end_gateway` | Échappez-vous de l'île. | [C60](#c60) |
+| Les mystérieuses cités de l'End / The City at the End of the Game | `minecraft:end/find_end_city` | Allez-y, que pourrait-il vous arriver ? | [C60](#c60), [C61](#c61) |
+| Libérez l'End ! / Free the End | `minecraft:end/kill_dragon` | Tuez le dragon. Bonne chance ! | [C60](#c60), [C62](#c62) |
+| Superbe panorama / Great View From Up Here | `minecraft:end/levitate` | Lévitez à 50 blocs de hauteur après l'attaque d'un Shulker. | [C61](#c61) |
+| Un air de déjà vu... / The End... Again... | `minecraft:end/respawn_dragon` | Faites réapparaître l'Ender Dragon. | [C62](#c62) |
+| L'End / The End | `minecraft:end/root` | Est-ce vraiment la fin ? | [C60](#c60) |
+
+## Variantes et cas particuliers
+
+### Les essences de bois
+
+La version étudiée contient **treize familles** : chêne (`oak`), sapin (`spruce`), bouleau (`birch`), acajou (`jungle`), acacia (`acacia`), chêne noir (`dark_oak`), palétuvier (`mangrove`), cerisier (`cherry`), chêne pâle (`pale_oak`), peuplier (`poplar`), bambou (`bamboo`), carmin (`crimson`) et biscornu (`warped`). Les termes affichés doivent être repris des traductions de la version au moment de l’interface.
+
+Chaque essence possède seulement les formes présentes dans ses données : bûche/tige ou bambou, bois/hyphes, formes écorcées, planches, dalles, escaliers, portes, trappes, barrières, portillons, boutons, plaques, pancartes, pancartes suspendues et étagères. Le bambou ajoute sa mosaïque. Feuilles, pousses, propagules et végétation ont leurs renvois botaniques ; les familles du Nether sont aussi accessibles par C53.
+
+Les bateaux de C29 forment **22 recettes** : dix bois de bateaux avec leurs versions à coffre, plus le radeau de bambou et sa version à coffre. Il n’y a ni bateau carmin/biscornu ni radeau de chêne. La collection beta.024 contient quelques identifiants de variantes absentes, ignorés par le chargeur : l’annexe de cette proposition les exclut et inclut le peuplier.
+
+### Les seize couleurs
+
+Blanc, orange, magenta, bleu clair, jaune, vert clair, rose, gris, gris clair, cyan, violet, bleu, marron, vert, rouge et noir. Elles s’appliquent aux familles réellement disponibles : teintures, laine, tapis, lits, dalles/escaliers de laine, coussins, verres/vitres colorés, terres cuites/émaillées, poudres de béton, bétons et leurs dalles/escaliers, bougies, bannières, boîtes de Shulker, sacs et harnais.
+
+Une recette de teinture ou de transmutation peut conserver les composants de l’objet source. Le document ne demande pas de remplacer ces recettes par des objets vierges : contenu des sacs et boîtes, texte des livres et propriétés des objets doivent rester conformes à la recette native.
+
+### Cuivre, soufre et cinabre
+
+Le cuivre comporte les états natif, exposé, patiné et oxydé, avec leurs versions cirées lorsque présentes. Le catalogue couvre également les outils/armures de cuivre, lances, lanternes, torches, barres, chaînes, coffres, paratonnerres et statues. L’oxydation n’est pas un craft ; le décapage et le cirage sur bloc sont des gestes. Les recettes de cirage et de taille réellement présentes restent listées individuellement.
+
+Le soufre et le cinabre appartiennent bien aux données de cette version. Ils possèdent leurs variantes de construction dans C17 et C18. Le cube de soufre, son absorption et le soufre puissant demandent des fiches de mécanique du monde ; aucun craft supplémentaire n’est inventé pour les faire entrer dans le catalogue.
+
+### Alchimie et recettes spéciales
+
+La version possède **279 JSON `minecraft:brewing`**. Il faut garder séparément chaque mélange et ses composants, pas compter uniquement les trois objets `potion`, `splash_potion` et `lingering_potion`. La collection C55 totalise **286 recettes primaires**, en ajoutant l’alambic, la fiole, le chaudron et les autres préparations/flèches qui lui sont attribuées.
+
+Le déblocage complet à « Apprenti chimiste » prolonge le choix de beta.024 : il peut révéler des mélanges dont le joueur ne possède pas encore les ingrédients rares. « À bout de souffle » ouvre également C55, mais ne prétend pas offrir un second lot exclusif si toute l’alchimie était déjà connue. Un fractionnement de l’alchimie en paliers constituerait une autre décision de conception et ne doit pas retirer des recettes déjà apprises.
+
+| Opération | Collection | Traitement |
+| --- | --- | --- |
+| Potions, conversions de contenants et allongement/renforcement | C55 | 279 identifiants réels, avec potion et contenant en résultat |
+| Flèches à effet | C55 | Recette `crafting_imbue`, composants de la potion conservés selon le jeu |
+| Vase décoré | C43 | Recette à composants ; vingt-trois tessons à découvrir, pas vingt-trois crafts de tessons |
+| Ragoût suspect | C34 | Recettes distinctes par fleur dans cette version, effets issus des données |
+| Ornement d’armure | C44 | Dix-huit duplications de modèles et dix-huit recettes d’application ; la table de forge porte le total à 37 |
+| Amélioration en netherite | C24 | Douze transformations, premier modèle à trouver puis duplication possible |
+| Bannières et bouclier décoré | C45 | Duplications et décoration réelles ; métier à tisser expliqué séparément |
+| Livres écrits et cartes | C31/C30 | Copie et agrandissement ; résultat à composants ou repris de l’entrée |
+| Feux d’artifice | C47 | Fusée, étoile et dégradé ; variations de forme/couleur/durée au sein des recettes |
+| Réparation dans la grille | C32 | Recette dynamique, pas un objet résultat fixe |
+| Teinture du cuir, des sacs, harnais, boîtes et armures pour loup | Collection de l’objet | Préserver les données de l’objet selon la recette native |
+
+### Les objets qui se trouvent et les gestes qui se font
+
+**Un objet sans recette reçoit une méthode d’obtention, jamais une recette fictive.** Cela concerne notamment les élytres, totems, tridents, œuf/tête/souffle de dragon, étoile du Nether, cœur de la mer, éclats d’écho, clés des épreuves, noyau lourd, fragments/tessons, premiers modèles de forge, plusieurs armures de montures, pomme dorée enchantée, coraux, grelampes et la plupart des disques. La duplication d’un modèle demande déjà ce modèle ; la reconstitution du disque 5 demande ses fragments.
+
+À l’inverse, **selle, laisse, étiquette et Ghast desséché ont des recettes dans cette version**. Ne pas reprendre les règles d’une ancienne version de Minecraft pour les classer comme butin exclusif.
+
+Les méthodes suivantes seront des fiches distinctes du compteur des 2 042 recettes : enchantement ; enclume ; troc et commerce ; métier à tisser ; liaison de boussole ; taille des citrouilles ; écorçage ; oxydation/décapage ; béton au contact de l’eau ; fabrication de boue dans le monde ; croissance des plantes, champignons et spéléothèmes ; abeilles ; hydratation du Ghast ; production de grelampes ; capture au seau ; assemblage de golems, Wither, portails et pyramides de balise. Leurs paramètres devront être relus dans les règles serveur avant leur implémentation.
+
+Les blocs sans objet propre — cultures, pancartes murales, fluides, bougies sur gâteau et autres états — sont rattachés à leur famille dans l’annexe. Leur présence n’impose pas d’en obtenir un exemplaire dans l’inventaire. Bedrock, cadres de portail, ardoise renforcée, générateurs et blocs de commande ne sont pas des objectifs de possession ordinaire en survie. Les œufs d’apparition vanilla restent techniques ; les familiers Sanctuary ont un contrat autonome.
+
+## Intégration proposée
+
+### Compatibilité avec beta.024
+
+Le [contrat beta.024](recipes-jei-beta024.md) et [RecipeCollections.java](../mods/sanctuary/src/main/java/fr/koka/sanctuary/recipes/RecipeCollections.java) fournissent déjà les listes `items`, `item_tags`, `advancements`, `recipes` et `recipe_types`. Chaque déclencheur est une alternative. Les recettes absentes du serveur sont ignorées ; `/reload` relit les collections et les connaissances existantes restent conservées.
+
+Les références C01 et C55 conservent **`sanctuary:basic_wood`** et **`sanctuary:brewing`**. Le classement principal de cette proposition ne retire pas les bateaux et les menuiseries du Nether déjà appris via les bases du bois en beta.024 : ces connaissances et les anciens déclenchements doivent rester valables. Les collections spécialisées ajoutent une organisation et des voies d’accès.
+
+Pour les nouvelles familles, matérialiser les listes de recettes à partir de l’annexe et les listes d’objets/découvertes à partir des familles et amorces. Ne pas utiliser un sélecteur global `minecraft:crafting` pour une famille comme le bois ou le fer : il ouvrirait des sujets sans rapport. `minecraft:brewing` reste pertinent pour l’alchimie complète, avec une décision explicite sur l’inclusion de recettes ajoutées par les mods.
+
+Exemple **illustratif et incomplet**, non installé :
+
+```json
+{
+ "advancements": ["minecraft:story/smelt_iron"],
+ "items": ["minecraft:iron_ingot"],
+ "recipes": [
+ "minecraft:iron_pickaxe",
+ "minecraft:iron_sword",
+ "minecraft:iron_spear",
+ "minecraft:bucket",
+ "minecraft:shears",
+ "minecraft:shield",
+ "minecraft:anvil"
+ ]
+}
+```
+
+Dans une livraison, cet exemple deviendra la liste complète voulue pour C21 et ses déclencheurs. Les noms FR/EN, regroupements visuels, fiches d’obtention et renvois ne sont pas des champs déjà reconnus par ce JSON : ils nécessitent leur propre interface ou format de présentation.
+
+### Ordre de livraison proposé
+
+1. **Fondations** : bois, pierre, fer, cuivre, couleurs, redstone, nourriture et élevage. Vérifier un départ neuf sans carnet consultable.
+2. **Exploration** : familles de construction, botanique, archéologie, océans, villages et épreuves.
+3. **Dimensions** : Nether, alchimie, netherite, balise, End et Shulkers, lorsque leurs ressources sont accessibles dans Sanctuary.
+4. **Recettes spéciales et fiches** : composants, transformations du monde, objets sans recette et catalogue technique.
+
+### Critères d’acceptation du futur ticket
+
+- Terminer chacun des advancements configurés enseigne les collections prévues, même sans achat du catalogue et sans les ingrédients en inventaire.
+- Découvrir un premier matériau enseigne sa famille ; une planche seule ne révèle pas les pistons. Les chemins d’amorce évitent les dépendances circulaires du lit, de l’alambic, du fabricateur et de la brosse.
+- Un même plan obtenu par deux voies n’est compté qu’une fois. Un exploit répété ou déjà acquis n’attribue ni ressource ni nouvelle possession fictive.
+- Les connaissances et identifiants existants sont conservés après mort, prestige, reconnexion et rechargement des collections. La reprise des anciens joueurs est vérifiée sans réécrire leur monde.
+- Les identifiants sont validés sur les recettes réellement chargées du serveur. Les variantes absentes échouent au contrôle de livraison ; elles ne sont pas inventées depuis un suffixe.
+- Le client reçoit seulement les informations prévues par la vue habitant ; les noms et descriptions sont disponibles en FR/EN. Les recettes à composants conservent leurs données lors de la consultation et du transfert de fabrication.
+- Un objet sans recette, un bloc sans objet et un objet technique ne sont jamais présentés comme un craft réalisable en survie.
+- Une livraison de code suit `./gradlew check build`, et `./gradlew assemblePack` si la distribution change, avec son incrément de version. Cette livraison documentaire ne change aucune version binaire.
+
+## Sources et vérification
+
+La base factuelle est l’archive vanilla de la version exacte, pas une liste mémorisée d’une ancienne version :
+
+- [Annonce officielle Minecraft Java 26.3 Pre-Release 2](https://www.minecraft.net/en-us/article/minecraft-26-3-pre-release-2), consultée le 14 septembre 2026 : contexte de version.
+- [Archive client officielle 26.3-pre-2, Mojang](https://piston-data.mojang.com/v1/objects/4bf834bcf10e94999f0ee5f17f1b5a7388b9b679/client.jar) : `data/minecraft/advancement/**/*.json`, `data/minecraft/recipe/**/*.json` et traduction anglaise. SHA-1 attendu et mesuré : **`4bf834bcf10e94999f0ee5f17f1b5a7388b9b679`**.
+- Traduction française de l’index d’assets `26.3-pre-2-34`, objet SHA-1 **`9e82765fbb08c2fc109c72a46c78ac9701faef35`** : intitulés et descriptions des advancements, noms de blocs/objets.
+- Sources Minecraft de cette même version déjà disponibles dans le cache Loom : `ItemIds`, `BlockItemIds` et `BlockIds`, avec expansion des collections de couleurs et d’états du cuivre. Les **1 658 identifiants d’objets concordent exactement avec les 1 658 définitions de modèles d’objets** de l’archive client. Les **1 286 identifiants de blocs** sont ceux déclarés par ces références, y compris ceux dont l’objet porte un autre identifiant.
+- [Version du pack](../gradle.properties), [vision](vision.md), [contrat des recettes beta.024](recipes-jei-beta024.md), [collection bois actuelle](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/basic_wood.json) et [collection alchimie actuelle](../mods/sanctuary/src/main/resources/data/sanctuary/sanctuary_recipe_collections/brewing.json) : contraintes propres à Sanctuary.
+
+Les JSON vérifiés comprennent **126 advancements affichables et 1 740 définitions sous `recipes/`**. Aucun des 126 advancements affichables ne déclare directement une liste de recettes dans `rewards.recipes` : leurs associations aux collections sont donc bien une proposition Sanctuary. Les advancements techniques de découverte de recettes continuent, eux, d’alimenter le livre natif et le carnet existant.
+
+La vérification documentaire contrôle que chaque advancement de progression a une correspondance, que les 2 042 recettes sont classées exactement une fois, que les 1 815 identifiants de blocs/objets sont couverts et que les amorces nommées existent. Elle ne constitue pas un test en jeu des futurs déblocages. Les ensembles de butins, offres commerciales et transformations du monde ne sont pas audités exhaustivement ici ; leurs fiches détaillées constituent le travail suivant, sans impact sur l’exhaustivité de l’inventaire des identifiants.
diff --git a/docs/companion-powers-balance-proposal.md b/docs/companion-powers-balance-proposal.md
new file mode 100644
index 0000000..68bac96
--- /dev/null
+++ b/docs/companion-powers-balance-proposal.md
@@ -0,0 +1,351 @@
+# Familiers — proposition d’équilibrage v1
+
+Une [proposition v2 du 14 septembre 2026](spawn-eggs-refonte-v2.md) reprend
+la fréquence d’usage, la complémentarité avec les potions et les combinaisons
+libres entre joueurs. Elle reste documentaire et non implémentée ; cette v1
+conserve son rôle de référence de la première implémentation.
+
+Passe de conception du 13 septembre 2026, pour les **88 œufs Minecraft de
+26.3-pre-2** présents dans Sanctuary. Les cinq raretés sont : **commun, peu
+commun, rare, extraordinaire, légendaire**.
+
+**Première implémentation dans [beta.019](companion-powers-beta019.md), à éprouver.**
+Les [ajustements beta.020](familiar-interactions-beta020.md) remplacent les limites
+v1 de perception et de téléportation à travers les murs, et corrigent le freinage
+des chutes. Cette proposition reste la référence initiale pour les essais ;
+elle ne constitue pas un équilibrage déjà démontré. Les pouvoirs restent désactivés
+dans beta.018. L’[inventaire historique](companion-powers-inventory.md) conserve
+les anciennes définitions de 26.2 ; ce document propose de les remplacer.
+Les 25 anciennes variantes ItsAlive restent hors de cette première passe :
+elles ne font pas partie du pack actuel.
+
+## Intention
+
+Chaque familier donne **un passif identifiable et un actif qui change un geste
+de jeu**. Un œuf commun doit pouvoir rester un choix de fin de partie. La rareté
+décrit la difficulté d’obtention et l’ampleur de l’intervention ; elle ne doit pas
+devenir un multiplicateur général de dégâts, de vie ou de rendement.
+
+On part de trois cœurs, trois barres de faim et trois bulles de respiration.
+Les familiers complètent les compétences et les aptitudes. Ils ne donnent pas
+de rang d’inventaire, de tri gratuit, de nage rapide sans Nage, de carte complète,
+de recettes inconnues, de minage groupé au-delà du rang acheté ou de vol permanent.
+Les anciens bonus permanents Force II, Résistance II, respiration aquatique
+illimitée, régénération gratuite et trêve générale avec les monstres disparaissent.
+
+| Rareté | Place dans le jeu | Obtention proposée | Recharge habituelle |
+|---|---|---|---|
+| Commun | Un geste quotidien devient plus agréable ; une petite manœuvre utile. | Découverte et premier objectif lié à l’espèce. | 20–60 s |
+| Peu commun | Une spécialité nette : potager, plongée, troupeau, déplacement, protection. | Quelques objectifs différents et une offrande adaptée. | 30–75 s |
+| Rare | Un véritable outil de métier ou une action tactique marquante. | Expédition, maîtrise d’une activité ou petite quête propre au mob. | 40–100 s |
+| Extraordinaire | Une capacité qui transforme brièvement une situation, parfois en groupe. | Défi important, lieu spécifique et composant difficile à obtenir. | 60–150 s |
+| Légendaire | Un moment exceptionnel, visible et préparé, avec une identité forte. | Aboutissement d’une grande aventure ; déblocage collectif puis épreuve personnelle. | 180 s |
+
+Les chiffres de recharge ci-dessous priment sur ces fourchettes. La rareté de
+l’œuf est indépendante de la fréquence d’apparition naturelle du mob : un
+Creeper peut être courant dans le monde et son lien de familier être rare.
+La répartition couvre **22 communs, 22 peu communs, 25 rares, 16 extraordinaires
+et 3 légendaires**. Ces nombres ne sont pas des probabilités de tirage :
+l’obtention proposée passe par des objectifs identifiés, pas par une loterie.
+
+## Règles communes proposées
+
+### Équipement, coût et rythme
+
+- Un seul familier équipé, donc un seul passif et un seul actif. Le passif prend
+ effet après 10 secondes avec le même œuf. Les améliorations temporaires qu’il
+ donne cessent au déséquipement ; cela ne supprime pas les objets déjà déplacés
+ ou les blocs déjà construits légitimement.
+- Une aptitude générale **Lien actif**, proposée à **4 niveaux**, ouvre la touche
+ de pouvoir configurable. Les passifs viennent avec le familier. La rareté
+ n’ajoute pas une série de taxes en XP. Ce nom et ce prix sont implémentés dans beta.019 ; G est proposé par défaut
+ et reste reconfigurable.
+- La recharge appartient au joueur et au slot de familier : utiliser un actif
+ bloque tous les autres jusqu’à la fin de cette recharge. Changer d’œuf, mourir,
+ se reconnecter ou passer en New Game+ ne la réinitialise pas. Les échéances
+ utilisent le temps réel du serveur et sont conservées ; le temps hors ligne
+ peut s’écouler normalement. Les effets actifs, eux, ne travaillent pas hors ligne.
+- Pas de barre de mana supplémentaire ni de coût de faim uniforme. Les dépenses
+ sont indiquées : ingrédients, lait, munition, durabilité ou faim pour un soin.
+ Une action impossible ne consomme rien et ne lance pas la recharge. Une action
+ réellement déclenchée, même esquivée, dépense ses ressources et sa recharge.
+- Aucun bonus de vie maximale, de faim maximale ou de taille de groupe miné/posé.
+ Les soins ne dépassent jamais la vie maximale. **Un cœur = 2 points de vie
+ (PV)** ; une demi-barre de faim = 1 point de nourriture.
+- Les bonus de vitesse continus et temporaires d’un même familier ne se cumulent
+ pas : prendre le plus fort. Une impulsion ponctuelle reste limitée par sa
+ distance annoncée. Une plateforme ou suspension de familier ne constitue pas
+ un nouvel appui pour relancer un pouvoir aérien.
+- Un bonus de respiration ralentit la consommation d’air, sans remplir la jauge.
+ Une réserve d’air active ajoute seulement la quantité annoncée, plafonnée à
+ la capacité achetée. Les déplacements aquatiques restent lents sans Nage.
+- Un déplacement respecte les collisions et les zones autorisées. Pas de traversée
+ d’une paroi, de porte fermée ou d’un accès réservé. Une capacité de sauvetage
+ n’immunise pas contre la lave, le vide ou une chute ultérieure.
+
+### Production, progression et informations
+
+- Le compagnon invincible reste sans collision et n’est pas une cible qui absorbe
+ les attaques. Il ne combat, ne récolte, ne pond, ne se reproduit et ne produit
+ aucune ressource de manière autonome.
+- Un actif de récolte transforme ou ramasse uniquement des ressources réelles.
+ Plantation, construction et carburant utilisent le stock du joueur ; les outils
+ perdent leur durabilité. Une case d’inventaire verrouillée reste inutilisable.
+- Les plafonds de récolte ne cumulent pas un deuxième groupe avec le vein mining.
+ Un actif exécute soit son geste spécialisé, soit le geste groupé normal. Les
+ outils requis, protections, portées et limites de compétences restent applicables.
+- Les gains de rendement plafonnent à **+10 % au total**, y compris le favori et
+ les autres futurs systèmes. Pas de bonus aux minerais, au butin de mort, à l’XP,
+ aux charges de faction, aux monnaies rares ou aux probabilités d’œufs.
+- Un bonus de production se valide sur un cycle complet avec le même propriétaire
+ et le même familier. Un seul bonus par culture, animal ou machine et par cycle :
+ équiper au moment de récolter, multiplier les joueurs ou casser/reposer ne crée
+ pas de nouveau droit. Chaque cycle reste traçable côté serveur.
+- Un repérage aide à chercher dans les chunks déjà chargés ; il ne charge pas de
+ terrain distant. Il ne révèle ni coffres privés, ni contenu de coffres fermés,
+ ni identité ou position précise d’un joueur caché, ni secret du scénario. Les marqueurs temporaires de pouvoir
+ restent locaux au monde : ils ne débloquent aucune fonction de l’Atlas.
+- Les filtres par objet ou espèce ne proposent que les découvertes personnelles.
+ Un sonar peut signaler une présence anonyme, sans révéler son nom ou compléter
+ une entrée du Blockodex à travers un mur.
+
+### Combat et coopération
+
+- Les dégâts annoncés sont **par cible, avant protection, en PvE**. Les coups
+ respectent les immunités et fenêtres d’invulnérabilité natives : pas de double
+ impact caché par tick. Les dégâts viennent du joueur, jamais d’une IA invincible.
+- Les bonus offensifs passifs concernent les monstres, pas les autres joueurs.
+ En PvP, les interventions offensives viennent des actifs annoncés ci-dessous.
+- En PvP, les dégâts additionnels de familier sont plafonnés à **2 PV par cible
+ pour toute l’activation**, y compris projectiles, zones, poison et dégâts différés.
+ Les dégâts d’une arme utilisée normalement restent ceux de l’arme ; seul son
+ supplément de familier entre dans ce plafond. Aucune pénétration d’armure en PvP.
+- La visibilité et les droits du joueur sont vérifiés au déclenchement et à
+ l’impact. Pas d’explosion qui casse le terrain, d’incendie propagé, d’attaque à
+ travers une paroi ou de commande opérateur. Les boss résistent aux déplacements
+ forcés et aux entraves, mais prennent les dégâts permis par leurs règles natives.
+- Les ralentissements PvP sont limités à **20 % et 2 secondes** ; pas d’immobilisation
+ ni d’aveuglement d’un joueur. Après une entrave de familier, une cible est protégée
+ des nouvelles entraves de familiers pendant 6 secondes. Les déplacements forcés
+ d’un joueur ne dépassent pas 1 bloc ; leurs dégâts de chute attribuables au pouvoir
+ entrent dans le plafond PvP. À défaut de cette attribution fiable, on désactive
+ la poussée PvP au premier portage.
+- Les tirs et charges ont un signal perceptible d’au moins 0,5 seconde ; les actifs
+ légendaires annoncent leur départ durant 1,5 à 2 secondes. On peut esquiver,
+ interrompre une canalisation en blessant son auteur ou se mettre à couvert.
+- Les soins et déplacements de groupe concernent le propriétaire et des alliés
+ consentants, jamais des inconnus déplacés de force. Un même buff ne se cumule pas
+ entre familiers. Un soin, rechargement ou suspension de consommation d’air impose à son bénéficiaire une
+ récupération égale à la recharge du pouvoir reçu, pour empêcher les rotations
+ de soigneurs et les échanges entre espèces. Cette récupération est commune à
+ tous les familiers pour chaque famille d’effet : soin d’un côté, air de l’autre.
+ Elle est conservée à la reconnexion et au prestige. Les bonus de réduction de dégâts venant des familiers plafonnent à
+ 25 %, sauf le coup unique explicitement protégé par une garde.
+- Les effets de peur ne fonctionnent que sur les petits monstres ordinaires,
+ pendant au plus 2 secondes sur un monstre déjà engagé. Aucun raid ou boss ne se
+ désactive. Une cible effrayée bénéficie ensuite de 10 secondes de répit.
+
+## Commun — gestes quotidiens et premières aventures
+
+| Familier | Passif | Actif proposé | Recharge |
+|---|---|---|---|
+| Chauve-souris (`bat`) | **Adaptation obscure** : après 3 s immobile dans l’obscurité, les surfaces visibles s’éclaircissent légèrement ; aucune vision à travers la roche. | **Écho** : pendant 6 s, indique la direction de trois présences vivantes maximum à 12 blocs ; aucune identité ni position précise derrière une paroi. | 30 s |
+| Chat (`cat`) | **Pattes de velours** : −20 % de dégâts de chute. | **Feulement** : repousse jusqu’à trois Creepers ou Phantoms à moins de 5 blocs et les fait hésiter 2 s ; aucune immunité ensuite. | 45 s |
+| Poule (`chicken`) | **Petites ailes** : la distance de chute tolérée augmente de 1 bloc. | **Battement** : ralentit la descente pendant 2 s, sans remonter et sans effacer la chute déjà accumulée. | 30 s |
+| Morue (`cod`) | **Petites branchies** : l’air diminue 10 % moins vite. | **Dernière bulle** : rend une bulle d’air ; utilisable seulement dans l’eau, sans dépasser la capacité achetée. | 45 s |
+| Vache (`cow`) | **Lait sélectif** : boire du lait conserve les effets bénéfiques. | **Lait partagé** : consomme un seau de lait, rend le seau vide et retire Poison/Faim au porteur et à deux alliés consentants à 3 blocs. | 60 s |
+| Âne (`donkey`) | **Pas chargé** : −10 % d’épuisement dû au sprint. | **Déchargement** : transfère au plus 18 piles existantes vers le coffre visé à portée normale, seulement des types déjà présents ; hotbar et équipement exclus. | 30 s |
+| Renard (`fox`) | **Approche discrète** : −15 % de distance de détection initiale par les animaux sauvages fuyants. | **Plongeon** : petit bond horizontal de 3 blocs, puis ramasse une pile visible à l’arrivée si une case est disponible. | 25 s |
+| Grenouille (`frog`) | **Berges faciles** : se déplacer au sol dans un bloc d’eau peu profonde est 10 % plus rapide ; ne débloque pas la nage. | **Langue** : attire une pile d’objets visible à 6 blocs ; peut aussi attirer d’un bloc un petit monstre ordinaire, sans dégâts. | 20 s |
+| Cheval (`horse`) | **Cavalier** : +10 % de vitesse à une monture terrestre réellement chevauchée, sans améliorer son saut. | **Galop** : +25 % de vitesse terrestre pendant 4 s, monté ou à pied ; exige la faim nécessaire au sprint. | 35 s |
+| Lama (`llama`) | **Caravanier** : les laisses du joueur tolèrent 2 blocs supplémentaires avant rupture. | **Crachat** : projectile à 10 blocs, 1 PV et ralentissement de 15 % pendant 2 s ; une seule cible. | 25 s |
+| Mule (`mule`) | **Sentier sûr** : −20 % de recul reçu en chevauchant une monture. | **Chargement** : transfère au plus 9 piles de stockage vers l’inventaire de sa monture visée, à 3 blocs ; aucune case ni capacité ajoutée. | 30 s |
+| Cochon (`pig`) | **Bon appétit** : +10 % de saturation obtenue avec carottes, betteraves et pommes de terre cuites ; aucun point de faim supplémentaire. | **Fouille** : indique durant 10 s jusqu’à trois végétaux récoltables déjà connus à 16 blocs, sans générer de truffe ou de butin. | 40 s |
+| Lapin (`rabbit`) | **Pied léger** : +10 % de vitesse en position accroupie. | **Bond** : saut dirigé jusqu’à 2 blocs de haut ou 3 blocs de long ; aucun enchaînement aérien. | 25 s |
+| Saumon (`salmon`) | **À contre-courant** : −25 % de poussée subie par l’eau courante. | **Remontée** : impulsion verticale jusqu’à 3 blocs dans l’eau ; ne donne pas d’air et respecte le plafond de la surface. | 30 s |
+| Mouton (`sheep`) | **Laine isolante** : le gel de la neige poudreuse s’accumule 20 % moins vite. | **Coussin** : consomme une laine pour réduire de 50 % les dégâts du prochain atterrissage dans les 8 s ; réduction plafonnée à 2 PV. | 45 s |
+| Squelette (`skeleton`) | **Main stable** : l’arc reste précis en déplacement lent, avec dispersion réduite de 15 %, sans accélérer sa charge. | **Tir repère** : la prochaine flèche tirée dans les 8 s marque sa cible 6 s pour le tireur ; dégâts normaux, munition consommée, joueurs visibles seulement en ligne de vue. | 35 s |
+| Slime (`slime`) | **Souplesse** : −15 % de dégâts de chute et −15 % de ralentissement dans les blocs de miel. | **Rebond** : réduit de 50 % un atterrissage imminent, au plus 2 PV, puis rebondit de 2 blocs maximum ; expire après 4 s, une seule fois. | 40 s |
+| Araignée (`spider`) | **Prise ferme** : +15 % de vitesse sur les échelles et les lianes. | **Accroche** : adhère à la paroi visée pendant 4 s et peut y grimper de 2 blocs maximum ; pas d’escalade permanente ni de plafond franchi. | 35 s |
+| Poulpe (`squid`) | **Eau claire** : réduit légèrement le voile sous-marin, sans voir plus loin que les chunks chargés. | **Nuage d’encre** : échappe 2 s à la détection de monstres aquatiques non-boss à 4 blocs ; contre des joueurs, produit uniquement un nuage de particules lisible. | 45 s |
+| Têtard (`tadpole`) | **Reprendre haleine** : la jauge d’air se recharge 20 % plus vite à la surface, sans augmenter sa capacité. | **Retour à la rive** : indique pendant 10 s la berge praticable la plus proche dans un rayon de 16 blocs ; pas de téléportation. | 30 s |
+| Poisson tropical (`tropical_fish`) | **Œil du récif** : les coraux et herbiers déjà découverts contrastent mieux sous l’eau à moins de 8 blocs. | **Palette vivante** : repère durant 10 s jusqu’à trois variantes de coraux ou poissons déjà connues dans un rayon de 16 blocs, sans dévoiler les inconnues. | 40 s |
+| Zombie (`zombie`) | **Tenace** : lorsque la vie est sous 30 %, le recul reçu diminue de 20 %. | **Second souffle** : transforme 1 point de nourriture en 1 PV sur 5 s ; dégâts reçus ou attaque interrompent le soin, la nourriture est déjà dépensée. | 60 s |
+
+## Peu commun — une spécialité affirmée
+
+| Familier | Passif | Actif proposé | Recharge |
+|---|---|---|---|
+| Tatou (`armadillo`) | **Carapace réflexe** : après 5 s accroupi, le prochain projectile inflige 15 % de dégâts en moins ; le bénéfice expire dès la fin de l’accroupissement. | **En boule** : garde immobile de 3 s, −40 % sur le premier coup uniquement, réduction plafonnée à 2 PV ; attaquer ou bouger la rompt. | 45 s |
+| Abeille (`bee`) | **Pollinisateur** : +10 % de progression naturelle des cultures proches à 4 blocs, seulement durant la présence active du joueur. | **Butinage** : consomme jusqu’à 4 poudres d’os pour traiter jusqu’à 4 plantes accessibles distinctes ; chaque plante reçoit une application native, sans garantie de maturité. | 45 s |
+| Dromadaire (`camel`) | **Endurance du désert** : −15 % d’épuisement du sprint sur sable et sable rouge. | **Grande enjambée** : franchit un intervalle horizontal jusqu’à 5 blocs, depuis un sol stable ; pas de charge infinie au-dessus du vide. | 40 s |
+| Araignée venimeuse (`cave_spider`) | **Habitué au venin** : −20 % de durée des nouveaux poisons reçus. | **Morsure** : la prochaine frappe de mêlée dans les 6 s ajoute jusqu’à 2 PV de poison sur 4 s ; poison non létal. | 45 s |
+| Golem de cuivre (`copper_golem`) | **Lecture du circuit** : affiche le niveau de signal du composant de redstone directement visé à portée normale. | **Diagnostic** : pendant 10 s, colore les états alimentés de 32 composants maximum appartenant au circuit exposé visé, dans un rayon de 8 blocs ; aucun signal modifié. | 40 s |
+| Dauphin (`dolphin`) | **Nage accompagnée** : +10 % de vitesse de nage, uniquement avec l’aptitude Nage et la faim requise. | **Sillage** : laisse pendant 5 s un courant de vitesse +20 % pour le porteur et deux nageurs consentants à 4 blocs ; ne fournit pas d’air. | 50 s |
+| Noyé (`drowned`) | **Main sous l’eau** : réduit de 10 % la pénalité de vitesse de minage sous-marin, sans supprimer la pénalité ni modifier les groupes. | **Ancrage** : reste stable dans l’eau pendant 6 s malgré les courants ; peut miner à portée habituelle et continue à consommer de l’air. | 40 s |
+| Poulpe luisant (`glow_squid`) | **Lueur douce** : éclairage personnel des surfaces visibles à 4 blocs sous l’eau ; aucun changement du niveau lumineux serveur. | **Balise vivante** : pose pour 20 s une balise lumineuse non solide à un point visible à 8 blocs, partagée avec le groupe ; pas de découverte ou waypoint permanent. | 45 s |
+| Chèvre (`goat`) | **Sabots** : réduit de 20 % les glissades sur glace quand on est accroupi. | **Coup de corne** : charge de 3 blocs, inflige 2 PV à la première cible et la pousse de 2 blocs maximum en PvE ; collisions respectées. | 40 s |
+| Momifié (`husk`) | **Marche aride** : les effets de Faim reçus durent 20 % moins longtemps. | **Poussière** : une zone de 3 blocs gêne 3 monstres maximum, ralentis de 20 % pendant 3 s ; aucun aveuglement de joueur. | 50 s |
+| Ocelot (`ocelot`) | **Sous-bois** : +10 % de vitesse en position accroupie dans une végétation dense, sans traverser les feuilles solides. | **Esquive** : pas latéral de 3 blocs ; aucun instant d’invulnérabilité ni passage à travers une entité solide. | 30 s |
+| Panda (`panda`) | **Gourmandise ciblée** : manger une part de gâteau donne 10 % de saturation supplémentaire. | **Culbute** : roulade de 3 blocs sur terrain libre ; réduit de 25 % le premier dégât reçu durant la seconde du mouvement, au plus 1 PV. | 40 s |
+| Perroquet (`parrot`) | **Sentinelle** : imite un monstre découvert et visible qui entre dans les 10 blocs, avec au moins 15 s entre alertes. | **Imitation** : attire l’attention de trois monstres ordinaires non engagés vers un point visible à 8 blocs pendant 4 s ; aucune cible invincible persistante. | 45 s |
+| Poisson-globe (`pufferfish`) | **Immunité partielle** : −20 % de dégâts de poison, sans supprimer les autres effets. | **Hérissement** : pendant 5 s, la première attaque de mêlée reçue applique à son auteur 2 PV de poison maximum sur 4 s, non létal ; n’annule pas le coup reçu. | 50 s |
+| Ours blanc (`polar_bear`) | **Pelage polaire** : −20 % de dégâts de gel. | **Protection du petit** : pendant 5 s, le porteur ou un allié consentant à 4 blocs subit 25 % de dégâts de mêlée en moins ; un seul bénéficiaire. | 60 s |
+| Golem de neige (`snow_golem`) | **Pas d’hiver** : le joueur ne s’enfonce plus pendant la première seconde d’un contact avec la neige poudreuse ; le gel reste possible. | **Bourrasque** : lance trois boules de neige en éventail, ralentissement 20 % pendant 2 s au plus une fois par cible ; consomme trois boules de neige, dégâts natifs. | 35 s |
+| Arpenteur (`strider`) | **Pieds chauds** : −20 % de dégâts de contact avec le magma, aucune immunité à la lave. | **Pas de braise** : pendant 3 s, permet de marcher sur la surface de lave sur 5 blocs maximum ; ne protège pas une immersion ni la fin de l’effet. | 60 s |
+| Tortue (`turtle`) | **Apnée tranquille** : l’air diminue 15 % moins vite quand le joueur reste presque immobile dans l’eau. | **Abri** : garde de 4 s, −40 % sur le premier projectile reçu, réduction plafonnée à 2 PV ; déplacement ralenti de 30 %, attaque impossible pendant la garde. | 50 s |
+| Villageois (`villager`) | **Carnet du voisinage** : mémorise les offres déjà consultées, leurs prix et l’heure de consultation ; n’actualise pas un marchand éloigné. | **Commande groupée** : exécute jusqu’à trois échanges d’une offre choisie en un geste, au prix et dans le stock actuels, puis place les résultats dans les cases libres. Fournitures réelles, aucune réservation de case verrouillée, aucun bonus d’XP par échange. | 45 s |
+| Loup (`wolf`) | **Riposte de meute** : +10 % de dégâts de mêlée contre le dernier monstre qui a blessé le porteur, durant 6 s. | **Cible de meute** : désigne un monstre visible à 12 blocs pendant 8 s ; les animaux déjà apprivoisés et réellement présents du porteur le priorisent, sans bonus de dégâts ou d’invincibilité. Ne déclenche pas une attaque collective contre un joueur. | 45 s |
+| Embourbé (`bogged`) | **Pied de marais** : +10 % de vitesse sur boue et dans l’eau peu profonde, sans nage rapide. | **Flèche des marais** : la prochaine flèche dans les 8 s porte 2 PV de poison non létal maximum sur 4 s ; utilise une flèche et l’arc normalement. | 50 s |
+| Desséché (`parched`) | **Habitué à l’épuisement** : les nouveaux effets de Faiblesse durent 20 % moins longtemps. | **Trait épuisant** : la prochaine flèche dans les 8 s réduit de 15 % les dégâts de mêlée de sa cible pendant 4 s ; ne détruit pas sa nourriture. | 50 s |
+
+## Rare — des outils de métier et de vraies manœuvres
+
+| Familier | Passif | Actif proposé | Recharge |
+|---|---|---|---|
+| Allay (`allay`) | **Ramasseur choisi** : rayon de ramassage de 4 blocs pour le seul type d’objet tenu en main secondaire ; objets libres et accessibles seulement. | **Récolte musicale** : attire pendant 8 s jusqu’à 16 piles libres du type choisi à 8 blocs, autour du joueur ou d’un bloc musical visé ; aucun accès aux conteneurs. | 45 s |
+| Axolotl (`axolotl`) | **Convalescence aquatique** : les dégâts reçus de monstres aquatiques diminuent de 10 %. | **Soin de rive** : au contact de l’eau, consomme 1 point de nourriture pour rendre 1 PV au porteur ou à un allié consentant à 4 blocs, sur 4 s ; interruption aux dégâts. | 60 s |
+| Blaze (`blaze`) | **Chaleur entretenue** : +10 % de progression de cuisson d’un four déjà allumé à 4 blocs ; consomme le carburant 10 % plus vite, sans multiplier l’XP par objet cuit. | **Salve ardente** : trois projectiles esquivables répartissant au plus 4 PV par cible, portée 16 blocs ; pas de feu au sol ni de brûlure supplémentaire. | 60 s |
+| Breeze (`breeze`) | **Lecture du vent** : les charges de vent personnelles produisent 15 % de recul en moins sur le porteur ; pas de réduction générale des explosions. | **Pas de vent** : impulsion de 5 blocs horizontalement ou 3 blocs verticalement depuis un appui, puis 1 s de descente ralentie ; une seule impulsion. | 40 s |
+| Dromadaire momifié (`camel_husk`) | **Caravane nocturne** : −15 % d’épuisement dû au sprint la nuit sur terrain sec. | **Traversée** : ruée horizontale de 6 blocs depuis le sol, sans dégâts ; le ralentissement de terrain est ignoré 2 s, mais les collisions restent actives. | 50 s |
+| Creeper (`creeper`) | **Mèche sensible** : alerte sonore lorsqu’une TNT amorcée ou un Creeper en amorçage est visible à 8 blocs ; ne désamorce rien. | **Détonation maîtrisée** : après 1 s d’annonce, souffle de rayon 3 blocs, jusqu’à 4 cibles, 4 PV chacune et petit recul ; aucun bloc détruit ni explosion à la mort. | 75 s |
+| Endermite (`endermite`) | **Récupération de perle** : −20 % de dégâts subis lors d’une téléportation par perle d’Ender. | **Petit décalage** : téléportation de 3 blocs maximum vers un sol visible et libre ; ne franchit pas de paroi, aucune annulation d’une chute engagée. | 45 s |
+| Gardien (`guardian`) | **Écailles** : −10 % de dégâts de projectiles reçus sous l’eau. | **Rayon** : verrouillage visible de 1,5 s, puis 4 PV à une cible à 12 blocs ; couper la ligne de vue ou blesser le lanceur interrompt le tir. | 60 s |
+| Hoglin (`hoglin`) | **Carrure** : −20 % de recul en mêlée. | **Percussion** : charge de 4 blocs, 4 PV à la première cible, recul PvE de 2 blocs ; s’arrête contre un obstacle. | 55 s |
+| Cube de magma (`magma_cube`) | **Peau chaude** : les embrasements reçus durent 20 % moins longtemps, sans protéger de la lave. | **Bond brûlant** : saut de 3 blocs maximum ; l’atterrissage dans les 3 s produit une onde de rayon 2 blocs, 3 PV, trois cibles maximum, sans incendie. | 55 s |
+| Mooshroom (`mooshroom`) | **Cuisine fongique** : les ragoûts aux champignons consommés donnent 10 % de saturation supplémentaire. | **Bouillon réconfortant** : consomme un ragoût aux champignons et rend son bol ; le porteur et deux alliés consentants à 4 blocs récupèrent chacun 1 PV sur 5 s. Un dégât interrompt le soin du bénéficiaire concerné ; aucune nourriture ajoutée. | 90 s |
+| Nautile (`nautilus`) | **Réserve mesurée** : l’air diminue 15 % moins vite pendant un déplacement lent. | **Cloche de plongée** : suspend la consommation d’air pendant 6 s pour le porteur immobile sous l’eau ; ne remplit pas sa jauge, bouger ou attaquer interrompt l’effet. | 75 s |
+| Phantom (`phantom`) | **Vol de nuit** : −20 % de dégâts de chute pendant la nuit. | **Glissade nocturne** : plane en descente durant 4 s maximum, sans gain d’altitude ; l’usage nocturne porte la durée à 6 s. | 65 s |
+| Piglin (`piglin`) | **Habitué du troc** : mémorise la liste des résultats de troc déjà obtenus ; les résultats inconnus restent masqués. | **Choix du marchand** : pour un seul prochain troc dans les 20 s, consomme deux lingots d’or et propose deux tirages natifs ; garde un résultat et abandonne l’autre. Pas d’amélioration des poids de rareté. | 90 s |
+| Pillard (`pillager`) | **Arbalétrier** : −10 % de temps de chargement de l’arbalète, sans modifier les munitions ou l’enchantement Charge rapide. | **Tir de couverture** : le prochain projectile d’arbalète dans les 8 s crée à l’impact une zone d’hésitation de 2 blocs pendant 3 s ; trois monstres maximum ralentis de 20 %, dégâts du projectile normaux. | 50 s |
+| Poisson d’argent (`silverfish`) | **Lecture de la pierre** : après avoir visé 2 s un bloc de pierre à portée, signale s’il est infesté ; aucun minerai révélé. | **Fissure témoin** : montre pendant 8 s les faces de cavité directement derrière le bloc visé, profondeur 2 blocs maximum ; ne révèle ni contenu, ni minerai, ni structure protégée. | 50 s |
+| Cube de soufre (`sulfur_cube`) | **Décontamination** : les nouveaux effets de Poison et de Faiblesse durent 10 % moins longtemps. | **Projection corrosive** : projectile à 10 blocs, 3 PV et dégâts de mêlée de la cible réduits de 15 % pendant 4 s ; aucune armure détruite, aucune corrosion de blocs. | 60 s |
+| Lama de marchand (`trader_llama`) | **Inventaire de caravane** : affiche les places libres des montures du joueur réellement présentes à 8 blocs. | **Convoi** : rappelle jusqu’à trois de ses montures à 12 blocs vers une place visible proche ; elles marchent, ne se téléportent pas et ne traversent pas les obstacles. | 60 s |
+| Marchand ambulant (`wandering_trader`) | **Carnet de voyage** : garde les offres et emplacements de marchands déjà rencontrés, avec date ; aucune actualisation distante ou garantie de présence. | **Étal partagé** : pendant 30 s, un allié consentant à 16 blocs peut consulter et effectuer une transaction du marchand devant le porteur avec ses propres ressources ; les deux joueurs restent proches, le prix et le stock sont inchangés. | 90 s |
+| Cheval-zombie (`zombie_horse`) | **Longue route** : la monture terrestre réellement contrôlée subit 20 % de dégâts de chute en moins ; le familier lui-même ne sert pas de monture. | **Endurance morte-vivante** : pendant 6 s, la monture et son cavalier reçoivent 20 % de recul en moins et traversent l’eau peu profonde 15 % plus vite ; pas de respiration offerte. | 60 s |
+| Zombie-villageois (`zombie_villager`) | **Médecin de campagne** : affiche l’avancement estimé d’une guérison de zombie-villageois visible à 6 blocs. | **Veille** : canalise 8 s près d’un zombie-villageois déjà en guérison pour avancer de 20 s son temps restant ; une seule application de familier par guérison, pas d’ingrédient économisé. | 90 s |
+| Piglin zombifié (`zombified_piglin`) | **Retenue** : raccourcit de 20 % la colère individuelle d’un Piglin zombifié, sans prévenir l’alerte de groupe ou pardonner une nouvelle attaque. | **Offrande d’apaisement** : consomme un lingot d’or pour interrompre 2 s la poursuite de trois Piglins zombifiés ordinaires à 5 blocs ; la colère reste enregistrée. | 75 s |
+| Vagabond (`stray`) | **Tireur du froid** : −20 % de glissade sur glace pendant la visée à l’arc. | **Trait givrant** : la prochaine flèche dans les 8 s ralentit de 20 % pendant 3 s ; munition et dégâts natifs, aucune glace placée. | 50 s |
+| Vindicateur (`vindicator`) | **Bûcheron précis** : −10 % de durée de casse des bûches à la hache, dans les limites normales du minage acheté. | **Brise-garde** : la prochaine frappe de hache dans les 6 s prolonge de 1 s une désactivation de bouclier réussie ; pas de dégâts supplémentaires. En PvP, prolongation plafonnée à 0,5 s. | 60 s |
+| Zoglin (`zoglin`) | **Instinct de survie** : −15 % de recul lorsque la vie est sous 50 %. | **Ruée furieuse** : charge de 5 blocs sans changement de direction après départ, 4 PV à la première cible ; annonce de 0,75 s qui permet l’esquive. | 60 s |
+
+## Extraordinaire — transformer une situation
+
+| Familier | Passif | Actif proposé | Recharge |
+|---|---|---|---|
+| Grinceur (`creaking`) | **Écorce** : après 3 s immobile au sol, −15 % de dégâts tant qu’on reste immobile ; attaquer interrompt ce passif. | **Enracinement** : pendant 6 s, immobile et sans attaquer, réduit les dégâts de 25 % et le recul de 50 % ; jusqu’à trois alliés consentants à 3 blocs reçoivent −10 % de dégâts tant qu’ils restent proches. | 100 s |
+| Grand gardien (`elder_guardian`) | **Bâtisseur immergé** : −15 % de pénalité de minage sous-marin, sans supprimer l’exigence d’air, d’outil ou de rang. | **Sanctuaire marin** : zone de rayon 4 blocs pendant 8 s, centrée au lancement ; le porteur et trois alliés consentants suspendent leur consommation d’air et subissent 15 % de dégâts en moins tant qu’ils y restent. | 120 s |
+| Enderman (`enderman`) | **Calme du regard** : croiser brièvement le regard d’un Enderman donne 1 s supplémentaire avant son agression ; une attaque ne bénéficie d’aucune tolérance. | **Pas de l’End** : téléportation jusqu’à 8 blocs sur un appui visible et libre, après 0,75 s de préparation ; ne traverse aucune paroi, porte fermée ou zone interdite. | 75 s |
+| Évocateur (`evoker`) | **Étude des maléfices** : les nouveaux effets de Faiblesse et de Lenteur durent 15 % moins longtemps. | **Ligne de crocs** : après 1 s d’annonce, dessine une ligne de 8 blocs sur un sol continu visible ; 5 PV par cible au maximum, trois cibles maximum, aucun croc derrière un mur. | 90 s |
+| Ghast (`ghast`) | **Artillerie aérienne** : les projectiles renvoyés par le joueur ont 10 % de vitesse supplémentaire, sans bonus de dégâts. | **Boulet spectral** : projectile lent et renvoyable, portée 24 blocs, explosion de rayon 2 blocs pour 5 PV, quatre cibles maximum ; terrain et feu inchangés. | 90 s |
+| Ghast joyeux (`happy_ghast`) | **Main du bâtisseur** : tient la position 0,5 s après avoir quitté accidentellement un bord en mode accroupi ; une fois par appui stable de 2 s, aucun escalier dans le vide. | **Plateforme de nuage** : crée à côté d’un bloc d’appui visible une plateforme personnelle de 3 × 3 pour 8 s, dans la portée de pose ; aucun objet ou bloc ne peut y être construit, elle ne porte ni passager ni mécanisme et avertit avant de disparaître. | 120 s |
+| Golem de fer (`iron_golem`) | **Présence protectrice** : le premier coup de mêlée reçu après 10 s sans dégâts inflige 15 % de dégâts en moins. | **Interposition** : pendant 5 s, le porteur et deux alliés consentants à 3 blocs réduisent les dégâts de mêlée de 25 % et ne sont pas projetés en l’air ; le porteur avance 20 % moins vite. | 90 s |
+| Piglin barbare (`piglin_brute`) | **Garde d’or** : porter au moins une pièce d’armure d’or réduit le recul de mêlée de 15 % ; aucune trêve avec les brutes. | **Contre brutal** : fenêtre de parade frontale de 1 s avec arme de mêlée ; si un coup est paré, réduit ses dégâts de 40 %, au plus 2 PV, puis la prochaine frappe dans les 3 s ajoute 2 PV en PvE. | 75 s |
+| Ravageur (`ravager`) | **Masse** : −25 % de recul terrestre, sans bonus de dégâts permanent. | **Percée** : charge annoncée de 6 blocs, heurte jusqu’à trois cibles, 5 PV chacune ; s’arrête devant le premier bloc solide et ne casse aucune culture. | 90 s |
+| Shulker (`shulker`) | **Prudence verticale** : −20 % de durée de Lévitation reçue. | **Ancrage de chantier** : reste suspendu à sa position actuelle pendant 6 s maximum, à moins de 4 blocs d’un appui solide ; peut poser à portée normale, sans ascension. Se déplacer annule l’effet, avertissement avant la chute. | 100 s |
+| Cheval-squelette (`skeleton_horse`) | **Cavalier des fonds** : l’air du cavalier diminue 15 % moins vite lorsqu’il est sur une monture immergée. | **Traversée des eaux** : le cavalier et sa monture marchent sur l’eau pendant 6 s et 10 blocs au maximum ; aucun effet sur la lave, aucune protection après expiration. | 90 s |
+| Renifleur (`sniffer`) | **Mémoire botanique** : en visant une plante connue, indique ses conditions de croissance manquantes observables : lumière, substrat ou eau, selon son espèce. | **Piste ancienne** : pendant 15 s, indique une direction approximative vers du sable ou du gravier suspect à 24 blocs, types déjà découverts et chunks déjà chargés ; ne lit pas le butin, ne détecte pas une salle fermée ou un lieu réservé au scénario. | 120 s |
+| Vex (`vex`) | **Esprit agile** : −20 % de durée de ralentissement dû aux effets reçus, sans traversée des collisions. | **Main spectrale** : atteint un bloc de construction visible 2 blocs au-delà de la portée habituelle pour une seule pose ou récupération autorisée ; exige outil, matériaux et rang, jamais de coffre ou mécanisme distant. | 60 s |
+| Sorcière (`witch`) | **Dosage précis** : les potions bues par le porteur durent 10 % plus longtemps ; aucune amplification de niveau ni changement des effets instantanés. | **Fiole partagée** : consomme une potion buvable choisie et transmet à deux alliés consentants à 4 blocs sa moitié de durée, ou sa moitié de soin instantané, chacun ; ne transforme pas une potion en objet revendable. | 100 s |
+| Wither squelette (`wither_skeleton`) | **Os calcinés** : les nouveaux effets de Wither durent 20 % moins longtemps. | **Entaille sombre** : la prochaine frappe de mêlée dans les 6 s applique au plus 3 PV de flétrissement sur 4 s et réduit les soins reçus de 20 % pendant 4 s ; soin réduit de 10 % et durée 2 s en PvP. | 75 s |
+| Nautile-zombie (`zombie_nautilus`) | **Sang-froid abyssal** : à moins d’un tiers d’air, le recul reçu sous l’eau diminue de 25 %, sans restaurer d’air. | **Sauvetage** : propulse vers le haut, sur 6 blocs maximum, le porteur et un allié immergé consentant à 3 blocs ; rend une bulle à chacun, sans dépasser leur capacité. Plafonds et obstacles arrêtent la remontée. | 120 s |
+
+## Légendaire — trois moments exceptionnels
+
+| Familier | Passif | Actif proposé | Recharge |
+|---|---|---|---|
+| Ender Dragon (`ender_dragon`) | **Maîtrise du ciel** : −20 % de dégâts de collision en élytres et de projectiles reçus en l’air ; aucune armure supplémentaire au sol. | **Essor draconique** : après 2 s d’annonce au sol, soulève de 8 blocs le porteur et trois alliés consentants à 4 blocs, puis permet 10 s de vol plané, déplacement total plafonné à 40 blocs. Aucun maintien d’altitude, aucune deuxième impulsion ; descente signalée, pas d’immunité à l’arrivée. | 180 s |
+| Warden (`warden`) | **Écoute profonde** : perçoit la direction de vibrations à 8 blocs, sans nom, silhouette ou coordonnées exactes ; les joueurs accroupis ne sont pas révélés. | **Onde sonique** : après 2 s de canalisation sonore, frappe dans une ligne de 12 blocs jusqu’à trois cibles visibles pour 10 PV chacune en PvE, interrompt leur attaque si elle est interruptible et détruit les projectiles hostiles rencontrés. Pas de dégâts à travers un mur, ni de pénétration d’armure en PvP. | 180 s |
+| Wither (`wither`) | **Volonté funèbre** : quand la vie passe sous 30 %, les nouveaux ralentissements durent 25 % moins longtemps ; aucune résurrection ni explosion à la mort. | **Moisson des ombres** : après 1,5 s d’annonce, tire trois crânes lents et esquivables vers les cibles visées à 16 blocs, au maximum 12 PV par cible et trois cibles au total. Les dégâts réellement infligés rendent 10 % de leur valeur au porteur, plafonnés au plus petit de 2 PV ou 20 % de sa vie maximale. Aucun vol de vie sur alliés, familiers, animaux apprivoisés ou cibles invulnérables ; aucun terrain détruit. | 180 s |
+
+## Obtenir les œufs sans transformer les animaux en distributeurs
+
+**Toute cette acquisition est proposée : beta.018 ne la contient pas.**
+
+1. Découvrir personnellement l’espèce en jeu ouvre sa fiche de lien dans le
+ Blockodex. Observer ne donne pas directement un œuf ou une série d’XP.
+2. Réussir un objectif approprié débloque une première essence : s’occuper d’un
+ animal, réaliser une production variée, accomplir une exploration ou maîtriser
+ une interaction avec un monstre. Pas de « tuer 500 exemplaires » ni de minuterie
+ AFK pour les obtenir.
+3. L’offrande fournit le coût matériel. Les objectifs et leurs récompenses sont
+ enregistrés par habitant et monde ; New Game+ ne distribue pas à nouveau les
+ premières récompenses. Les duplicatas éventuels demandent une recette ou un
+ échange explicite, pas une nouvelle validation de la même découverte.
+4. Le familier peut être conservé au prestige. Sa puissance n’augmente pas avec
+ le chiffre romain, et ses réserves respectent toujours les compétences du
+ personnage courant. Aucun prestige supplémentaire n’est requis pour rééquiper
+ un familier déjà acquis.
+
+Exemples de pistes d’obtention, à transformer ensuite en vrais objectifs :
+
+| Œuf | Objectif proposé |
+|---|---|
+| Poule — commun | Découvrir, nourrir et accompagner la croissance d’une poule réelle. |
+| Squelette — commun | Observer son arc et bloquer un tir avec un bouclier ; aucune ferme à kills requise. |
+| Abeille — peu commun | Installer un rucher et récolter miel puis cire en préservant ses habitantes. |
+| Golem de cuivre — peu commun | Construire puis diagnostiquer un premier circuit fonctionnel. |
+| Allay — rare | Rencontrer un Allay, lui confier un objet et organiser une collecte près d’un bloc musical. |
+| Piglin — rare | Découvrir plusieurs résultats de troc distincts ; pas acheter cent fois le même résultat. |
+| Golem de fer — extraordinaire | Participer à la défense d’un village puis à sa remise en état, avec des objectifs vérifiables. |
+| Renifleur — extraordinaire | Mener une séquence d’archéologie, faire éclore un véritable renifleur et cultiver ses plantes. |
+| Ender Dragon — légendaire | L’achèvement collectif de l’aventure de l’End ouvre le lien ; chaque habitant réalise ensuite son épreuve d’exploration. |
+| Warden — légendaire | Réussir une expédition de discrétion dans les profondeurs et rapporter une archive ; tuer le Warden n’est pas le passage obligé. |
+| Wither — légendaire | Participer au défi du Wither puis à la construction d’une balise utile à la communauté ; pas recevoir un œuf à chaque boss farmé. |
+
+Le déblocage collectif ne remet pas automatiquement un légendaire à tous les
+comptes. La contribution personnelle doit rester accessible aux nouveaux venus
+après l’événement, par une épreuve rejouable dont la récompense est unique pour
+chaque habitant. Les noms d’épreuves ci-dessus sont des intentions, pas des
+advancements déjà présents.
+
+L’objet obtenu en survie devra être un **œuf de familier inerte** : même apparence
+d’espèce et même slot, mais impossible à utiliser comme œuf d’apparition vanilla
+dans le monde ou dans un distributeur. Sans cette distinction, l’obtention d’un
+œuf de Wither, de villageois ou de renifleur ferait aussi apparaître des entités
+et casserait l’équilibre de production. Il faudra spécifier le contrat d’objet,
+sa provenance serveur et la migration des œufs de test avant tout portage ;
+aucune transformation des objets actuels n’est faite ici.
+
+Les règles actuelles de mort restent la référence : l’œuf équipé tombe si
+keepInventory est désactivé. Une récupération/recréation d’essence perdue et un
+éventuel commerce de familiers demandent leurs propres règles avant ouverture en
+survie. En première expérimentation, proposer le lien comme personnel et les
+objets de test comme opérateur évite de décider silencieusement de cette économie.
+
+## Première série de tests d’équilibrage
+
+Commencer par **Poule, Vache, Âne, Abeille, Golem de cuivre, Axolotl, Creeper,
+Enderman et Shulker**. Ce petit ensemble couvre chute, soin, transfert, production,
+information, dégâts, téléportation et construction. Il vérifie les règles communes
+avant de multiplier les effets. Les légendaires viennent après cette mesure.
+
+| Situation | Ce qu’il faut vérifier |
+|---|---|
+| Nouveau personnage à 3–3–3 | Aucun passif ne permet d’ignorer durablement faim, air ou chute ; un actif utile ne suppose pas un inventaire rempli de potions. |
+| Compétences maximales | Un commun est encore utile pour un métier ; il n’est pas remplacé automatiquement par un légendaire. |
+| Après New Game+ | Les réserves d’air, les soins et la portée groupée se bornent aux nouveaux rangs ; aucun coût ou délai réinitialisé. |
+| PvP à trois cœurs | Au plus un cœur de dégâts additionnels par activation et par cible, y compris poison, feu, impacts multiples et chute attribuable ; annonce et esquive réelles. |
+| 30 joueurs réunis | Pas de chaîne de peur, de boucliers ou de bulles d’air infinie ; pas de cumul de bonus de production et pas de recherche de chunks distants. |
+| Échanges et déséquipement | Aucun buff stocké en changeant d’œuf, aucune potion/munition/ressource dupliquée, une seule recharge commune persistante. |
+| Claims et lieux à secrets | Téléportations, silhouettes, main spectrale et plateformes respectent les accès ; les pouvoirs ne court-circuitent pas une salle fermée. |
+| Ergonomie | Prévisualiser la portée, l’appui, les bénéficiaires, les matériaux et la recharge ; un refus explique ce qui manque sans consommer l’action. |
+
+Mesurer le temps gagné sur une vraie tâche, le taux de survie et la fréquence
+d’utilisation. Un passif constamment équipé dans tous les contextes est suspect ;
+un actif jamais utilisé malgré sa disponibilité doit être renforcé ou remplacé.
+Les +10 % ne sont pas une garantie : les pouvoirs de construction et d’information
+peuvent gagner bien plus de temps qu’un petit bonus de dégâts.
+
+## Points à résoudre avant implémentation
+
+- Les bonus permanents de vitesse de production, les échanges à distance et les
+ plateformes demandent un état serveur dédié : ce
+ sont des contrats à écrire, pas de simples effets de potion à importer.
+- Le Renifleur ne génère aucun site ni butin. Le filtre excluant lieux protégés
+ et salles fermées doit être fiable avant d’activer sa recherche.
+- La main spectrale du Vex étend uniquement un geste de construction explicite ;
+ son interaction avec les structures protégées devra être testée avant activation.
+- Le premier ticket de pouvoirs inclura configuration serveur, libellés FR/EN,
+ attribution PvP et contrat de sauvegarde des recharges. Cette passe documentaire
+ ne modifie ni version, ni monde, ni binaire.
diff --git a/docs/companion-powers-beta019-coverage.md b/docs/companion-powers-beta019-coverage.md
new file mode 100644
index 0000000..2a7d45a
--- /dev/null
+++ b/docs/companion-powers-beta019-coverage.md
@@ -0,0 +1,107 @@
+# beta.019 — couverture des familiers
+
+Les 88 passifs et 88 actifs disposent d’un chemin d’exécution. Les chiffres et
+conditions de chaque pouvoir restent détaillés dans la [proposition v1](companion-powers-balance-proposal.md).
+
+La suite client extrait les **88 modèles natifs**, à deux instants, en contrôlant
+leur éclairage et leur échelle. Cela ne constitue pas 88 essais manuels de pouvoirs.
+Le tableau distingue les contrôles comportementaux propres à une espèce des
+contrats communs (achat, persistance, coûts, portées, consentement et interruptions).
+La balance en partie réelle reste à éprouver ; « modèle seulement » signifie
+que le gameplay de cette espèce ne possède pas encore de scénario automatique dédié.
+
+Points d’entrée : `CompanionPassives` et les mixins `Companion*` pour les passifs ;
+`CompanionActions`, `CompanionWork`, `CompanionQueries`, `CompanionTrades`,
+`CompanionPlatform` et `CompanionProjectiles` pour les actifs. Les tests sont dans
+`Companion019GameTests`, `Companion019ClientChecks` et `Companion019Smoke`.
+
+| Familier | Passif implémenté | Actif implémenté | Vérification spécifique |
+| --- | --- | --- | --- |
+| Chauve-souris (`bat`) | Adaptation obscure | Écho (30 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Chat (`cat`) | Pattes de velours | Feulement (45 s) | Attribut de chute et suppression au changement d’œuf ; consentement. |
+| Poule (`chicken`) | Petites ailes | Battement (30 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Morue (`cod`) | Petites branchies | Dernière bulle (45 s) | Ajout d’air plafonné à trois bulles et récupération du bénéficiaire. |
+| Vache (`cow`) | Lait sélectif | Lait partagé (60 s) | Nettoyage natif du lait, conservation des effets bénéfiques, seau consommé/rendu. |
+| Âne (`donkey`) | Pas chargé | Déchargement (30 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Renard (`fox`) | Approche discrète | Plongeon (25 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Grenouille (`frog`) | Berges faciles | Langue (20 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Cheval (`horse`) | Cavalier | Galop (35 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Lama (`llama`) | Caravanier | Crachat (25 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Mule (`mule`) | Sentier sûr | Chargement (30 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Cochon (`pig`) | Bon appétit | Fouille (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Lapin (`rabbit`) | Pied léger | Bond (25 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Saumon (`salmon`) | À contre-courant | Remontée (30 s) | Navigation aquatique après le déplacement laborieux au sol. |
+| Mouton (`sheep`) | Laine isolante | Coussin (45 s) | Consommation dans les seules cases ouvertes ; coussin limité et utilisé une fois. |
+| Squelette (`skeleton`) | Main stable | Tir repère (35 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Slime (`slime`) | Souplesse | Rebond (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Araignée (`spider`) | Prise ferme | Accroche (35 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Poulpe (`squid`) | Eau claire | Nuage d’encre (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Têtard (`tadpole`) | Reprendre haleine | Retour à la rive (30 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Poisson tropical (`tropical_fish`) | Œil du récif | Palette vivante (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Zombie (`zombie`) | Tenace | Second souffle (60 s) | Achat, persistance de recharge, soin progressif payé en faim et interrompu par dégâts. |
+| Tatou (`armadillo`) | Carapace réflexe | En boule (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Abeille (`bee`) | Pollinisateur | Butinage (45 s) | Propriétaire du cycle de culture, interruption sans crédit récupérable. |
+| Dromadaire (`camel`) | Endurance du désert | Grande enjambée (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Araignée venimeuse (`cave_spider`) | Habitué au venin | Morsure (45 s) | Réduction native de durée du poison. |
+| Golem de cuivre (`copper_golem`) | Lecture du circuit | Diagnostic (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Dauphin (`dolphin`) | Nage accompagnée | Sillage (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Noyé (`drowned`) | Main sous l’eau | Ancrage (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Poulpe luisant (`glow_squid`) | Lueur douce | Balise vivante (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Chèvre (`goat`) | Sabots | Coup de corne (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Momifié (`husk`) | Marche aride | Poussière (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Ocelot (`ocelot`) | Sous-bois | Esquive (30 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Panda (`panda`) | Gourmandise ciblée | Culbute (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Perroquet (`parrot`) | Sentinelle | Imitation (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Poisson-globe (`pufferfish`) | Immunité partielle | Hérissement (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Ours blanc (`polar_bear`) | Pelage polaire | Protection du petit (60 s) | Révocation immédiate d’un effet allié. |
+| Golem de neige (`snow_golem`) | Pas d’hiver | Bourrasque (35 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Arpenteur (`strider`) | Pieds chauds | Pas de braise (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Tortue (`turtle`) | Apnée tranquille | Abri (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Villageois (`villager`) | Carnet du voisinage | Commande groupée (45 s) | Coûts natifs, limites de stock, objets produits et rejet du rejeu du devis. |
+| Loup (`wolf`) | Riposte de meute | Cible de meute (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Embourbé (`bogged`) | Pied de marais | Flèche des marais (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Desséché (`parched`) | Habitué à l’épuisement | Trait épuisant (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Allay (`allay`) | Ramasseur choisi | Récolte musicale (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Axolotl (`axolotl`) | Convalescence aquatique | Soin de rive (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Blaze (`blaze`) | Chaleur entretenue | Salve ardente (60 s) | Progression réelle du four, carburant accéléré et sortie unique. |
+| Breeze (`breeze`) | Lecture du vent | Pas de vent (40 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Dromadaire momifié (`camel_husk`) | Caravane nocturne | Traversée (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Creeper (`creeper`) | Mèche sensible | Détonation maîtrisée (75 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Endermite (`endermite`) | Récupération de perle | Petit décalage (45 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Gardien (`guardian`) | Écailles | Rayon (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Hoglin (`hoglin`) | Carrure | Percussion (55 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Cube de magma (`magma_cube`) | Peau chaude | Bond brûlant (55 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Mooshroom (`mooshroom`) | Cuisine fongique | Bouillon réconfortant (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Nautile (`nautilus`) | Réserve mesurée | Cloche de plongée (75 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Phantom (`phantom`) | Vol de nuit | Glissade nocturne (65 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Piglin (`piglin`) | Habitué du troc | Choix du marchand (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Pillard (`pillager`) | Arbalétrier | Tir de couverture (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Poisson d’argent (`silverfish`) | Lecture de la pierre | Fissure témoin (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Cube de soufre (`sulfur_cube`) | Décontamination | Projection corrosive (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Lama de marchand (`trader_llama`) | Inventaire de caravane | Convoi (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Marchand ambulant (`wandering_trader`) | Carnet de voyage | Étal partagé (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Cheval-zombie (`zombie_horse`) | Longue route | Endurance morte-vivante (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Zombie-villageois (`zombie_villager`) | Médecin de campagne | Veille (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Piglin zombifié (`zombified_piglin`) | Retenue | Offrande d’apaisement (75 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Vagabond (`stray`) | Tireur du froid | Trait givrant (50 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Vindicateur (`vindicator`) | Bûcheron précis | Brise-garde (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Zoglin (`zoglin`) | Instinct de survie | Ruée furieuse (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Grinceur (`creaking`) | Écorce | Enracinement (100 s) | Déplacement annule racines et résistance liée. |
+| Grand gardien (`elder_guardian`) | Bâtisseur immergé | Sanctuaire marin (120 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Enderman (`enderman`) | Calme du regard | Pas de l’End (75 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Évocateur (`evoker`) | Étude des maléfices | Ligne de crocs (90 s) | Crocs visuels temporaires exclus des sauvegardes. |
+| Ghast (`ghast`) | Artillerie aérienne | Boulet spectral (90 s) | Vol ; rendu client. |
+| Ghast joyeux (`happy_ghast`) | Main du bâtisseur | Plateforme de nuage (120 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Golem de fer (`iron_golem`) | Présence protectrice | Interposition (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Piglin barbare (`piglin_brute`) | Garde d’or | Contre brutal (75 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Ravageur (`ravager`) | Masse | Percée (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Shulker (`shulker`) | Prudence verticale | Ancrage de chantier (100 s) | Destruction du support arrête la suspension. |
+| Cheval-squelette (`skeleton_horse`) | Cavalier des fonds | Traversée des eaux (90 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Renifleur (`sniffer`) | Mémoire botanique | Piste ancienne (120 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Vex (`vex`) | Esprit agile | Main spectrale (60 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Sorcière (`witch`) | Dosage précis | Fiole partagée (100 s) | Potion réelle partagée, demi-durée, maintien d’une potion ordinaire ultérieure. |
+| Wither squelette (`wither_skeleton`) | Os calcinés | Entaille sombre (75 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Nautile-zombie (`zombie_nautilus`) | Sang-froid abyssal | Sauvetage (120 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Ender Dragon (`ender_dragon`) | Maîtrise du ciel | Essor draconique (180 s) | Déplacement aérien effectif, espace libre ; rendu client. |
+| Warden (`warden`) | Écoute profonde | Onde sonique (180 s) | Modèle natif ; essais gameplay spécifiques à poursuivre. |
+| Wither (`wither`) | Volonté funèbre | Moisson des ombres (180 s) | Plafonds PvE/PvP partagés, exclusion des apprivoisés ; vol. |
diff --git a/docs/companion-powers-beta019.md b/docs/companion-powers-beta019.md
new file mode 100644
index 0000000..ac13c38
--- /dev/null
+++ b/docs/companion-powers-beta019.md
@@ -0,0 +1,152 @@
+# beta.019 — pouvoirs des 88 familiers
+
+Ticket ouvert le 13 septembre 2026, branche `codex/companion-powers-beta019`.
+
+### États ajoutés et déplacements
+
+La livraison inclut les déplacements propres aux familles : vol orbital du
+Dragon, vol du Wither et des Ghasts, nage et difficultés des aquatiques hors de
+l’eau. Le familier reste temporaire, invincible et sans combat autonome.
+
+Les cycles de culture et de four utilisent de nouvelles pièces jointes JSON
+versionnées `sanctuary:companion_crop_cycles` sur le niveau et
+`sanctuary:companion_furnace_cycle` sur le four. Absence : aucun cycle antérieur
+crédité. Un cycle déjà entamé ne gagne pas de propriétaire rétroactivement.
+La présence continue, le même propriétaire et le même équipement sont vérifiés ;
+quitter la zone, changer de lien ou arrêter le serveur invalide le cycle en cours.
+Les fractions ne sont jamais transférées à un nouveau cycle. Aucun objet,
+résultat, carburant, niveau d’XP ou bloc de sauvegarde existant n’est converti.
+Un JSON invalide ou d’une version future est conservé et bloque seulement ce
+bonus. Les nouveaux tags peuvent être ignorés lors d’un retour à beta.018.
+La guérison du zombie-villageois reçoit un booléen persistant indépendant,
+`sanctuary:companion_cure_used`, absent par défaut, limitant Veille à une fois.
+Les devis, choix de troc et plateformes sont temporaires et ne survivent pas à
+une déconnexion. Un choix de troc abandonné perd ses deux lingots déjà offerts ;
+il ne fournit aucun des deux résultats.
+**Livraison locale beta.019 vérifiée — 13 septembre 2026.**
+
+Le créateur autorise l’implémentation des 88 passifs et 88 actifs de la
+[proposition v1](companion-powers-balance-proposal.md), et la correction du rendu
+noirci des compagnons. Les rangs I–III, les soins faisant évoluer le lien et les
+œufs Anomaly à contreparties sont explicitement reportés au prochain ticket.
+Aucun malus solaire ou autre malus historique n’est ajouté aux œufs ordinaires.
+
+## Contrat de stockage et de migration
+
+Les accessoires beta.018 et leurs identifiants restent inchangés. Aucun monde,
+chunk ou objet d’une installation personnelle n’est converti par les outils de
+développement. Les nouvelles données se créent uniquement quand le serveur du
+mod exécutera les nouvelles fonctionnalités.
+
+Le lien actif et les délais des pouvoirs utilisent un état joueur indépendant,
+versionné et persistant, copié à la mort et conservé au New Game+. L’absence de
+cet état signifie lien non acheté, aucune recharge. Une version inconnue ou des
+données invalides désactivent les pouvoirs de ce joueur sans effacer son état.
+L’achat utilise les niveaux réellement disponibles côté serveur. Les identifiants
+et achats des anciennes aptitudes ne sont pas renommés et leur stockage n’est
+pas réécrit pour ajouter ce lien indépendant.
+
+Les recharges reposent sur les échéances du serveur et sont communes au slot.
+Le déséquipement interrompt les améliorations temporaires. Reconnexion, mort et
+prestige ne donnent pas de nouvelle charge. Les améliorations de soin et d’air
+possèdent aussi leur récupération par bénéficiaire.
+
+La configuration dédiée `config/sanctuary/companion-powers.json` propose les
+valeurs v1, les cinq raretés et les réglages serveur. Les clients reçoivent une
+vue bornée de leur propre état. Les cibles, ressources, droits, dégâts et
+transactions sont contrôlés côté serveur. Le consentement aux effets alliés
+est explicite et révocable ; l’appartenance à une faction ne le remplace pas.
+
+Les œufs natifs restent les objets de test déjà utilisés en beta.018.
+L’obtention des essences en survie et leur forme inerte sont un chantier distinct
+de l’activation des pouvoirs demandée ici : aucun œuf natif n’est distribué par
+un advancement et aucune récompense d’acquisition n’est inventée.
+
+## Livraison et vérifications
+
+La livraison comprend une interface FR/EN lisible pour le lien actif,
+le familier équipé, sa rareté, ses deux effets, sa recharge et son raccourci.
+Le rendu est contrôlé dans le client avec les modèles natifs et leur lumière
+ambiante ; la couverture des scénarios est détaillée ci-dessous. Les vérifications couvrent les transactions, les interruptions,
+le PvP, les portées, l’absence de chargement distant, les capacités initiales et
+maximales, les échanges d’œufs, la mort et le prestige.
+
+Les pouvoirs et leurs vérifications sont [recensés individuellement](companion-powers-beta019-coverage.md). Une
+déclaration de registre ou une description affichée ne prouve pas à elle seule
+que son effet fonctionne. `check build` et `assemblePack` sont exécutés pour
+la livraison locale beta.019. Le canal packwiz et Prism ne sont pas modifiés
+sans demande de mise à jour correspondante.
+
+## Utilisation de la version d’essai
+
+Équiper un œuf dans sa case de familier. Son passif s’active après **10 secondes**
+avec le même œuf. **B → Progression → Aptitudes → Lien actif** coûte **4 niveaux**
+par défaut ; **G**, configurable dans Contrôles, déclenche le pouvoir.
+Le panneau **Familier** indique la rareté, le passif, l’actif, les conditions et
+la recharge. Il contient le consentement aux effets alliés et un bouton opérateur
+pour préparer un nouvel essai. Les achats, temps de recharge et récupérations
+soin/air sont stockés séparément des compétences et conservés à la mort et au prestige.
+
+Viser ce sur quoi agir : coffre, monstre, villageois ou surface. Les objets
+nécessaires se prennent dans les rangées ouvertes. Le villageois ouvre un devis
+aux prix et stocks natifs ; le Piglin offre deux tirages après paiement. Un choix
+en attente peut être rouvert dans le panneau Familier, par exemple après avoir
+libéré une case. Les relevés marchands s’affichent dans le carnet.
+
+Les équipes Minecraft et les membres d’une même faction Sanctuary sont exclus
+des cibles offensives. Recevoir les aides reste un consentement personnel,
+indépendant de la faction. Les poussées offensives PvP sont désactivées dans ce
+premier portage, comme prévu par la proposition.
+
+Les familiers utilisent des navigations terrestres, volantes ou aquatiques ;
+le Dragon décrit une orbite, le Wither et les Ghasts restent en vol. Poissons,
+calmars, gardiens, dauphins et nautiles nagent dans l’eau et se débattent au sol.
+Grenouilles, lapins et cubes bondissent ; les amphibies peuvent aussi nager.
+Ces déplacements sont propres au compagnon : aucune IA hostile native n’est importée.
+Le rendu réutilise les modèles et animations natifs, avec l’éclairage calculé à
+la position réelle du petit compagnon. Les anciens proxys non tickés conservaient
+une position précédente à l’origine, ce qui faussait l’échantillonnage de lumière.
+
+## Limites de cette première implémentation
+
+Les rangs I–III, œufs Anomaly, malus et évolution par les soins restent au prochain
+ticket. L’obtention des œufs en survie reste à concevoir. Aucun équilibrage de
+partie à 30 joueurs ni comportement de chaque actif en toutes circonstances
+n’est revendiqué. La [couverture](companion-powers-beta019-coverage.md) distingue
+les scénarios vérifiés des essais à poursuivre.
+
+La lumière du calmar luisant est une indication visuelle personnelle sur les
+surfaces cubiques exposées, sans modifier la lumière du monde ni les règles de
+spawn. Les signaux redstone sont un relevé du circuit au déclenchement. Les
+variantes de poissons tropicaux doivent avoir été observées personnellement ;
+leur mémoire utilise une note `seen_fish_v1` bornée, dérivée des identifiants
+natifs de motif et de couleurs. Ces indices ne révèlent aucune entrée via l’Atlas.
+Les permissions passent par les règles natives et le point d’intégration
+`CompanionWorld.ALLOW` ; les futurs claims devront s’y raccorder.
+
+## Proximité du joueur et transparence
+
+Les familiers volants passent progressivement de transparents à opaques entre
+**0,6 et 3,5 blocs** du regard du joueur. La distance à la caméra est également
+prise en compte en troisième personne. La courbe est continue et adoucie aux
+extrémités : aucun changement brutal d’opacité au seuil. Le corps et les couches
+des yeux partagent ce fondu, y compris pour le Dragon et le Wither.
+
+Le fondu appartient uniquement au rendu du familier volant. Il n’agit ni sur
+les monstres du monde ni sur les collisions, pouvoirs ou déplacements. La
+couleur et le matériau translucide sont inscrits dans chaque modèle envoyé au
+rendu différé ; l’opacité ne reste jamais active pour l’entité suivante.
+
+## Vérifications de livraison
+
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=companions,accessories,movement,progression,cycle,mining,building,inventory,inventoryflow,sorting,tutorial` : contrôles purs et **91 tests serveur réussis**.
+- Parcours client natif sur un nouveau monde Sanctuary de développement, graine 42 : achat à quatre niveaux, consentement, interface aux échelles 2/3/4, touche G et recharge après changement d’œuf, puis devis de villageois avec paiement réel.
+- Extraction des 88 modèles natifs à deux instants ; éclairage et échelle contrôlés. Pour chaque volant, vérification du matériau translucide et de l’alpha des couches du corps et des yeux, sans effet sur l’entité suivante. Captures de proximité à 4 / 2 / 0,8 blocs.
+- Le catalogue ne constitue pas une validation exhaustive du gameplay des 88 paires. Voir la [couverture par espèce](companion-powers-beta019-coverage.md).
+- MRpack contrôlé : CRC, versions, JAR embarqué identique au build, Demeure imbriqué identique, FR/EN, 88 passifs et 88 actifs, absence de tests. Les PNG existants et l’export beta.018 gardent leurs empreintes.
+
+Export : [Sanctuary-beta.019.mrpack](../build/Sanctuary-beta.019.mrpack).
+Reçu : `build/companion019-artifact.json`.
+
+SHA-256 MRpack : `ba3a7a0ceb1a9a71c135cdb5ddeacb431969eb2e8fd32d8bfaf5fab0e34d3bd3`.
+SHA-256 JAR : `e27cb61fdb1d55fec88f86cb8e9696eca6ca12a923129843208afbff6a6efc71`.
diff --git a/docs/companion-powers-inventory.md b/docs/companion-powers-inventory.md
new file mode 100644
index 0000000..89874ae
--- /dev/null
+++ b/docs/companion-powers-inventory.md
@@ -0,0 +1,242 @@
+# Inventaire des pouvoirs de familiers — référence 26.2
+
+Relevé du 13 septembre 2026 pour préparer le ticket de validation des pouvoirs.
+**Aucun de ces pouvoirs n’est activé dans Sanctuary beta.018.** L’œuf y fournit
+uniquement l’apparence du compagnon, son suivi et les particules de caresse.
+
+La [proposition d’équilibrage v1](companion-powers-balance-proposal.md) classe
+les 88 œufs actuels en commun, peu commun, rare, extraordinaire et légendaire,
+avec de nouveaux passifs, actifs, coûts et recharges. C’est une proposition
+de remplacement ; le présent document reste le relevé historique de la 26.2.
+
+Le code historique contient **88 essences Minecraft** : elles correspondent
+exactement aux **88 fichiers d’œufs de Minecraft 26.3-pre-2**, version du pack.
+Il définit aussi **25 overrides ItsAlive**, absents du pack beta.018 actuel.
+Ce document décrit les branches du code, pas des comportements déjà testés en bêta.
+
+Les effets sans chiffre sont de niveau I. Les passifs concernent le joueur portant
+l’œuf. La recharge concerne l’actif, en secondes à 20 ticks/s. Chaque utilisation
+réussie ajoute **0,45 point d’épuisement**, et non 0,45 barre de faim. L’ancien
+actif exige l’aptitude COMPANION_ACTIVE_POWER ; son raccourci était G, configurable.
+Les cooldowns sont communs au joueur : changer d’œuf ne les annule pas.
+
+## Les 88 œufs Minecraft de notre version
+
+| Mob / œuf | Passifs et contreparties | Actif | Recharge |
+|---|---|---|---|
+| Abeille (`bee`) | Chute lente | Pollinisation | 12 s |
+| Allay (`allay`) | Chance | Aimant à objets | 8 s |
+| Âne (`donkey`) | Résistance | Aimant à objets | 10 s |
+| Araignée (`spider`) | Vision nocturne ; Escalade des murs | Langue agrippante | 8 s |
+| Araignée venimeuse (`cave_spider`) | Vision nocturne ; Escalade des murs | Poison | 8 s |
+| Arpenteur (`strider`) | Résistance au feu ; Faiblesse à l’eau/pluie | Ruée | 6 s |
+| Axolotl (`axolotl`) | Respiration aquatique | Soin | 14 s |
+| Blaze (`blaze`) | Résistance au feu ; Faiblesse à l’eau/pluie | Petite boule de feu | 8 s |
+| Breeze (`breeze`) | Chute lente | Ruée aérienne | 4 s |
+| Chat (`cat`) | Vitesse ; Apaise les Creepers ; Apaise les Phantoms | Effroi | 12 s |
+| Chauve-souris (`bat`) | Vision nocturne ; Chute lente | Sonar des entités | 10 s |
+| Cheval (`horse`) | Vitesse II | Ruée | 5 s |
+| Cheval-squelette (`skeleton_horse`) | Respiration aquatique ; Vision nocturne ; Trêve morts-vivants | Ruée | 7 s |
+| Cheval-zombie (`zombie_horse`) | Vitesse II ; Brûle au soleil ; Trêve morts-vivants | Ruée | 7 s |
+| Chèvre (`goat`) | Sauts améliorés ; Résistance | Charge | 7 s |
+| Cochon (`pig`) | Aucun | Repérage du butin | 14 s |
+| Creeper (`creeper`) | Explosion à la mort sans dégâts au terrain | Détonation | 16 s |
+| Cube de magma (`magma_cube`) | Résistance au feu ; Sauts améliorés II ; Faiblesse à l’eau/pluie | Bond | 5 s |
+| Cube de soufre (`sulfur_cube`) | Résistance au feu ; Faiblesse à l’eau/pluie | Acide | 9 s |
+| Dauphin (`dolphin`) | Respiration aquatique ; Grâce du dauphin | Sonar des entités | 9 s |
+| Desséché (`parched`) | Résistance au feu ; Faiblesse à l’eau/pluie ; Trêve morts-vivants | Acide | 10 s |
+| Dromadaire (`camel`) | Vitesse | Ruée | 6 s |
+| Dromadaire momifié (`camel_husk`) | Vitesse ; Trêve morts-vivants | Charge | 8 s |
+| Embourbé (`bogged`) | Vision nocturne ; Trêve morts-vivants | Poison | 10 s |
+| Ender Dragon (`ender_dragon`) | Chute lente ; Résistance II | Souffle du dragon | 20 s |
+| Enderman (`enderman`) | Force ; Faiblesse à l’eau/pluie | Téléportation | 6 s |
+| Endermite (`endermite`) | Vitesse II | Téléportation | 4 s |
+| Évocateur (`evoker`) | Chance | Crocs d’évocateur | 14 s |
+| Gardien (`guardian`) | Respiration aquatique | Rayon gardien | 10 s |
+| Ghast (`ghast`) | Chute lente ; Faiblesse à l’eau/pluie | Boule de feu explosive | 12 s |
+| Ghast joyeux (`happy_ghast`) | Chute lente | Flottement | 6 s |
+| Golem de cuivre (`copper_golem`) | Célérité | Sonar des entités | 10 s |
+| Golem de fer (`iron_golem`) | Résistance II | Frappe au sol | 12 s |
+| Golem de neige (`snow_golem`) | Aucun | Rafale de neige | 7 s |
+| Grand gardien (`elder_guardian`) | Respiration aquatique ; Force de conduit | Rayon gardien | 16 s |
+| Grenouille (`frog`) | Sauts améliorés II | Langue agrippante | 6 s |
+| Grinceur (`creaking`) | Vision nocturne ; Résistance III immobile | Enracinement | 14 s |
+| Hoglin (`hoglin`) | Force | Charge | 9 s |
+| Lama (`llama`) | Aucun | Crachat ciblé | 6 s |
+| Lama de marchand (`trader_llama`) | Chance | Troc chanceux | 14 s |
+| Lapin (`rabbit`) | Vitesse II ; Sauts améliorés II | Bond | 4 s |
+| Loup (`wolf`) | Force | Rugissement | 10 s |
+| Marchand ambulant (`wandering_trader`) | Chance II | Sonar des entités | 16 s |
+| Momifié (`husk`) | Force ; Trêve morts-vivants | Festin | 12 s |
+| Mooshroom (`mooshroom`) | Régénération | Festin | 12 s |
+| Morue (`cod`) | Respiration aquatique | Bulle protectrice | 12 s |
+| Mouton (`sheep`) | Chute lente ; Résistance | Flottement | 8 s |
+| Mule (`mule`) | Vitesse ; Résistance | Ruée | 7 s |
+| Nautile (`nautilus`) | Respiration aquatique ; Souffle du Nautile | Bulle protectrice | 8 s |
+| Nautile-zombie (`zombie_nautilus`) | Respiration aquatique ; Brûle au soleil ; Trêve morts-vivants | Bulle protectrice | 9 s |
+| Noyé (`drowned`) | Respiration aquatique ; Brûle au soleil ; Trêve morts-vivants | Crachat ciblé | 9 s |
+| Ocelot (`ocelot`) | Vitesse II ; Vision nocturne | Roulade | 5 s |
+| Ours blanc (`polar_bear`) | Résistance ; Respiration aquatique | Rugissement | 12 s |
+| Panda (`panda`) | Résistance | Roulade | 8 s |
+| Perroquet (`parrot`) | Chute lente | Sonar des entités | 8 s |
+| Phantom (`phantom`) | Vision nocturne ; Chute lente ; Brûle au soleil ; Trêve morts-vivants | Flottement | 6 s |
+| Piglin (`piglin`) | Chance ; Trêve Piglins | Troc chanceux | 16 s |
+| Piglin barbare (`piglin_brute`) | Force II ; Trêve Piglins | Frappe au sol | 10 s |
+| Piglin zombifié (`zombified_piglin`) | Résistance au feu ; Trêve morts-vivants ; Trêve Piglins | Troc chanceux | 14 s |
+| Pillard (`pillager`) | Chance | Crachat ciblé | 7 s |
+| Poisson d'argent (`silverfish`) | Célérité II | Téléportation | 5 s |
+| Poisson tropical (`tropical_fish`) | Respiration aquatique ; Vision nocturne | Bulle protectrice | 10 s |
+| Poisson-globe (`pufferfish`) | Respiration aquatique | Poison | 9 s |
+| Poule (`chicken`) | Chute lente | Flottement | 5 s |
+| Poulpe (`squid`) | Respiration aquatique | Encre | 9 s |
+| Poulpe luisant (`glow_squid`) | Respiration aquatique ; Vision nocturne | Encre | 10 s |
+| Ravageur (`ravager`) | Force II ; Résistance | Charge | 12 s |
+| Renard (`fox`) | Vitesse ; Vision nocturne | Roulade | 5 s |
+| Renifleur (`sniffer`) | Chance II | Repérage du butin | 12 s |
+| Saumon (`salmon`) | Respiration aquatique ; Grâce du dauphin | Ruée | 5 s |
+| Shulker (`shulker`) | Résistance ; Résistance III immobile | Lévitation | 10 s |
+| Slime (`slime`) | Sauts améliorés II ; Chute lente | Bond | 4 s |
+| Sorcière (`witch`) | Chance | Potion de soutien | 14 s |
+| Squelette (`skeleton`) | Vision nocturne ; Brûle au soleil ; Trêve morts-vivants | Crachat ciblé | 7 s |
+| Tatou (`armadillo`) | Aucun | Carapace | 12 s |
+| Têtard (`tadpole`) | Respiration aquatique ; Sauts améliorés | Bond | 5 s |
+| Tortue (`turtle`) | Respiration aquatique ; Résistance | Carapace | 10 s |
+| Vache (`cow`) | Aucun | Purification | 14 s |
+| Vagabond (`stray`) | Vision nocturne ; Brûle au soleil ; Trêve morts-vivants | Gel | 9 s |
+| Vex (`vex`) | Chute lente ; Vitesse | Téléportation | 5 s |
+| Villageois (`villager`) | Héros du village | Faveur villageoise | 18 s |
+| Vindicateur (`vindicator`) | Force II | Frappe au sol | 9 s |
+| Warden (`warden`) | Vision nocturne ; Résistance II | Onde sonique | 20 s |
+| Wither (`wither`) | Chute lente ; Résistance II ; Trêve morts-vivants | Flétrissement | 20 s |
+| Wither squelette (`wither_skeleton`) | Résistance au feu ; Trêve morts-vivants | Flétrissement | 12 s |
+| Zoglin (`zoglin`) | Force II ; Trêve morts-vivants | Charge | 10 s |
+| Zombie (`zombie`) | Force ; Brûle au soleil ; Trêve morts-vivants | Rugissement | 12 s |
+| Zombie-villageois (`zombie_villager`) | Chance ; Brûle au soleil ; Trêve morts-vivants | Faveur villageoise | 18 s |
+
+## Détail des traits passifs
+
+- **Trêve** : toutes les 10 ticks, efface la cible des mobs concernés qui visent
+ le propriétaire, dans une boîte de 24 blocs autour de lui. Ce n’est pas une
+ invulnérabilité. Le chat utilise ce même mécanisme pour Creepers et Phantoms.
+- **Morts-vivants reconnus par la trêve** : bogged, camel_husk, drowned, husk,
+ parched, phantom, skeleton, skeleton_horse, stray, wither_skeleton, zoglin,
+ zombie, zombie_horse, zombie_nautilus, zombie_villager, zombified_piglin.
+ Le boss Wither ne figure pas dans cette liste de cibles pacifiées.
+- **Faiblesse à l’eau/pluie** : Faiblesse II et Lenteur I tant que le joueur
+ est dans l’eau ou sous la pluie ; ce trait ne code pas de dégât direct.
+- **Soleil** : applique 45 ticks de feu chaque seconde si le ciel est lumineux,
+ visible au-dessus du joueur, sans eau/pluie. Aucun test de casque ici.
+- **Défense immobile** : Résistance III si la vitesse horizontale au carré est
+ inférieure à 0,0025. Le contrôle ne porte pas sur la vitesse verticale.
+- **Escalade** : pousse la vitesse verticale à au moins 0,18 contre un mur et
+ réinitialise la chute.
+- **Explosion à la mort** : puissance 3, sans feu ni destruction de blocs.
+- Les effets passifs ordinaires sont renouvelés toutes les 10 ticks pour 35 ticks.
+ Retirer l’œuf arrête leur renouvellement ; les dernières durées peuvent subsister.
+
+## Ce que font réellement les actifs
+
+Les dégâts indiqués sont des points de vie : **2 points = 1 cœur**, avant les
+réductions natives éventuelles. Les zones sont des boîtes autour du joueur,
+pas un rayon sphérique strict. Elles excluent le propriétaire et les familiers,
+mais peuvent inclure d’autres joueurs. Les actifs ciblés choisissent la créature
+vivante la plus proche dans un cône jusqu’à 24 blocs ; la fonction ne vérifie
+pas la ligne de vue. Ces points devront être testés et arbitrés avant portage.
+
+| Actif | Comportement du code historique |
+|---|---|
+| Acide (`ACID`) | Poison II et Lenteur II pendant 7 s sur la cible. |
+| Aimant à objets (`MAGNET`) | Attire les objets au sol dans une zone de 12 blocs autour du joueur, impulsion de 0,45 vers ses yeux. |
+| Bond (`BOUNCE`) | Vitesse verticale relevée au minimum à 1,05 ; distance de chute réinitialisée. |
+| Bond du chasseur (`POUNCE`) | Impulsion force 2,15, minimum vertical 0,65 ; si une cible existe, Lenteur V pendant 2,5 s. |
+| Boule de feu explosive (`FIREBALL`) | Lance une véritable LargeFireball native, puissance d’explosion 1. Les effets sur le terrain suivent le projectile natif. |
+| Bulle protectrice (`BUBBLE`) | Respiration aquatique pendant 15 s et Absorption I pendant 10 s. |
+| Carapace (`SHELL`) | Résistance IV et Lenteur II pendant 5 s. |
+| Charge (`CHARGE`) | Impulsion force 2,35, minimum vertical 0,15 ; Résistance II pendant 3 s. Aucun dégât de contact spécifique codé ici. |
+| Crachat ciblé (`SPIT`) | 3 dégâts d’attaque et recul sur la cible. Le code applique directement les dégâts : il ne crée pas de projectile de crachat ou de flèche. |
+| Crocs d’évocateur (`FANGS`) | Fait apparaître huit crocs natifs en ligne, espacés de 1,35 bloc dans la direction du regard. |
+| Détonation (`DETONATE`) | Explosion de puissance 2,25 centrée sur le joueur, sans feu ni destruction du terrain. |
+| Effroi (`SCARE`) | Même effet codé que Rugissement : 2 dégâts, recul, Faiblesse I pendant 4 s dans une zone de 11 blocs. |
+| Encre (`INK`) | Invisibilité 5 s pour le joueur ; Cécité 4 s aux autres entités vivantes proches, zone de 9 blocs. |
+| Enracinement (`ROOT`) | Résistance IV, Absorption II et Lenteur IV pendant 6 s. |
+| Faveur villageoise (`TRADE`) | Héros du village II pendant 15 s. |
+| Festin (`FEAST`) | Rend 3 points de vie, soit 1,5 cœur, et donne Absorption I pendant 10 s. Ne remplit pas la faim. |
+| Floraison (`BLOOM`) | Même effet codé que Pollinisation : Régénération I et Célérité I pendant 6 s aux autres entités vivantes proches, zone de 7 blocs. |
+| Flottement (`FLOAT`) | Ajoute une impulsion de 0,45 dans le regard et 0,55 vers le haut ; Chute lente pendant 8 s ; distance de chute réinitialisée. |
+| Flétrissement (`WITHER`) | Wither II pendant 7 s et 5 dégâts magiques sur la cible. |
+| Frappe au sol (`SLAM`) | 7 dégâts, recul et Faiblesse I pendant 4 s aux autres entités vivantes dans une zone de 8 blocs ; efface leur cible si elle était le propriétaire. |
+| Gel (`FREEZE`) | Lenteur IV et Faiblesse I pendant 8 s sur la cible ; ne crée pas de glace. |
+| Langue agrippante (`TONGUE`) | Attire la cible vers le joueur avec une impulsion horizontale et une composante verticale de 0,25. |
+| Lévitation (`LEVITATE`) | Lévitation I pendant 5 s sur la cible. |
+| Onde sonique (`SONIC`) | 10 dégâts de type sonicBoom et recul sur la cible. |
+| Petite boule de feu (`SMALL_FIREBALL`) | Lance une véritable SmallFireball native dans la direction regardée. |
+| Poison (`POISON`) | Poison I pendant 7 s sur la cible. |
+| Pollinisation (`POLLINATE`) | Régénération I et Célérité I pendant 6 s aux autres entités vivantes proches, zone de 7 blocs. Le propriétaire est exclu ; aucune pousse de cultures dans cette fonction. |
+| Potion de soutien (`POTION`) | Purification, Régénération II et Résistance I pendant 8 s. Cette potion est fixe, malgré l’ancien libellé « aléatoire ». |
+| Purification (`CLEANSE`) | Retire Lenteur, Fatigue de minage, Nausée, Cécité, Faim, Faiblesse, Poison, Wither et Obscurité. Ne retire pas indistinctement tous les effets. |
+| Rafale de neige (`SNOW`) | Même effet codé que Gel : Lenteur IV et Faiblesse I pendant 8 s ; pas de boule de neige native créée. |
+| Rayon gardien (`BEAM`) | 6 dégâts magiques et Surbrillance 5 s sur la cible. |
+| Repérage du butin (`TRUFFLE`) | Rend lumineux les objets déjà au sol dans une zone de 18 blocs et donne Chance II pendant 12 s. Ne génère aucun trésor ; le marquage des objets n’a pas de minuterie de retrait ici. |
+| Roulade (`ROLL`) | Impulsion force 1,35, minimum vertical 0,08 ; Résistance III pendant 1,5 s. |
+| Rugissement (`ROAR`) | 2 dégâts, recul et Faiblesse I pendant 4 s aux autres entités vivantes dans une zone de 11 blocs ; efface leur cible si elle était le propriétaire. |
+| Ruée (`DASH`) | Impulsion selon le regard : force 1,8, minimum vertical 0,12 ; distance de chute réinitialisée. |
+| Ruée aérienne (`AIR_DASH`) | Impulsion selon le regard : force 1,45, minimum vertical 0,45 ; distance de chute réinitialisée. |
+| Soin (`HEAL`) | Rend 6 points de vie, soit 3 cœurs. |
+| Sonar des entités (`SONAR`) | Applique Surbrillance pendant 10 s aux autres entités vivantes proches, zone de 28 blocs. Ne détecte ni minerais ni redstone. |
+| Souffle du dragon (`DRAGON_BREATH`) | Lance une véritable DragonFireball native. |
+| Troc chanceux (`BARTER`) | Chance III et Héros du village I pendant 12 s. Ne produit aucun échange ou objet à lui seul. |
+| Téléportation (`TELEPORT`) | Cherche une destination sans collision de 12 à 2 blocs devant le joueur, par pas de 0,5. Se téléporte à la première trouvée et réinitialise la chute. |
+| Vomi (`VOMIT`) | Cécité 6 s, Surbrillance 12 s et Faim II pendant 8 s sur la cible. Aucune attraction de horde codée dans cette fonction. |
+
+## Les 25 œufs ItsAlive historiques
+
+Ils sont résolus par l’identifiant de l’objet œuf, pour distinguer notamment les
+Mooblooms qui partagent un type d’entité. Ils ne sont pas distribués en beta.018.
+
+| Mob / œuf | Passifs et contreparties | Actif | Recharge |
+|---|---|---|---|
+| Œuf d'apparition d'infecté (`infected`) | Vitesse II à 40 % de vie ou moins ; Trêve Infectés | Rugissement | 10 s |
+| Œuf d'apparition de chargeur (`charger`) | Résistance ; Trêve Infectés | Charge | 9 s |
+| Œuf d'apparition de cracheur (`spitter`) | Trêve Infectés | Acide | 9 s |
+| Œuf d'apparition de Hunter (`hunter`) | Vitesse ; Trêve Infectés | Bond du chasseur | 7 s |
+| Œuf d'apparition de Moobloom allium (`allium_moobloom`) | Régénération | Purification | 12 s |
+| Œuf d'apparition de Moobloom bleuet (`cornflower_moobloom`) | Chance II | Troc chanceux | 16 s |
+| Œuf d'apparition de Moobloom coquelicot (`poppy_moobloom`) | Bonus de vie | Soin | 12 s |
+| Œuf d'apparition de Moobloom hibiscus (`hibiscus_moobloom`) | Régénération | Floraison | 14 s |
+| Œuf d'apparition de Moobloom houstonie bleue (`azure_bluet_moobloom`) | Célérité | Ruée | 7 s |
+| Œuf d'apparition de Moobloom lilas (`lilac_moobloom`) | Vitesse II | Ruée | 7 s |
+| Œuf d'apparition de Moobloom marguerite (`oxeye_daisy_moobloom`) | Aucun | Purification | 12 s |
+| Œuf d'apparition de Moobloom muguet (`lily_of_the_valley_moobloom`) | Aucun | Poison | 10 s |
+| Œuf d'apparition de Moobloom narcisse (`narcissus_moobloom`) | Absorption | Bulle protectrice | 14 s |
+| Œuf d'apparition de Moobloom orchidée bleue (`blue_orchid_moobloom`) | Respiration aquatique | Bulle protectrice | 12 s |
+| Œuf d'apparition de Moobloom pissenlit (`dandelion_moobloom`) | Chute lente | Flottement | 7 s |
+| Œuf d'apparition de Moobloom tournesol (`sunflower_moobloom`) | Célérité | Floraison | 10 s |
+| Œuf d'apparition de Moobloom tulipe blanche (`white_tulip_moobloom`) | Résistance | Carapace | 12 s |
+| Œuf d'apparition de Moobloom tulipe orange (`orange_tulip_moobloom`) | Résistance au feu | Floraison | 14 s |
+| Œuf d'apparition de Moobloom tulipe rose (`pink_tulip_moobloom`) | Régénération | Soin | 12 s |
+| Œuf d'apparition de Moobloom tulipe rouge (`red_tulip_moobloom`) | Force | Floraison | 14 s |
+| Œuf d'apparition de vomiteur (`vomiter`) | Trêve Infectés | Vomi | 12 s |
+| Œuf d’apparition de baleine volante (`flying_whale`) | Chute lente ; Respiration aquatique | Bulle protectrice | 10 s |
+| Œuf d’apparition de Bracken (`bracken`) | Vision nocturne ; Vitesse | Effroi | 12 s |
+| Œuf d’apparition de fantôme (`ghost`) | Invisibilité ; Chute lente | Téléportation | 7 s |
+| Œuf d’apparition de papillon (`papillon`) | Chute lente ; Sauts améliorés | Pollinisation | 8 s |
+
+## Sources et points à reprendre
+
+- [Registre et exécution historiques](../../26.2/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryCompanionPowers.java) : registre vanilla ligne 827, ItsAlive ligne 918 ; passifs ligne 222, actifs ligne 286.
+- SHA-256 du Java consulté : `0b3ea8571161fe8eccb059ca2d5ca12f8351c80ad7cd5a81556e199175ebeea5`.
+- [Ancien résumé alpha.96](../../26.2/sanctuary/COMPANION_POWERS.md), conservé en lecture seule.
+- Œufs présents : fichiers `assets/minecraft/items/*_spawn_egg.json` du JAR
+ Minecraft 26.3-pre-2 ; noms FR issus de son index d’assets local.
+- [Contrat beta.018](companions-cosmetics-beta018.md) : pouvoirs expressément exclus du portage actuel.
+
+Le présent relevé corrige des imprécisions du résumé historique : le sonar du
+golem de cuivre est le sonar générique d’entités ; le chat possède aussi Vitesse ;
+l’Embourbé, l’Araignée venimeuse et le Grinceur ont Vision nocturne ; le dromadaire
+momifié a Vitesse ; le Nautile a aussi Respiration aquatique ; le Phantom a aussi
+la trêve. Potion n’est pas aléatoire, Festin ne nourrit pas, et les « projections »
+de squelette, noyé et pillard appliquent des dégâts ciblés sans projectile natif.
+Les seuls projectiles natifs créés dans le registre actif sont les boules de feu
+et le souffle du dragon ; les crocs sont leurs propres entités.
+
+Aucun effet, identifiant, réglage, monde ou binaire n’a été modifié pour ce relevé.
diff --git a/docs/companions-cosmetics-beta018.md b/docs/companions-cosmetics-beta018.md
new file mode 100644
index 0000000..e9cdbab
--- /dev/null
+++ b/docs/companions-cosmetics-beta018.md
@@ -0,0 +1,153 @@
+# beta.018 — cases de familier, cape et cosmétique de tête
+
+Ticket du 13 septembre 2026, branche `codex/companions-cosmetics-beta018`.
+Contrat écrit avant les changements de stockage. Référence 26.2 consultée en
+lecture seule ; aucun pouvoir passif ou actif de familier n'est porté ici,
+sur décision explicite du créateur. Leur validation aura son ticket dédié.
+
+## Interface et objets
+
+L'en-tête de l'inventaire indique uniquement Inventaire / Inventory, sans
+compteur de rangées. La capacité reste lisible dans Progression et par les cases.
+Trois cases d'équipement distinctes rejoignent le panneau personnel, toujours
+disponibles : un œuf de familier, une cape, un objet cosmétique sur la tête.
+Chaque case contient un vrai objet, limité à une unité. Les clics et Maj-clics
+utilisent les menus synchronisés et respectent les rangées débloquées.
+De haut en bas : cosmétique de tête, cape, œuf de familier, main secondaire.
+Cet ordre est visuel ; les identifiants de stockage familier/cape/tête restent fixes.
+Maj-clic équipe automatiquement œufs et capes ; le cosmétique se place à la main.
+Le tri et le changement de hotbar ne déplacent pas cet équipement.
+Maj + clic gauche maintenu permet de parcourir les cases et d’exécuter leur
+Maj-clic natif successivement. Le geste part d’une case avec le curseur vide ;
+chaque case est visitée une seule fois, y compris lors d’un mouvement rapide.
+Relâcher le clic ou Maj termine le geste. Coffres, fours et autres conteneurs
+gardent leurs destinations et restrictions natives. Le catalogue créatif garde
+ses gestes d’origine ; l’inventaire personnel Sanctuary utilise ce raccourci.
+La touche de rangée fonctionne également en jeu sans écran : Tab avance,
+Maj + Tab recule. Elle reste configurable et garde la même sélection de rangée
+à la réouverture de l’inventaire. Seules les rangées débloquées sont parcourues.
+Une commutation interrompt l’utilisation de l’objet tenu, comme un changement
+de case de hotbar natif, et reste validée par le serveur.
+Les trois PNG fournis dans `../textures/{egg,cape,cosmetics}.png` sont copiés sans
+retouche dans le mod et dans les sources du resource pack.
+
+Un œuf d'apparition de mob équipé fait apparaître un petit compagnon temporaire
+qui se déplace réellement : navigation autour des obstacles, promenade proche
+et téléportation vers une place libre s'il est trop loin ou bloqué. Le suivi par
+apparition à côté du joueur de la 26.2 est remplacé. Le retirer le fait disparaître.
+Il est invincible et ne collisionne pas avec les autres entités. Le clic droit
+du propriétaire le caresse et émet des particules aux couleurs de son œuf.
+Une entité de familier dédiée utilise l'apparence du mob, afin de ne pas hériter
+de ses attaques ou comportements destructeurs. Les animations restent visuelles.
+Le familier n'est pas une source renouvelable de butin, d'XP ou de produits.
+Ses pouvoirs ne sont pas activés. Les œufs natifs restent disponibles pour les
+tests opérateur ; leur acquisition en survie n'est pas inventée dans ce ticket.
+
+`sanctuary:zero_cape` est une première cape de test, libellée Cape Zéro / Zero Cape,
+avec un visuel provisoire trouvé en ligne et une provenance documentée. Elle se
+teste depuis le créatif ou `/give`. Elle utilise le mouvement de cape natif ;
+elle ne modifie ni les élytres ni le compte Minecraft.
+
+Le cosmétique accepte tout objet/bloc. Il ne remplace pas le casque serveur et
+ne donne ni protection, ni effet, ni déclenchement d'objet. Les propriétés d'état
+de bloc sont utilisées pour le rendu lorsque possible. Un piston porté sur la
+tête s'étend brièvement quand le joueur reçoit des dégâts, uniquement en rendu :
+aucun bloc placé ou déplacé, aucun signal de redstone.
+
+## Sauvegarde, mort et New Game+
+
+Ajout d'un champ joueur `sanctuary:accessories`, schéma 1, contenant trois piles
+optionnelles dans l'ordre familier/cape/tête. Un ancien joueur beta.017 commence
+avec trois cases vides ; aucun déplacement ni changement des 54 cases existantes,
+du journal de progression ou de cycle. Les données inconnues/corrompues sont
+conservées et rendues non modifiables, sans remplacement silencieux.
+
+Les objets suivent la mort native : ils tombent si keepInventory est désactivé,
+sont conservés sinon. Le New Game+ les garde comme le reste de l'inventaire.
+La créature temporaire n'est jamais sauvegardée dans les chunks ; elle est
+recréée depuis l'œuf après reconnexion/changement de dimension, sans doublon.
+Les apparences cape/tête sont synchronisées par le serveur aux observateurs,
+y compris lors de l'arrivée d'un nouveau joueur. Aucun bonus serveur ne dépend
+de ces apparences. Nouveau contrat réseau `sanctuary:accessories_v1` : client et
+serveur beta.018 ensemble. Aucun monde personnel ou installation n'est modifié.
+
+## S’allonger et se reposer
+
+W est un geste de base gratuit. S’allonger disparaît des aptitudes achetables.
+Maj + W conserve le déblocage Se reposer et son coût. Le repos utilise le rendu
+natif de sommeil au lit, orienté selon le joueur, distinct de la posture mobile.
+Il ne déclenche pas le sommeil serveur, ne fixe pas de point de réapparition et
+ne fait pas avancer le temps. Les anciens champs et preuves d’achat `lying`
+sont conservés pour lire les sauvegardes beta.017 ; aucun nouvel achat n’est
+accepté et `lyingCost` devient un champ historique sans effet.
+
+## Validation
+
+### Sélections de minage et de construction
+
+Le retour d’essai ajouté pendant ce ticket fixe le groupe au premier clic
+maintenu avec R. Le client conserve l’origine, les cases et le palier ; le
+serveur valide la visée au démarrage puis poursuit le même travail sans exiger
+de regarder encore l’origine. Tourner la caméra ou bouger en restant à portée
+ne remet pas la charge à zéro. La construction garde son plan, sa face et ses
+directions de pose initiales, y compris pour les escaliers. Le contexte de pose
+est figé sans modifier la caméra ou la position réelle du joueur.
+
+La molette règle le palier avant le clic. Pendant le maintien, le groupe reste
+fixe, même après sa fin : il faut relâcher avant d’en choisir un autre. Les
+interruptions par changement d’outil, portée, menu, mort, droits, matériaux ou
+perte de maintien restent actives. Les interactions natives, durées, coûts et
+limites de blocs restent inchangés. Aucun nouveau format de sauvegarde ou paquet
+réseau n’est introduit pour ce verrouillage ; son état est temporaire.
+
+### Parcours client
+
+Le parcours automatisé du client Minecraft 26.3-pre-2, sur un monde de
+développement neuf de graine 42, passe avec le vrai clavier et la vraie souris :
+
+- Emplacements synchronisés et ordre visuel, inventaire à 1 et 6 rangées, FR/EN.
+- Maj-clic œuf/cape, placement manuel du cosmétique, piston avant/après un dégât
+ serveur et rendu d’épée, tête de joueur, coffre et piston.
+- Suivi vivant et caresse ; rendus loup, vache, perroquet, creeper, morue et ghast.
+- W gratuit, achat du repos, rendu de sommeil distinct et arrêt par déplacement.
+- Tab en jeu, Maj inverse, maintien sans répétition, autre touche configurée,
+ conservation des 54 positions visibles et de la colonne sélectionnée.
+- Balayage rapide dans les deux sens d’un coffre, destination pleine, une seule
+ visite par case, tri ingrédient/combustible du four, arrêt au relâchement de Maj,
+ équipement automatique œuf/cape et absence de cosmétique automatique.
+- Veine de huit blocs entièrement cassée après changement de visée et déplacement
+ à portée ; cible voisine conservée pendant le maintien, puis minée après
+ relâchement et nouveau clic. Molette sans effet sur la sélection verrouillée.
+- Surface de 25 escaliers terminée en regardant ailleurs et en bougeant, avec
+ orientation initiale et consommation exacte ; aucune nouvelle surface après
+ la fin tant que le clic est maintenu.
+
+Les captures sont dans `mods/sanctuary/build/run/clientGameTest/screenshots/` ;
+le journal complet final est `build/group-lock018-client.log` (1 min 46 s).
+`build/cosmetics018-client.log` conserve le premier parcours des accessoires
+et raccourcis, également réussi avant l’ajout des sélections verrouillées.
+
+Limites : les essais ne constituent pas une validation de toutes les espèces,
+de tous les modèles spéciaux d’objets/blocs, ni d’une session multijoueur sous
+latence. Le déplacement du compagnon reste terrestre, même pour une apparence
+aquatique ou volante. Les pouvoirs de familiers attendent leur ticket dédié.
+
+### Serveur et distribution
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=accessories,progression,movement,inventory,inventoryflow,sorting,cycle,mining,building`
+passe en **4 min 51 s**, avec **67 tests serveur obligatoires réussis**, ainsi
+que les contrôles purs du dépôt. Journal : `build/cosmetics018-check-build.log`.
+Les tests couvrent les six capacités d’inventaire, équipement typé, destination
+pleine, mort, sauvegarde/rechargement, conservation au prestige, données futures
+préservées, navigation et invincibilité du familier, interruption de l’arc lors
+d’un changement de rangée, protections et coûts natifs du minage/construction.
+La durée de minage reste identique entre une caméra fixe et une caméra qui
+tourne ; le contexte de construction figé ne fuit pas dans la pose individuelle.
+
+Export local : [Sanctuary-beta.018.mrpack](../build/Sanctuary-beta.018.mrpack),
+**2 617 278 octets**. SHA-256 :
+`06997ae60f3306b9b973c4b5e83894ca302905fbdd337e1d0c36abf0fa4d9aba`.
+Reçu : `build/cosmetics018-artifact.json`. ZIP, versions, JAR Sanctuary et Demeure
+embarqués, ressources, libellés, index packwiz et dépendance Fabric API vérifiés.
+Les exports beta.003 à beta.017 restent identiques ; aucune installation
+personnelle ni canal publié n’a été modifié.
diff --git a/docs/consultation-discord-analyse.md b/docs/consultation-discord-analyse.md
new file mode 100644
index 0000000..0dfdf58
--- /dev/null
+++ b/docs/consultation-discord-analyse.md
@@ -0,0 +1,187 @@
+# Sanctuary — analyse de la vision et préparation de la consultation
+
+**14 septembre 2026 · Ticket documentaire local CONSULT-01 · Branche `codex/consultation-discord`.**
+
+Livrable associé : **[120 posts Discord prêts à copier](consultation-discord-posts.md)**. Ils couvrent les décisions de jeu, les retours sur les mécaniques actuelles et les besoins de communauté déduits de l’analyse. Ils n’ont pas été publiés. Aucun vote, témoignage ou accord d’anciens joueurs n’a été inventé.
+
+Un **[extrait des 12 premiers posts](consultation-discord-premiere-serie.md)** permet de lancer la discussion sans parcourir toute la banque.
+
+## 1. Ce que l’analyse fait ressortir
+
+**Sanctuary dispose déjà d’une progression personnelle documentée, mais sa promesse centrale — transformer ensemble un monde grâce à ses activités — demande encore sa boucle de jeu collective.** On peut développer son personnage, explorer, construire, recommencer et créer une faction. Les recherches et contributions ouvrant un continent, l’économie, les protections territoriales, les quêtes et la grande histoire ne sont pas encore reliées en un parcours jouable documenté.
+
+La vision est cohérente autour de trois expériences : devenir capable de choses nouvelles ; rendre le monde habitable avec d’autres ; laisser une histoire visible dans les lieux et le ciel. Ses inconnues les plus importantes portent sur **qui peut agir, à quel rythme, avec quelles conséquences pour les autres et pour ceux qui arrivent plus tard**. Choisir d’abord les objets, leurs recettes ou leurs prix laisserait ces problèmes sans réponse.
+
+Les anciens joueurs peuvent particulièrement bien éclairer les moments qui rassemblaient, les contraintes qui décourageaient, le plaisir des pouvoirs et la manière dont les voisinages fonctionnaient. La documentation ne contient pas un corpus de leurs retours permettant de présumer ces préférences. Les deux premières questions sont donc libres et précèdent les listes de fonctionnalités.
+
+## 2. Périmètre et méthode
+
+La base est l’**état local de travail**, qui comportait de nombreuses modifications et additions avant cette tâche, sur `codex/realtime-tracking-beta037`. La configuration indique `mod_version=beta.037` et `pack_version=beta.037`. Les notes décrivent des exports locaux ; cela ne prouve pas que les anciens joueurs disposent de cette version ni qu’elle est installée sur un serveur public.
+
+Le corpus inventorié comprend les **102 fichiers Markdown alors présents dans `docs/`**, le README, le changelog, les consignes de contribution et de versionnement, les notices et guides du pack, les README des modules et les archives documentaires pertinentes. La vision, les cahiers de cosmologie, structures, expansion et progression ont été examinés pour leurs décisions ; les contrats de versions pertinentes ont été croisés pour actualiser les statuts. Les historiques de terrain et de validation ont été parcourus par leur structure, leurs états et leurs limites. Les grands inventaires de recettes servent à vérifier la couverture des familles, pas à voter ligne par ligne sur des milliers de recettes.
+
+Il s’agit d’un **audit documentaire de conception**, pas d’un nouvel audit exhaustif du code, d’une vérification de toutes les preuves historiques ni d’un essai multijoueur. « Livré » ci-dessous signifie **décrit comme livré et accompagné de vérifications dans le contrat concerné**. Aucun test Minecraft n’a été exécuté pour cette tâche documentaire et aucune compatibilité externe n’est extrapolée depuis une ancienne étude.
+
+Ordre de lecture retenu en cas de divergence : décision explicite récente pour l’intention ; contrat de livraison le plus récent sur la mécanique pour l’état documenté ; proposition datée pour les possibilités ; référence alpha ou 26.2 pour l’historique. Un document récent ne remplace un ancien contrat que sur les sujets qu’il traite.
+
+Les posts portent quatre statuts :
+
+- **Souvenir** : information à recueillir, absente des sources.
+- **Choix ouvert** : décision explicitement laissée à concevoir ou à équilibrer.
+- **Retour sur l’existant** : une règle existe ; on évalue son effet sans la présenter comme indécise à l’origine.
+- **Question déduite** : conséquence ou besoin identifié par l’analyse, à confirmer auprès des joueurs.
+
+## 3. Ce qui est déjà fixé ou documenté comme jouable
+
+| Sujet | Point de départ à conserver dans les discussions | Sources |
+| --- | --- | --- |
+| Monde | Un monde flottant commun ; Petit 512, Moyen 724, Grand 1 024 de diamètre nominal. Chantier de terrain clôturé par le créateur sur alpha.30.7. | [Vision](vision.md), [alpha.30.7](generation-alpha30.7.md) |
+| Expéditions | Quatre régions de 512 blocs déjà ouvertes dans les nouveaux mondes concernés. La révision beta.002 retient nord boréal, est tropical/volcan, sud aride/canyon, ouest humide/océan ; les futures ouvertures collectives restent absentes. | [beta.001](expeditions-beta001.md), [beta.002](expeditions-beta002.md), [expansion](expansion.md) |
+| Arrivée | Fiche d’habitant et couleur, accueil, cinématique de 29,5 secondes que l’on peut passer. Départ 3/3/3. | [Progression](progression-beta003-contract.md), [introduction](introduction-beta031.md) |
+| Compétences | Six compétences fonctionnelles. Minage individuel à vitesse native au départ, jusqu’à 200 % et veines de 256 blocs ; construction jusqu’aux plans 15×15 ; inventaire de 9 à 54 cases, hotbar comprise. | [Minage](mining-beta008.md), [construction](building-beta009.md), [inventaire](inventory-beta010.md) |
+| Coûts et amorce | Courbe par défaut 1, 2, 4, 8, 16, 32, 48 niveaux ; inventaire sur les cinq premiers achats. Cinq étapes de premiers gestes, 8 points d’XP chacune, une fois par habitant et monde. Équilibrage à éprouver. | [beta.013](progression-inline-beta013.md), [tutoriel](tutorial-xp-beta016.md) |
+| Mort | Compétences et aptitudes conservées. Sans keepInventory, 70 % des points d’XP non investis laissés en orbes récupérables par tous ; objets selon les règles de mort applicables. | [Contrat initial](progression-beta003-contract.md), [inventaire](inventory-beta010.md) |
+| New Game+ | Volontaire après les six compétences au maximum, sans exiger toutes les aptitudes. Compétences et XP remises à zéro par défaut ; aptitudes, objets, connaissances, monde et affiliations conservés. Objets des rangées verrouillées retirables. | [Cycles](cycle-factions-beta011.md) |
+| Factions | Une affiliation à la fois. Un cycle donne un prestige et une charge ; une charge fonde deux places, une charge ajoute une place. Invitations, départ, exclusion, transfert et dissolution existent. Aucun claim n’en découle. | [Cycles et factions](cycle-factions-beta011.md) |
+| Demeure | Mesure des empreintes d’habitation dans les zones connues ; la décroissance après absence ne constitue pas un droit de reprise de propriété. | [Demeure](demeure-beta006.md), [module](../mods/demeure/README.md) |
+| Connaissances et carte | Carte native personnelle de surface, exploration mémorisée avant achat, absence de partage automatique ; filtres Discovery des entrées connues. Bestiaire complet, minimap, marqueurs et partage restent des sujets distincts. | [Atlas](atlas-beta005.md), [menus beta.033](menus-discovery-beta033.md), [navigation](blocodex-navigation.md) |
+| Recettes | Catalogue JEI porté, connaissances distinctes de la possession, fabrication manuelle libre. 67 collections dont une technique ; 2 042 recettes classées et 126 advancements associés. Fiches détaillées d’obtention et musée encore futurs. | [Recettes](recipes-jei-beta024.md), [collections beta.035](collections-beta035.md) |
+| Familiers | 88 paires de pouvoirs révisées, recharges et budgets partagés, nom et taille persistants, interactions. Acquisition de survie, rangs I–III et Anomaly non livrés. | [beta.020](familiar-interactions-beta020.md), [beta.032](spawn-eggs-v2-beta032.md), [catalogue](spawn-eggs-v2-catalogue-beta032.md) |
+| Gestes et visuels | Portage et piles de joueurs, plongeon, posture allongée, plané avec animal volant porté, cosmétiques interactifs et lumières dynamiques. La TNT portée est une vraie exception avec dégâts. | [Accessoires](companions-cosmetics-beta018.md), [TNT](head-cosmetics-beta021.md), [lumières](dynamic-lights-beta030.md), [beta.036](corrections-beta036.md) |
+| Temps et objectifs | Temps réel saisonnier, choix serveur, sommeil sans saut de nuit, phantoms naturels d’insomnie désactivés en temps réel. Suivi commun avec trois objectifs épinglés au maximum ; quêtes non livrées. | [beta.037](realtime-beta037.md) |
+| Canon | Neuf anciens personnages, ruines comme traces d’anciennes solutions, Indoors contrôlés et Backrooms orphelins. Le canon et les identités retenus ne deviennent pas un sondage de popularité. | [Cosmologie](cosmologie.md) |
+
+## 4. Les tensions de design à résoudre en priorité
+
+| Tension | Pourquoi elle change réellement la partie | Décision ou essai utile |
+| --- | --- | --- |
+| Coopération / passage obligé par le prestige | Un groupe d’amis peut vouloir se structurer avant qu’un membre ait maximisé six compétences. Les places financées par répétition de cycles peuvent faire du groupe une récompense de grind. | P06, P07, P10–P12, puis essai de fondation à plusieurs. |
+| Départ fragile / joueur occasionnel | Trois cœurs et neuf cases peuvent créer de l’entraide ou décourager avant la première relation sociale. Les premiers gestes donnent déjà de l’XP : il faut mesurer le départ actuel. | P04, P08, P09, P15 et une heure sans commandes. |
+| Exploration / stocks disponibles | Quatre îles sont déjà ouvertes, et certains îlots aériens de la fin alpha sont volontairement riches. « Ressources rares » n’est pas une preuve de besoin d’expansion. | P19, P23, P26–P27 ; relever les ressources effectivement accessibles pendant une partie. |
+| Initiative / décision commune | Ouvrir une île modifie durablement le monde de tous. Le financeur, le constructeur de la machine et les futurs usagers ne sont pas forcément les mêmes. | P21–P25 ; préciser aussi la décision de lancement et l’annulation avant consommation. |
+| Progression / économie de l’XP | Capacités, enchantements, transports et futurs gestes créatifs peuvent se concurrencer. Une source d’XP dominante peut décider du rythme de tous les autres systèmes. | P09–P10, P18, P29 ; mesurer les budgets par activité avant de choisir les prix. |
+| Habiter / combattre / plaisanter | Factions, Demeure, braquages, téléportations, TNT et lucky blocks ne partagent pas encore un contrat de protection. Un geste amusant pour l’un peut détruire le projet d’un autre. | P31–P40, P114, P102 ; tester des situations aux frontières des permissions. |
+| Monde permanent / pression horaire | Temps réel, offres horaires, dimanche, quotas et production peuvent favoriser une disponibilité que les anciens n’ont plus. | P04–P06, P49, P52, P54–P55, P58, P100, P113. |
+| Découverte / connaissance extérieure | Les joueurs connaissent déjà Minecraft et peuvent consulter un wiki. Le secret du catalogue ne doit pas devenir une obligation d’ignorer ce qu’ils savent. | Garder la fabrication libre ; P63–P67 et un parcours d’information volontaire. |
+| Attachement / objet échangeable | Un compagnon nommé et singulier peut aussi être perdu, vendu, prêté ou remplacé par un plus rare. Ces intentions produisent des relations très différentes. | P71–P75 ; fixer l’acquisition et la récupération avant un marché des œufs. |
+| Histoire commune / événements manqués | Boss uniques, reliques et révélations peuvent laisser les absents sans rôle. Le ciel et les archives peuvent valoriser une première victoire sans rendre les suivants inutiles. | P06–P07, P82–P86, P107–P108, P116. |
+| Ballast / conservation économique | Une trace narrative n’est ni un prélèvement autorisé ni un stock restituable. Rendre le ballast exploitable peut créer une nouvelle source de monnaie ou de matériaux. | P92–P93, P117 ; un cas complet d’opération → trace → lieu → récupération. |
+| Proximité du créatif / avenir de la survie | Les possibilités créatives changent la valeur de la production, des objets rares et du commerce. Les droits opérateur ne sont pas une récompense de gameplay implicite. | P18 et P85 avant de définir la récompense des sept boules. |
+
+## 5. Carte complète des décisions restantes
+
+Les plages renvoient aux identifiants du [document de posts](consultation-discord-posts.md). La colonne finale conserve les détails à spécifier après la consultation : les joueurs choisissent une expérience, puis le ticket en fait une règle testable. Aucun de ces détails n’est marqué comme implémenté par ce document.
+
+| Domaine | Décisions de jeu encore ouvertes ou à réévaluer | Posts | Détails à arrêter au ticket |
+| --- | --- | --- | --- |
+| Identité du projet | Souvenirs à retrouver, irritants, priorité des piliers, durée des sessions et des aventures. | P01–P05 | Population et rythme de test visés ; distinguer monde permanent et futurs départs saisonniers. Aucun effacement décidé. |
+| Accueil et inclusion | Place des nouveaux, absents, non-combattants, autonomie des premières heures. | P06–P09, P111–P112 | Aides, jalons, lisibilité de la fiche et de l’introduction, accès sans vocal, besoins de clavier et de lecture. |
+| Progression | Temps du premier cycle, sources d’XP, intérêt du prestige, confort achetable et plafond d’inventaire. | P09–P16 | Prix et courbes après mesures ; ne pas récompenser de nouveau une découverte déjà payée ; traitement des futurs plafonds sans altérer les cycles archivés. |
+| Mort | XP publique ou réservée, pertes ordinaires et objets personnels, effort de récupération. | P13–P14, P73, P90–P91 | Expiration, décès répété, vide, lave, déconnexion, keepInventory et garanties contre les doubles restitutions. |
+| Factions | Moment de fondation, charges et capacité, délégation et responsable absent. | P11–P12, P35 | Rôles fins, seuils, invitations, règles de trésorerie future et sort des dépenses au départ ; ne pas confondre couleur d’habitant et identité collective. |
+| Claims et voisinages | Protection personnelle avant faction, agrandissement, visiteurs, abandon, visibilité Demeure. | P31–P36 | Emprise verticale, sous-sols, limites de chunk, espaces publics, accès aux animaux et coffres, chevauchements, alertes et contestations. Demeure reste une mesure sauf nouveau contrat explicite. |
+| Conflit et risques | PvP, conditions des braquages, enjeu volable, présence des défenseurs, dégâts collatéraux. | P37–P40, P114 | Fenêtres, cooldowns, avertissements, attribution d’une attaque, usages du foret, sanctions et réparation d’incidents. Les dates d’absence ne valent pas consentement. |
+| Expansion collective | Motivation après les quatre îles initiales, cadence, décideurs, formes de contribution et coûts. | P19–P23 | Population de référence, progression des prix, plusieurs projets simultanés, annulation, remboursement, droit de lancer et usage public de la nouvelle terre. |
+| Recherches et machine | Informations accessibles avant engagement, apprentissage, montage et programmation. | P24–P25, P60 | Rôles de l’assembleur, contrôleur, terminal, dépôt et ancre ; connexions, recettes, autorisations ; distinction mailbox / dépôt collectif / dépôt de machine. |
+| Territoire et ressources | Préservation de l’île, carrière, pénurie bloquante, ressources exclusives, identité des futurs biomes. | P19, P26–P27, P118 | Relevés après génération, ressources finies/renouvelables, emprises et accessibilité ; nouveau biome et palette sur graines versionnées, sans rouvrir implicitement le chantier alpha. |
+| Ruines | Utilité immédiate, indices, réappropriation, lien avec les machines et les personnages. | P78–P79, P84 | Pour chaque lieu : ancien usage, plan, accès, butin, état, exceptions collectives, présence garantie/facultative et répétition. Relire les versions plus récentes avant de reprendre WG-26. |
+| Donjon majeur | Blocage qu’il lève, nature du défi, bénéficiaires, répétition, usage après victoire. | P80–P84 | Identité et comportements du boss, objet installé/consommé/conservé, site indispensable accessible, objet perdu, participants tardifs, spawners et récompenses personnelles. |
+| Transport | Première traversée, priorité des moyens, coût et place des réseaux publics. | P28–P29, P119 | Waystones horizontales connues et ascenseurs, rayon et arrivée approximative du téléporteur, temps d’activation, accès, capacités et vitesses ; ziplines, leads consommés par grappin, bateau/poule, biplan, Happy Ghast, taille de Magic Carpet. |
+| Monnaies | Rôle pratique des trois gemmes, convertibilité, production et usages de dépense. | P42–P45, P48 | Origine et génération des rubis/saphirs, recettes, taux, frais, quantités, sinks et effets des fermes. Les valeurs de ponte de la vision demandent un essai d’économie. |
+| Boutique et marché | Lieu, stock réel ou apport du serveur, prix, échanges entre joueurs et renouvellement des offres. | P41–P46, P113 | Nombre et prix des neuf emplacements envisagés, durée d’une offre, frais d’annonce, réservation, transfert de propriété, livraison échouée et stockage plein. |
+| Catalogue et courrier | Signification exacte d’une réserve en saphirs, garantie apportée, livraison et délégation. | P45, P47, P49 | Limites par objet, dépôt ou assurance, propriétaire de la mailbox, récupération de l’accès, boîte pleine et envoi annulé ; éviter toute création illimitée par réservation. |
+| Navets et loterie | Achat dominical accessible, place du hasard, exclusivités et attentes des absents. | P49–P50 | Cours, fréquence, péremption éventuelle non décidée, ticket gagné/comment, probabilités, plafond, financement et règles des futurs casinos événementiels. |
+| Quêtes et événements | Activités utiles, quotas, partage des récompenses, rendez-vous libres ou programmés. | P51–P54 | Trois difficultés/gemmes, acceptation, abandon, expiration, dépôts, attribution XP/butins ; auteur des panneaux, inscriptions, anniversaires, visiteurs, concours et Gazette. |
+| Villageois et automatisation | Premières tâches, personnes autorisées, production en l’absence des joueurs. | P56–P58 | Rayon des cloches, bannières en conflit, salaires et entretien, stocks sources/destinations, métier, refus, capture/changement d’équipe ; différences ticks chargés / heures réelles. |
+| Machines de production | Convoyeurs, ordinateur, terminal commun et chargement de chunk. | P56, P58–P60 | Six ports, langage et exemples, stockage jusqu’à 128 coffres envisagés, connexion/permissions, périmètre et durée des clés de chunk, limites de charge serveur. |
+| Construction avancée | Plans guidés ou automatisés, circulation des schémas, utilité des prefabs. | P17, P61 | Attribution de l’auteur, partage/vente, matériaux inconnus, vraie consommation, permissions, supports, annulation ; vérifier Litematica sur la version exacte au ticket. Voxelier reste non prioritaire. |
+| Cartographie | Transmission volontaire, niveau d’information de la minimap, reliefs superposés, marqueurs. | P62–P64 | Copie ponctuelle ou mise à jour, périmètre du partage, provenance, carte de faction, limites du brouillard et de l’export Web ; autres dimensions et cartes souterraines. |
+| Découvertes | Critères du bestiaire, fiches d’obtention, niveau de guidage, alchimie par familles. | P65–P67, P115 | Vue/possession/fabrication distinctes, variantes d’espèces, gestes sans recette, encyclopédie technique, musée et récompenses éventuelles de collection ; contenu déjà connu conservé. |
+| Profil et affinités | Vitrine, favoris, effet sur apprivoisement, production ou chasse, consultation d’autrui. | P68–P69, P115, P120 | Nombre de trophées ; type/variante favori ; petit coefficient et bénéficiaire du butin ; délai de 24 h déjà retenu ; gestes et portée de lecture des PV. |
+| Familiers | Acquisition, hasard/objectif choisi, marché, perte, rangs et nature d’Anomaly. | P71–P76 | Contrat d’œuf de survie inerte à décider avant distribution ; duplication et première récompense ; sort des œufs de test, familles rares, découverte et stabilisation ; essais solo puis coop. |
+| Capes et objets exceptionnels | Valeur de souvenir, mérite, appartenance ou collection ; puissance des anomalies. | P50, P75, P77 | Visuel original de Cape Zéro, modes d’obtention des capes, tables des quatre familles de lootboxes, équipement titane indestructible et enchantements, rareté qui reste désirable sans remplacer tout le reste. |
+| Cosmologie et récit | Manière de raconter, rôle des joueurs dans l’issue, trois épreuves manquantes. | P78, P85–P86 | Nature exacte de la découverte, rôle encore vague de Zuri, liens d’Alex, journaux des neuf personnages, apparitions, dénouement ; ces révélations peuvent rester un atelier d’auteur séparé. |
+| Fin et après-fin | Réussite commune, droits créatifs de gameplay, poursuite du monde. | P05, P18, P85–P86 | Sept objets et leur réunion, seuil de fortune, cauchemar, rencontre d’Alpha, nécromancien, conséquences de victoire, nouveaux arrivants et nouveaux cycles. |
+| Dimensions | Priorité et rôle distinct des Cavernes, Alpha, Backrooms et Indoors. | P30, P87, P94 | Accès Nether/End existants ou à relier au parcours, ressources des Cavernes, cité ancienne et boss, apparence Alpha limitée à sa dimension ; conservation des régions exploitées. |
+| Backrooms | Accès, retour, récupération de biens perdus, propriété et rétention. | P88–P91 | Lumière et niveau de danger, lieu d’arrivée, mort/déconnexion, secours, distinction objet identifié / ressource de décor / résidu. Aucun espace réel ne devient sans référence de sauvegarde. |
+| Ballast et géologie | Trace gratuite, coût, conséquence contrôlable, lecture des périodes, extraction. | P92–P93, P117 | Opérations contributrices, unités, conservation ou transformation, provenance, période, moment de génération, strates des anciens, bornes ; préserver les lieux déjà explorés. |
+| Indoors | Usages, géométrie, propriété partagée et accès de remplacement. | P94–P97 | Tailles 5³ à 100³ indicatives, transfert/vente, déconnexion, visiteurs coincés, fermeture et devenir du contenu ; dimension technique ou régions d’un espace partagé à choisir par les développeurs. |
+| Cuisine et cultures | Complexité, terroirs, serres, transmission du savoir et temporalité alimentaire. | P98–P101 | Premier catalogue d’ingrédients/plats, ustensiles, étapes, nature de la « poudre », maturation du vin/fromage/saucisson, pages de Steve et pages secrètes ; rôle nutritionnel à mesurer avec Faim/Repos/familiers. |
+| Faune et végétation | Utilité et attachement aux rares, nouveaux environnements, productions localisées. | P48, P99, P106, P118 | Tables d’apparition, ponte non reproductible, armures de poule, variantes Moobloom, fleurs et bois lavande/ébène/mossy/blueberry ; formes, noms FR/EN et identifiants à stabiliser. |
+| Humour et danger | Limites d’Only Fun, événements absurdes, proximité des nouveaux hostiles. | P40, P102–P103, P106 | Portée des interactions, consentement, baleine fantôme et Happy Creeper, classes de zombies, fréquence, raids éventuels, distances aux refuges et comportement des boss. |
+| Armes et petits objets | Place des armes à feu, objets d’activité et écoute de musique. | P104–P105, P110 | Munitions pépites/lingots/minerais, argent/titane, creeper lock/mining rifle, enchantements, Weather/Fragment/Randomizer/Mega TNT ; format caméra/cartes, particuleur, référence du disque et lecture audiovisuelle. |
+| Ciel et calendrier | Organisation des étoiles, constellations, droits de dessin, cycles et datation visible. | P107–P109, P116 | Géométrie selon le point de vue, noms et longue-vue, étoiles adjacentes, coût point/segment, cycle stellaire de sept jours, origine et relation au soleil ; étoiles des absents et densité à long terme. |
+| Site et histoire | Gazette, activités entre connexions, publication volontaire et portée des découvertes. | P70, P120 | Identités partagées, événements personnels/collectifs, spoilers, causalité, exports et actions Web validées serveur ; journal fonctionnant sans site. Les agrégats matériels ne suffisent pas à reconstruire cette histoire. |
+| Pack et exploitation | Accessibilité, performances et format des essais ; besoin réel des intégrations. | P111–P112 | Contrôles sans bouton latéral, lisibilité, effets et sons réglables ; matrice de versions/licences, machines cibles, support Mac/Windows, rollback et canal. Master Key : rôles de maintenance, journal des réparations et diagnostics, distincts des pouvoirs de jeu. |
+
+## 6. Incohérences et formulations documentaires à corriger
+
+Ces points sont relevés pour un futur entretien documentaire ; les fichiers sources existants n’ont pas été réécrits dans cette tâche.
+
+| Passage pouvant induire en erreur | Lecture actualisée | Correction documentaire proposée |
+| --- | --- | --- |
+| [README](../README.md) : nouveautés beta.037, tableau de version et exemple de JAR encore beta.036. | Les deux propriétés sont beta.037 ; [la livraison](realtime-beta037.md) est locale. | Une courte fiche de version actuelle, puis un historique daté. Ne pas déduire l’état du canal depuis la version de travail. |
+| [Vision](vision.md) : progression décrite comme future dans « Périmètre de départ », conclusion invitant encore à obtenir le premier monde stable. | Des compétences, cycles et factions sont décrits comme livrés ; worldgen clôturé sur alpha.30.7. | Marquer ces paragraphes comme cadrage initial et ajouter l’état actuel en tête. |
+| Vision : tous commencent isolés avant l’ouverture progressive des continents. | [beta.001–002](expeditions-beta002.md) ouvre déjà quatre anciennes expéditions dans les nouveaux mondes concernés. | Distinguer les terres héritées de celles que les joueurs créeront ensuite. |
+| Vision/expansion : boussole froid/chaud/sec/humide. | Convention facultative ; les îles initiales ont une répartition spécifique confirmée, dont ouest humide et est tropical. | Ne pas transformer la convention climatique en loi des quatre expéditions. |
+| Vision et ancienne présentation worldgen : monde de 384 blocs. | [alpha.30.4](generation-alpha30.4.md) documente Y0–639 constructibles ; alpha.30.7 conserve le plafond Y640. | Distinguer hauteur constructible actuelle, volume du terrain et configurations historiques. |
+| Vision : minage très lent, place de la hotbar à préciser, taux de perte XP et charges encore ouverts. | [Minage](mining-beta008.md) démarre à 100 % ; [inventaire](inventory-beta010.md) inclut la hotbar ; [mort](progression-beta003-contract.md) et [cycles](cycle-factions-beta011.md) fixent des valeurs configurables. | Conserver ces mentions comme anciennes intentions, lier les contrats courants. Poser un retour d’essai, pas une question d’implémentation initiale. |
+| [Prochains tickets](prochains-tickets.md) et plusieurs sections du backlog gardent de longues questions désormais résolues en beta.011. | Le bandeau de statut est à jour, le corps conserve le cadrage d’origine. | Séparer « contrat final », « limites encore ouvertes » et « discussion historique ». |
+| [Backlog](backlog.md) : REALTIME « à cadrer » sous TIME-037 livré ; quêtes prévues en haut à gauche. | [beta.037](realtime-beta037.md) livre l’horloge et impose le même moteur et placement que les autres objectifs, sans second HUD. | Retirer ces deux entrées des prochaines décisions actives ou les marquer remplacées. |
+| [beta.034](notifications-beta034.md) : bas à droite et suivi automatique ; beta.033 : Factions/History dans Échap. | [beta.036](corrections-beta036.md) retient haut à droite par défaut et suivi choisi ; beta.034 déplace Factions/History dans Prestige. | Garder chaque contrat daté ; une notice joueur actuelle doit décrire seulement les accès actuels. |
+| [Navigation Blocodex](blocodex-navigation.md) : tout est encore présenté comme conception et les inconnus sont décrits visibles dans le socle. | Recettes, objets connus, filtrage et accès ont progressé en beta.024–036 ; vitrine/favoris et bestiaire restent à distinguer. | Une matrice par fonction évitera « tout livré » et « tout absent ». |
+| [Collections](collections-minecraft.md) : définitions intégrées en tête, puis « futur ticket » et ancienne séquence de livraison dans le corps ; README beta.024 encore « proposition non active ». | [beta.035](collections-beta035.md) matérialise les 67 collections. Les fiches d’obtention et le musée restent futurs. | Séparer les données livrées des extensions de présentation, conserver le prototype JSON comme exemple historique. |
+| [Familiers v1](companion-powers-balance-proposal.md), [v2](spawn-eggs-refonte-v2.md) : couches de propositions avec états à dates différentes. | [beta.032](spawn-eggs-v2-beta032.md) et son catalogue décrivent les 88 pouvoirs ; acquisition, rangs et Anomaly restent ouverts. | Présenter une source des valeurs courantes et une liste distincte des hypothèses d’acquisition. |
+| [Structures-conception](structures-conception.md) : « alpha.22 reste la version publiée », petite sélection de lieux, rôles des personnages encore largement ouverts. | Les versions [alpha.30.6](generation-alpha30.6.md), [alpha.30.7](generation-alpha30.7.md) et [cosmologie](cosmologie.md) décrivent des évolutions ultérieures. | Ne pas appliquer toute la liste WG-26 comme un prochain chantier. Réconcilier chaque lieu et son rôle avec la génération finale et le canon. |
+| Formules générales sur « Fabric API seule dépendance de gameplay » dans le README et les guides de pack. | Demeure et un fork JEI sont embarqués ; Fabric API reste la dépendance externe distincte décrite par le pack. | Distinguer dépendances externes, modules embarqués et intégrations envisagées. |
+| Anciennes notes : contrôles visuels en attente, versions suivantes : parcours clients réussis. | Une validation ultérieure ciblée n’est pas automatiquement celle de tous les anciens scénarios ni un test d’équilibre. | Une matrice consolidée « comportement / dernière preuve / essai restant » ferait gagner plus qu’une nouvelle accumulation de totaux de tests. |
+
+Le défaut principal de la documentation est donc **l’accumulation de l’historique dans les pages d’orientation**. Elle conserve utilement les décisions et les limites, mais le lecteur doit reconstruire lui-même ce qui fait encore débat. Une future page « état actuel et décisions ouvertes » reliée aux contrats éviterait de transformer les anciens « reste à faire » en nouveau backlog.
+
+## 7. Comment utiliser les réponses
+
+### Premier tour : choisir ce qu’on veut vivre
+
+Commencer par **P01 et P02**, sans afficher d’abord toutes les fonctionnalités possibles : on recueille les souvenirs spontanés. Continuer avec **P03, P06, P08, P12, P19, P21, P31, P37, P71, P85**. Cette série de douze questions fixe les priorités, le rythme social, la protection et les grandes récompenses.
+
+Lire les commentaires avant de conclure avec un pourcentage. Une préférence majoritaire pour le conflit peut aussi révéler qu’une partie du groupe ne reviendrait pas avec cette règle. Conserver séparément avis sur une ancienne version, préférence théorique et observation sur une bêta précise. La taille des groupes de répondants ne permet pas de présumer l’opinion de tous les anciens.
+
+### Deuxième tour : préciser seulement les branches retenues
+
+- Si le groupe veut des territoires durables : P32–P36, puis les accès et limites des protections.
+- Si le braquage est accepté : P39 et P114. Si le groupe choisit de le réserver aux donjons, ces questions deviennent inutiles.
+- Si l’expansion collective motive : P20, P22–P27, P119, puis le rôle du donjon P80.
+- Si les familiers comptent : P72–P75 et P76 sur des usages réels, sans mettre 88 fiches au vote d’un coup.
+- Si l’économie est prioritaire : P41–P50, P58, P113 ; vérifier ensemble sources de ressources, dépenses et disponibilité horaire.
+- Si les mystères attirent : P78–P86. Proposer P92–P93 et P117 dans un espace volontaire si l’on veut garder cette idée secrète pour le reste des joueurs.
+
+Les autres thèmes gardent leur série indépendante. Une préférence pour la cuisine n’impose pas immédiatement toute sa liste d’ingrédients ; on peut choisir une chaîne complète, par exemple récolter → transformer → préparer → partager.
+
+### Troisième tour : convertir les avis en petits essais
+
+| Essai proposé | Matériel déjà documenté | Observation attendue |
+| --- | --- | --- |
+| Une heure d’arrivée en survie | Profil normal, premiers gestes, compétences, inventaire. | Premier achat, premières morts, incompréhensions, moment où l’on veut aider quelqu’un ou demander de l’aide. |
+| Une tâche avec quelques familiers | Pouvoirs beta.032 ; œufs fournis dans un monde de test puisque l’acquisition n’existe pas. | Fréquence d’usage, effets compréhensibles, tâches rendues possibles, comparaison seul/à plusieurs et compagnon que l’on veut garder. |
+| Une expédition avec chantier | Quatre régions ouvertes, carte personnelle, gestes de construction. | Temps et difficulté de traversée, manque réel de ressources, besoin de transport et intérêt d’une future expansion. |
+| Un atelier de règles de voisinage | Situations discutées ou simulées ; ne pas prétendre que les claims sont actifs. | Visiteur, accident, stock commun, absence, reprise, personne hors faction ; règles contradictoires repérées avant le code. |
+
+Une décision exploitable contient : **règle provisoire, raison exprimée, personnes affectées, situation testée et critère qui conduirait à la revoir**. Le modèle de retour à la fin des posts rend cette décision visible sans la confondre avec une fonctionnalité livrée.
+
+## 8. Ce qui reste de la responsabilité de conception et de développement
+
+Certaines inconnues demandent un atelier d’auteur ou une preuve technique, plutôt qu’un vote Discord :
+
+- **Équilibrage chiffré** : coefficients des 88 familiers, coûts exacts, taux de change, tables de butin, quotas, courbes de prix, fréquences de ponte et de spawn. Les avis donnent le ressenti visé ; les parties et mesures fixent les valeurs d’essai.
+- **Contrats de données** : identités stables, achats non répétés, objets uniques, panne entre paiement et livraison, copie des cartes, restauration, fermeture d’Indoor et journal transactionnel du ballast. Un choix de joueur ne dispense pas de ces garanties.
+- **Sauvegardes et génération** : toute nouvelle version de terrain, dimension, extension ou récupération exige son périmètre et, si nécessaire, un contrat de migration. Aucun sondage n’autorise à effacer ou régénérer une partie existante.
+- **Intégrations** : disponibilité, licence, dépendances et comportement réel de Sodium, Iris, Simple Voice Chat, Litematica, Golden Days ou autres pistes sur la version exacte. Le besoin peut être voté ; la compatibilité se vérifie au moment du ticket.
+- **Canon et révélations** : nature de l’ancienne découverte, sacrifice et dénouement, détail des journaux et énigmes. On peut coécrire avec des volontaires avertis, sans dévoiler les réponses à tous pour choisir leur mode de découverte.
+- **Direction artistique** : formes d’arbres, silhouettes de lieux, variantes de Moobloom, graphismes de Cape Zéro, vocabulaire FR/EN et palette du pack. Après les attentes d’usage, présenter quelques exemples concrets permettra de décider plus utilement que des noms seuls.
+- **Exploitation et Master Key** : rôles de maintenance, diagnostics, réparation et journalisation ; état du canal de distribution, compatibilité client/serveur et limites de performances. L’administrateur doit pouvoir réparer un incident sans transformer les récompenses de jeu en droits opérateur.
+
+## 9. Livraison de cette analyse
+
+Trois fichiers documentaires ajoutés : analyse, banque complète et extrait de démarrage. Les changements préexistants ont été conservés ; aucun code, version binaire, manifeste, monde, serveur, canal packwiz ou instance Prism n’a été modifié par cette tâche. La branche documentaire conserve l’état de travail déjà présent ; elle ne constitue pas un commit isolé de l’ensemble des modifications antérieures.
+
+La vérification de livraison porte sur les **120 identifiants uniques**, les blocs à copier, leur longueur, les liens locaux et la couverture des familles de la vision. Les hypothèses sont signalées et les propositions de réponse ne valent ni résultats de sondage ni décisions validées. Une modification documentaire seule ne nécessite ni incrément beta ni construction Gradle.
diff --git a/docs/consultation-discord-posts.md b/docs/consultation-discord-posts.md
new file mode 100644
index 0000000..77a30fe
--- /dev/null
+++ b/docs/consultation-discord-posts.md
@@ -0,0 +1,1735 @@
+# Sanctuary — questions à poser aux anciens joueurs
+
+Préparation du 14 septembre 2026, à partir de la documentation locale jusqu’à **beta.037**. Les réponses proposées sont des pistes de discussion, pas des décisions prises. Aucun message n’a été publié.
+
+L’[analyse et la couverture des décisions](consultation-discord-analyse.md) expliquent les sources, les contradictions documentaires et les dépendances entre sujets.
+
+Pour commencer directement : **[extrait des 12 premiers posts conseillés](consultation-discord-premiere-serie.md)**.
+
+## Mode d’emploi
+
+Copier uniquement le bloc de texte du post. Chaque bloc contient une seule question principale et, sauf pour les souvenirs et essais, des réponses directement réutilisables dans un sondage. Les commentaires peuvent proposer une autre réponse, nuancer ou s’abstenir. Les montants et durées proposés servent à comparer des attentes ; ils ne constituent pas un équilibrage validé.
+
+Les mentions hors des blocs distinguent **souvenir**, **choix ouvert**, **retour sur l’existant** et **question déduite de l’analyse**. Une réponse sur une ancienne version ne vaut pas un test de la bêta actuelle.
+
+**Première série conseillée : P01, P02, P03, P06, P08, P12, P19, P21, P31, P37, P71 et P85.** Commencer par les souvenirs libres, puis les choix. Publier les autres selon ce qui ressort, à raison d’un ou deux sujets en discussion à la fois. La banque entière n’est pas un questionnaire à remplir d’un coup.
+
+## Message d’ouverture à épingler
+
+```text
+J’aimerais construire la suite de Sanctuary avec les personnes qui y ont joué.
+
+Je vais poster des questions petit à petit. Certaines parlent de vos souvenirs ; d’autres présentent une mécanique envisagée pour la suite. Quand quelque chose existe déjà dans la bêta, je le préciserai.
+
+Vous pouvez répondre avec une lettre, proposer autre chose ou raconter une situation vécue. « Je ne sais pas » et « ça ne m’intéresse pas » sont aussi utiles. Si vous parlez d’une ancienne partie, indiquez laquelle si vous vous en souvenez.
+
+Ce qui m’aide le plus : votre préférence et ce qu’elle changerait dans votre façon de jouer. Les votes me guideront ; les désaccords et les essais en jeu compteront aussi.
+```
+
+## 1. Retrouver ce qui faisait Sanctuary
+
+Sources : [vision](vision.md), [progression et jeu émergent](progression-beta003.md). P01–P07 complètent la documentation par des retours de joueurs qu’elle ne contient pas.
+
+### P01 — Le souvenir à retrouver
+
+Souvenir libre · À publier avant les sondages de fonctionnalités.
+
+```text
+Quel moment vécu sur Sanctuary aimerais-tu pouvoir revivre dans la nouvelle version ?
+
+Raconte une scène précise : ce que tu faisais, avec qui, et ce qui l’a rendue mémorable. Ça peut être une construction, une découverte, un accident, une rencontre ou un truc complètement idiot.
+```
+
+### P02 — Ce qui te faisait décrocher
+
+Souvenir libre.
+
+```text
+Qu’est-ce qui t’a déjà fait perdre l’envie de te connecter à Sanctuary ?
+
+Une situation concrète m’aidera plus qu’une liste de fonctionnalités manquantes. Ça peut venir du jeu, du rythme du serveur ou de la vie du groupe. Pas besoin de nommer quelqu’un.
+```
+
+### P03 — La priorité à protéger
+
+Question déduite · Arbitrage entre les piliers de la vision.
+
+```text
+Si je dois protéger une seule qualité de Sanctuary dans les prochains développements, laquelle doit passer en premier ?
+
+A — L’exploration et les découvertes surprenantes.
+B — La construction et les grands projets communs.
+C — La progression, la production et les échanges.
+D — La vie du groupe, les rencontres et les moments absurdes.
+```
+
+### P04 — Une session ordinaire
+
+Question déduite · Sert à dimensionner les activités, sans demander un engagement.
+
+```text
+Quelle durée correspondrait le mieux à une session ordinaire de Sanctuary pour toi aujourd’hui ?
+
+A — Moins de 30 minutes.
+B — De 30 minutes à moins d’une heure.
+C — D’une heure à deux heures.
+D — Plus de deux heures.
+```
+
+### P05 — La durée de vie du monde
+
+Question déduite · Politique de futurs serveurs, sans effacement autorisé d’une sauvegarde.
+
+```text
+Pour une future aventure Sanctuary, quelle organisation du monde te donnerait le plus envie de rester ?
+
+A — Un monde durable, enrichi pendant des mois ou des années.
+B — Des saisons avec un nouveau monde, les anciens restant visitables.
+C — Un monde durable, avec des aventures saisonnières dans des régions séparées.
+D — Des aventures courtes dont la durée est annoncée au départ.
+```
+
+### P06 — Revenir après une absence
+
+Question déduite · À traiter avant les coûts collectifs et le calendrier.
+
+```text
+Si tu reviens après deux semaines d’absence et que les autres ont beaucoup avancé, qu’est-ce qui t’aiderait le plus à reprendre ?
+
+A — Profiter immédiatement des infrastructures et des accès ouverts par le serveur.
+B — Avoir un parcours personnel de rattrapage plus court.
+C — Retrouver des activités utiles au groupe dès mon niveau actuel.
+D — Rejouer les grandes étapes manquées avec d’autres volontaires.
+```
+
+### P07 — Contribuer sans combattre
+
+Question déduite · Teste la promesse de coopération sans classes imposées.
+
+```text
+Quelle place doit pouvoir prendre une personne qui adore construire, cuisiner ou commercer, mais évite les combats ?
+
+A — Accéder à toute la progression grâce à des contributions équivalentes.
+B — Accéder à l’essentiel, avec quelques récompenses de combat réservées aux combattants.
+C — Préparer les expéditions et profiter de leurs déblocages collectifs.
+D — Garder un parcours distinct, avec ses propres grands accomplissements.
+```
+
+## 2. Départ, XP et recommencement
+
+Sources : [progression beta.003](progression-beta003-contract.md), [minage](mining-beta008.md), [construction](building-beta009.md), [inventaire](inventory-beta010.md), [cycles et factions](cycle-factions-beta011.md), [coûts beta.013](progression-inline-beta013.md), [premiers pas](tutorial-xp-beta016.md), [plafond d’inventaire](skill-xp-bars-beta025.md).
+
+### P08 — Le départ fragile
+
+Retour sur l’existant · Ne propose pas de rétablir l’ancien minage ralenti.
+
+```text
+Dans la bêta, on commence avec 3 cœurs, une petite réserve de faim et d’air, et 9 cases d’inventaire. Le minage de départ garde la vitesse Minecraft normale ; les premiers gestes donnent un peu d’XP.
+
+Quel ressenti voudrais-tu pendant la première heure ?
+
+A — Une vraie fragilité qui rend l’entraide nécessaire.
+B — Un départ exigeant, avec un abri et les premiers progrès rapidement accessibles.
+C — Une mise en route assez confortable ; le danger vient ensuite de l’exploration.
+D — Il me faut un essai de cette bêta pour me prononcer.
+```
+
+### P09 — Gagner son XP
+
+Choix ouvert · Sources complémentaires à l’XP Minecraft et au tutoriel existants.
+
+```text
+En plus de l’XP Minecraft, quelle activité devrait surtout aider à acheter ses améliorations dans Sanctuary ?
+
+A — Découvrir de nouveaux lieux et matériaux.
+B — Réussir des commandes et travaux utiles aux autres.
+C — Accomplir des défis de combat ou d’exploration.
+D — Pratiquer des activités variées, avec un intérêt à changer régulièrement.
+```
+
+### P10 — La durée d’un premier cycle
+
+Retour sur l’existant · Durées proposées pour un essai, pas promesse de durée.
+
+```text
+Le New Game+ devient disponible quand les six compétences sont au maximum. Pour un premier cycle en jouant normalement, sans chercher à optimiser une ferme à XP, quel ordre de durée te semblerait satisfaisant ?
+
+A — Jusqu’à 10 heures de jeu.
+B — Plus de 10 à 25 heures.
+C — Plus de 25 à 50 heures.
+D — Plus de 50 heures.
+```
+
+### P11 — L’intérêt de recommencer
+
+Retour sur l’existant · Les aptitudes, objets et constructions sont déjà conservés.
+
+```text
+Le New Game+ remet les compétences au départ, conserve les aptitudes et les objets, et donne du prestige ainsi qu’une charge pour les factions.
+
+Quelle récompense te donnerait le plus envie de refaire un cycle ?
+
+A — Davantage de moyens pour développer une faction.
+B — Des titres, capes et traces visibles dans l’histoire du serveur.
+C — De nouveaux défis et variantes de parcours.
+D — Aucune : recommencer ces compétences ne m’attire pas.
+```
+
+### P12 — Fonder une faction avant le prestige
+
+Retour sur une règle livrée · Une évolution demanderait une décision explicite.
+
+```text
+Aujourd’hui, fonder une faction demande une charge gagnée en terminant un cycle. La faction commence à deux places ; ses invités n’ont pas besoin de prestige.
+
+À quel moment voudrais-tu pouvoir créer ton groupe officiel ?
+
+A — Dès l’arrivée ; le prestige servirait ensuite à le développer.
+B — Après un petit objectif collectif accessible en début de partie.
+C — Après le premier cycle, comme dans la bêta actuelle.
+D — Les groupes informels me suffisent ; la faction peut rester tardive.
+```
+
+### P13 — Le risque sur l’XP
+
+Retour sur l’existant · En points d’XP, pas en pourcentage des niveaux.
+
+```text
+Sans keepInventory, la mort conserve les améliorations achetées, mais laisse actuellement 70 % de l’XP non dépensée en orbes que n’importe qui peut ramasser. Le reste est perdu.
+
+Quelle règle te donnerait un risque intéressant ?
+
+A — Garder cette règle : investir son XP protège sa progression.
+B — Laisser toute l’XP au lieu de mort, toujours récupérable par tous.
+C — Réserver temporairement les orbes à la personne morte.
+D — Conserver l’XP sur soi ; perdre ses objets suffit comme risque.
+```
+
+### P14 — Perdre ses affaires
+
+Retour sur l’existant et choix futur · À distinguer de la récupération Backrooms P90–P91.
+
+```text
+La bêta suit les pertes d’objets Minecraft quand keepInventory est désactivé. Une récupération spéciale dans les Backrooms est envisagée, mais n’existe pas encore.
+
+Quel poids la mort devrait-elle avoir sur tes affaires ?
+
+A — Une perte définitive possible, comme en survie classique.
+B — Une expédition difficile permet de récupérer presque tout.
+C — Les objets ordinaires restent à risque, quelques objets personnels sont protégés.
+D — Les affaires sont conservées ; la sanction passe par du temps ou des ressources.
+```
+
+### P15 — Les fonctions à acheter
+
+Retour sur l’existant · Grande carte, rangement et catalogue sont notamment des aptitudes.
+
+```text
+La bêta demande de l’XP pour ouvrir des fonctions comme la grande carte, le rangement ou le catalogue de recettes. Ces achats restent acquis après New Game+.
+
+Quel principe préfères-tu pour ces fonctions de confort ?
+
+A — Les acheter : elles donnent des premiers objectifs concrets.
+B — Les découvrir par de petites activités, sans prix en XP.
+C — Les avoir immédiatement et réserver l’XP aux capacités du personnage.
+D — Garder seulement certaines fonctions payantes, selon leur effet sur le jeu.
+```
+
+### P16 — Au-delà de six rangées
+
+Choix explicitement ouvert · Le plafond actuel comprend la hotbar.
+
+```text
+L’inventaire peut actuellement atteindre 54 cases, soit six rangées hotbar comprise. Un palier supplémentaire a été évoqué.
+
+Pour les gros projets, quelle suite préférerais-tu ?
+
+A — Garder ce plafond et développer les transports et stockages.
+B — Ajouter une dernière rangée de progression.
+C — Développer des sacs spécialisés plutôt qu’un inventaire général plus grand.
+D — Le plafond actuel suffit ; ce sujet peut attendre.
+```
+
+### P17 — L’aide à la construction
+
+Choix ouvert au-delà des plans de pose actuels.
+
+```text
+La bêta permet déjà de miner des veines et de poser des surfaces en consommant de vrais outils et matériaux.
+
+Jusqu’où voudrais-tu pousser l’aide à la construction en survie ?
+
+A — Des gestes groupés, mais je construis chaque partie moi-même.
+B — Des plans fantômes pour guider ma construction.
+C — Une machine qui construit un plan avec les matériaux que je fournis.
+D — Des bâtiments préfabriqués à transporter et installer.
+```
+
+### P18 — Les pouvoirs créatifs de fin de progression
+
+Choix ouvert · Aucun droit opérateur n’est promis.
+
+```text
+La vision imagine qu’à très long terme, la coopération donne des possibilités proches du créatif.
+
+Quelle forme te semblerait la plus intéressante ?
+
+A — Des gestes exceptionnels qui coûtent de l’XP et des ressources.
+B — Un espace de construction libre séparé du monde de survie.
+C — Des capacités créatives temporaires pour de grands projets communs.
+D — Un déblocage collectif permanent qui transforme le monde après un grand final.
+```
+
+## 3. Continents, ressources et voyages
+
+Sources : [expansion](expansion.md), [expéditions initiales](expeditions-beta001.md), [révision des climats et temples](expeditions-beta002.md), [machines et lieux](structures-conception.md), [vision](vision.md), [ressources aériennes alpha.30.7](generation-alpha30.7.md).
+
+### P19 — Ce que doit apporter une nouvelle île
+
+Choix ouvert · Distinction essentielle avec les quatre expéditions déjà ouvertes.
+
+```text
+Les nouveaux mondes ont déjà quatre îles d’expédition accessibles : boréale au nord, tropicale à l’est, aride au sud et océanique à l’ouest. Leur accès ne demande pas de déblocage collectif.
+
+Qu’est-ce qui justifierait le mieux de financer ensuite un nouveau continent ?
+
+A — Des ressources impossibles à trouver ailleurs.
+B — Un lieu de mystère, un donjon ou une nouvelle étape de l’histoire.
+C — Un nouveau territoire pour construire et s’installer.
+D — Un environnement qui permet de nouvelles cultures et productions.
+```
+
+### P20 — Le rythme d’ouverture
+
+Choix ouvert · Valeurs destinées à cadrer un prototype.
+
+```text
+Après les quatre îles déjà ouvertes, à quel rythme voudrais-tu que le groupe puisse créer de nouveaux continents ?
+
+A — Dès qu’il réunit les moyens, même plusieurs fois dans une semaine.
+B — Environ une ouverture importante par semaine de serveur actif.
+C — Environ une toutes les deux ou trois semaines.
+D — À des moments exceptionnels de l’histoire, sans cadence régulière.
+```
+
+### P21 — Qui choisit l’expansion
+
+Choix ouvert · Gouvernance à fixer avant les dépenses collectives.
+
+```text
+Une future installation permettra aux joueurs d’ouvrir un continent. Qui devrait décider du projet à financer ?
+
+A — L’ensemble du serveur, par un vote ouvert.
+B — Les personnes qui contribuent à ce projet.
+C — Chaque faction, pour ses propres expéditions.
+D — N’importe quel groupe qui construit l’installation et réunit les moyens.
+```
+
+### P22 — Reconnaître les contributions
+
+Choix ouvert · Dépend de P07 et P21.
+
+```text
+Pour financer un continent, certains pourront livrer énormément de matériaux et d’autres seulement un peu de temps.
+
+Quel fonctionnement te semblerait le plus juste ?
+
+A — Un pot commun libre ; chacun apporte ce qu’il peut.
+B — Plusieurs objectifs différents, pour que construction, exploration et production comptent.
+C — Une contribution individuelle plafonnée pour limiter le poids des plus gros producteurs.
+D — Des engagements annoncés à l’avance entre participants.
+```
+
+### P23 — Ce qui coûte dans une expansion
+
+Choix ouvert · Aucun coût actuel en survie.
+
+```text
+Quelle devrait être la principale difficulté pour créer un nouveau continent ?
+
+A — Réunir une grande quantité de ressources ordinaires.
+B — Trouver quelques composants rares en explorant.
+C — Construire une véritable chaîne de production.
+D — Accomplir un objectif collectif, puis payer un coût matériel modeste.
+```
+
+### P24 — Construire la machine d’expansion
+
+Choix ouvert · La possibilité de la reconstruire ailleurs est déjà retenue.
+
+```text
+L’installation d’expansion pourra être reconstruite chez les joueurs, et pas seulement dans une ruine ancienne.
+
+Quelle expérience voudrais-tu pour la mettre en route ?
+
+A — Assembler plusieurs blocs selon un plan puis alimenter le dépôt.
+B — Réparer une première machine pour comprendre comment en construire une autre.
+C — Concevoir soi-même les connexions et réglages d’une installation.
+D — Construire librement un bâtiment autour d’un appareil simple à utiliser.
+```
+
+### P25 — Choisir sans tout connaître
+
+Choix ouvert · Direction, climat et relief sont distincts.
+
+```text
+Avant de financer un continent, quelles informations voudrais-tu connaître sur la destination ?
+
+A — Seulement une ambiance et quelques indices ; je veux la surprise.
+B — Son climat et les grandes familles de ressources possibles.
+C — Ses grandes caractéristiques, avec quelques inconnues clairement annoncées.
+D — Une reconnaissance préalable détaillée avant d’engager les ressources.
+```
+
+### P26 — Une ressource indispensable manque
+
+Question déduite · Résout une difficulté de progression sans imposer des quotas au terrain.
+
+```text
+Si une partie manque d’une ressource indispensable à sa prochaine étape, quelle solution te semblerait la plus satisfaisante ?
+
+A — Un échange coûteux auprès d’un visiteur.
+B — Une quête qui donne juste de quoi débloquer la situation.
+C — Une expédition de secours vers un lieu où la rechercher.
+D — Une recette de remplacement moins efficace.
+```
+
+### P27 — Exploiter l’île de départ
+
+Question déduite · Tension entre stocks finis, patrimoine et production.
+
+```text
+Comment voudrais-tu gérer l’extraction à grande échelle sur l’île où tout le monde commence ?
+
+A — Chacun peut transformer le paysage dans le respect des propriétés.
+B — Certaines zones naturelles sont préservées et d’autres dédiées aux carrières.
+C — Les gros prélèvements se déplacent progressivement vers les nouveaux continents.
+D — La communauté décide des grands chantiers au cas par cas.
+```
+
+### P28 — Le prochain moyen de transport
+
+Choix ouvert · Les options de la vision ne sont pas annoncées comme livrées.
+
+```text
+Quel futur moyen de transport te donnerait le plus envie de relier les îles ?
+
+A — Des ziplines et des ponts construits ensemble.
+B — Des aéronefs pour voyager à plusieurs et transporter des animaux.
+C — Des waystones entre destinations déjà découvertes.
+D — Un grappin pour des trajets individuels plus risqués.
+```
+
+### P29 — Payer les téléportations
+
+Choix ouvert · La vision prévoit un coût d’XP croissant avec la distance.
+
+```text
+Pour les futures waystones, quel coût rendrait le voyage pratique tout en donnant un intérêt aux routes et véhicules ?
+
+A — Un peu d’XP à chaque trajet, davantage pour aller loin.
+B — Une construction chère, puis des trajets gratuits.
+C — Un consommable produit par les joueurs.
+D — Un nombre de trajets gratuits, puis un coût au-delà.
+```
+
+### P30 — L’accès à l’End
+
+Choix ouvert · Ne confond pas dimension vanilla et route de progression garantie.
+
+```text
+Quelle place voudrais-tu donner à la découverte de l’accès à l’End dans Sanctuary ?
+
+A — Une aventure personnelle, possible dès qu’on réunit les moyens.
+B — Une découverte collective qui ouvre ensuite l’accès à tous.
+C — Un objectif lié à l’ouverture d’un continent particulier.
+D — Une étape tardive après plusieurs grands projets du serveur.
+```
+
+## 4. Habiter ensemble, se protéger et se confronter
+
+Sources : [Demeure](demeure-beta006.md), [factions](cycle-factions-beta011.md), [CLAIM-01](prochains-tickets.md), [coffres-forts et risques de la vision](vision.md), [TNT portée déjà livrée](head-cosmetics-beta021.md).
+
+### P31 — Une maison sans faction
+
+Choix ouvert · Demeure n’est actuellement pas une protection.
+
+```text
+La bêta sait montrer les lieux habités avec Demeure, mais ne protège pas encore les constructions par des claims.
+
+Comment voudrais-tu protéger ta première maison ?
+
+A — Un petit terrain personnel gratuit dès l’arrivée.
+B — Un terrain personnel gagné après quelques activités de début de partie.
+C — Une protection fournie par une faction que je rejoins.
+D — Des règles de voisinage et une intervention humaine en cas de problème.
+```
+
+### P32 — Agrandir son territoire
+
+Choix ouvert · Demeure pourrait aider une demande, sans créer automatiquement un droit.
+
+```text
+Pour les futurs terrains protégés, qu’est-ce qui devrait principalement permettre d’agrandir une propriété ?
+
+A — Le nombre d’habitants qui y vivent réellement.
+B — Un investissement de ressources ou de monnaie.
+C — Les charges gagnées par les cycles de progression.
+D — Un projet de construction déclaré et validé par le voisinage.
+```
+
+### P33 — Les visiteurs chez soi
+
+Choix ouvert · Première règle par défaut, droits fins à détailler ensuite.
+
+```text
+Dans une future propriété protégée, que devrait pouvoir faire un visiteur par défaut ?
+
+A — Entrer et regarder, sans utiliser les équipements.
+B — Utiliser portes, sièges et équipements publics, sans toucher aux stocks.
+C — Récolter ce qui est indiqué comme partagé et utiliser les espaces communs.
+D — Rien sans invitation du propriétaire.
+```
+
+### P34 — Une maison abandonnée
+
+Choix ouvert · La décroissance des empreintes Demeure ne décide pas du sort des biens.
+
+```text
+Si quelqu’un ne revient plus pendant longtemps, que devrait devenir son terrain protégé ?
+
+A — Il reste protégé sans limite tant que le monde existe.
+B — Après avertissements et un long délai, une reprise peut être demandée.
+C — La faction peut le reprendre si cette règle était acceptée au départ.
+D — Une équipe d’administration examine chaque cas avec les voisins.
+```
+
+### P35 — Le responsable absent
+
+Retour sur l’existant · Aucun transfert automatique actuellement.
+
+```text
+Aujourd’hui, l’absence du propriétaire d’une faction ne transmet pas automatiquement son rôle.
+
+Comment voudrais-tu éviter qu’une faction reste bloquée si cette personne disparaît plusieurs semaines ?
+
+A — Des adjoints désignés à l’avance continuent à la gérer.
+B — Les membres élisent un nouveau responsable après un délai annoncé.
+C — Un administrateur intervient sur demande des membres.
+D — Le propriétaire garde seul ce rôle ; les membres peuvent partir créer autre chose.
+```
+
+### P36 — Ce que révèle Demeure
+
+Retour sur l’existant · L’Atlas montre déjà des empreintes dans les zones connues.
+
+```text
+Demeure peut indiquer qu’une zone explorée est habitée et par qui elle est surtout marquée.
+
+Quelle visibilité voudrais-tu pour ces traces de vie ?
+
+A — Tous les explorateurs de la zone peuvent les consulter.
+B — On voit qu’un lieu est habité, mais son occupant choisit de révéler son nom.
+C — Les détails sont réservés aux habitants et à leurs alliés.
+D — Chaque habitant choisit ce qu’il rend visible sur ses lieux.
+```
+
+### P37 — La place du PvP
+
+Choix ouvert · À trancher avant armes, familiers et braquages.
+
+```text
+Quelle place voudrais-tu donner aux combats entre joueurs dans Sanctuary ?
+
+A — Des duels acceptés par les deux personnes.
+B — Des arènes ou événements annoncés.
+C — Des conflits de factions déclarés, dans des zones et périodes précises.
+D — Un danger possible dans les territoires sauvages, avec des refuges protégés.
+```
+
+### P38 — Le principe du braquage
+
+Choix ouvert · Le coffre-fort perçable au foret en titane est encore une intention.
+
+```text
+La vision prévoit des coffres-forts cachés que l’on pourrait braquer avec un outil rare.
+
+Dans quel cadre aurais-tu envie de jouer avec cette mécanique ?
+
+A — Seulement lors d’événements auxquels on s’inscrit.
+B — Entre factions qui acceptent explicitement ce risque.
+C — Dans certains territoires dangereux, avec des lieux sûrs ailleurs.
+D — Je préfère réserver les braquages à des coffres de donjon, sans voler les joueurs.
+```
+
+### P39 — L’enjeu d’un braquage
+
+Choix ouvert · À publier seulement si P38 retient le vol entre joueurs.
+
+```text
+Dans un braquage entre participants qui ont accepté cette règle, quelle perte rendrait l’action intéressante sans ruiner la partie de la victime ?
+
+A — Un butin de mise choisi à l’avance.
+B — Une petite part plafonnée du contenu du coffre-fort.
+C — Un objet transportable qu’on peut poursuivre et récupérer.
+D — Tout le contenu du coffre-fort : cacher et défendre le coffre fait partie de l’enjeu.
+```
+
+### P40 — Les accidents qui touchent les autres
+
+Retour sur l’existant et choix ouvert · TNT portée existante ; lucky blocks et nouveaux explosifs futurs.
+
+```text
+La TNT portée existe déjà ; des lucky blocks et d’autres explosifs sont envisagés. Ces plaisanteries peuvent toucher les constructions ou les personnes autour.
+
+Quelle règle voudrais-tu pour leurs dégâts collatéraux ?
+
+A — Aucun dégât sur les propriétés et biens d’autrui.
+B — Des dégâts uniquement dans des zones de jeu prévues pour ça.
+C — Des dégâts possibles si toutes les personnes concernées ont accepté le risque.
+D — Des dégâts possibles dans le monde sauvage, avec les propriétés comme refuges.
+```
+
+## 5. Économie, quêtes et rendez-vous
+
+Sources : [vision économique et événements](vision.md), [cosmologie du shop](cosmologie.md), [temps réel et suivi des objectifs](realtime-beta037.md). Boutiques, monnaies nouvelles, quêtes et loterie restent à concevoir.
+
+### P41 — Où se trouve le shop
+
+Choix ouvert.
+
+```text
+Pour la future boutique du serveur, quel accès te semblerait le plus intéressant ?
+
+A — Un lieu commun où l’on se croise pour acheter et vendre.
+B — Un terminal que chaque groupe peut construire chez lui.
+C — Une boutique itinérante, présente à certains moments.
+D — Un lieu commun au départ, puis des terminaux personnels à débloquer.
+```
+
+### P42 — D’où viennent les marchandises
+
+Choix ouvert · Dépend de P26 pour les ressources indispensables.
+
+```text
+Que devrait principalement vendre la future boutique du serveur ?
+
+A — Ce que les joueurs ont réellement vendu ou déposé.
+B — Des ressources courantes disponibles en quantité limitée.
+C — Des ressources difficiles à trouver, à prix élevé.
+D — Des objets spéciaux ; les matériaux ordinaires s’échangent surtout entre joueurs.
+```
+
+### P43 — Le prix des ressources
+
+Choix ouvert · Aucun modèle de prix dynamique n’est présumé acquis.
+
+```text
+Comment voudrais-tu que les prix de la boutique du serveur évoluent ?
+
+A — Des prix fixes et faciles à apprendre.
+B — Des prix qui suivent l’offre et la demande des joueurs.
+C — Des variations dans une fourchette connue à l’avance.
+D — Des prix négociés directement entre joueurs, avec peu de commerce serveur.
+```
+
+### P44 — Les trois gemmes
+
+Choix ouvert · Les rôles sont une intention ; les taux de conversion ne sont pas définis.
+
+```text
+La vision distingue émeraudes pour les villageois, rubis pour les boutiques et saphirs pour réserver des objets dans un catalogue.
+
+Comment voudrais-tu passer d’une monnaie à l’autre ?
+
+A — Un échange simple à taux fixe.
+B — Un change avec des frais, pour garder l’intérêt de chaque activité.
+C — Des échanges entre joueurs, sans taux officiel.
+D — Pas de conversion directe : chaque monnaie se gagne par ses propres activités.
+```
+
+### P45 — Ce que garantit le catalogue en saphirs
+
+Choix explicitement ouvert · Distinct du catalogue de recettes déjà livré.
+
+```text
+Un futur catalogue payé en saphirs doit permettre de réserver des quantités limitées d’objets. Il est distinct du carnet de recettes actuel.
+
+Quel service voudrais-tu qu’il rende ?
+
+A — Mettre à l’abri des objets que j’ai déjà déposés.
+B — Réserver une part des marchandises réellement en stock.
+C — Précommander une quantité qu’un autre joueur devra produire.
+D — Assurer le remplacement limité d’un objet personnel perdu.
+```
+
+### P46 — Le marché des joueurs
+
+Choix ouvert · Le « black market » est ici un marché de joueurs.
+
+```text
+Quelle forme voudrais-tu pour acheter et vendre entre joueurs ?
+
+A — Des échoppes physiques à visiter.
+B — Un tableau d’annonces, puis un rendez-vous pour échanger.
+C — Un marché central avec livraison dans la boîte aux lettres.
+D — Des commandes de production rémunérées, en plus des ventes d’objets disponibles.
+```
+
+### P47 — Recevoir un achat
+
+Choix ouvert · Délais et logistique de la mailbox.
+
+```text
+Pour les futurs achats livrés dans une boîte aux lettres, quel délai ferait partie du plaisir de jeu ?
+
+A — Une livraison immédiate.
+B — Un court délai annoncé, sans transport à organiser.
+C — Une livraison à des heures connues, comme un passage du courrier.
+D — Un transport réel, que des joueurs ou des installations peuvent améliorer.
+```
+
+### P48 — Les animaux qui produisent de la monnaie
+
+Choix ouvert d’équilibrage · Les poules rares non reproductibles sont une intention retenue.
+
+```text
+La vision prévoit des poules rares, non reproductibles, qui pondent des gemmes très lentement.
+
+Quel rôle voudrais-tu pour cette production ?
+
+A — Un petit cadeau régulier, surtout pour le plaisir de posséder l’animal.
+B — Une activité d’élevage rentable si on soigne ses animaux.
+C — Une richesse exceptionnelle pour les personnes qui en trouvent une.
+D — Des objets de collection ou d’échange, plutôt qu’une source importante de monnaie.
+```
+
+### P49 — Le dimanche et les navets
+
+Question déduite · Le dimanche reste le jour d’achat prévu ; modalités d’accès ouvertes.
+
+```text
+La bourse du navet prévoit un achat uniquement le dimanche, puis une revente pendant la semaine à un cours variable.
+
+Comment voudrais-tu organiser l’achat dominical ?
+
+A — Pendant un rendez-vous collectif à heure fixe.
+B — À n’importe quel moment du dimanche.
+C — Par une commande préparée pendant la semaine et exécutée le dimanche.
+D — Par un autre joueur mandaté pour acheter à ma place le dimanche.
+```
+
+### P50 — Ce que peut donner la loterie
+
+Choix ouvert · Pas de paiement réel proposé ; cohérence à chercher avec les familiers.
+
+```text
+La future loterie utiliserait des tickets gagnés en jouant. Quelle place voudrais-tu donner au hasard dans ses récompenses ?
+
+A — Des cosmétiques, souvenirs et objets amusants.
+B — Des ressources utiles, sans récompense indispensable exclusive.
+C — Des objets puissants aussi obtenables par un objectif connu.
+D — Quelques objets exceptionnels accessibles uniquement par la chance.
+```
+
+### P51 — Les quêtes qui donnent envie
+
+Choix ouvert · Le suivi d’objectifs existe, les quêtes elles-mêmes sont futures.
+
+```text
+Quel type de quête aimerais-tu voir en premier sur les futurs panneaux de Sanctuary ?
+
+A — Livrer une production utile à quelqu’un.
+B — Partir enquêter ou explorer un lieu.
+C — Organiser une activité avec d’autres habitants.
+D — Réussir un défi de combat ou d’adresse.
+```
+
+### P52 — Limiter les quêtes sans imposer des horaires
+
+Choix ouvert · Réexamine la limite horaire envisagée.
+
+```text
+La vision envisage de limiter le nombre de quêtes récompensées pour éviter qu’une seule activité domine la progression.
+
+Quel rythme te conviendrait le mieux ?
+
+A — Une limite renouvelée chaque heure.
+B — Une réserve quotidienne utilisable quand je veux.
+C — Une réserve hebdomadaire utilisable en une ou plusieurs sessions.
+D — Pas de quota ; les récompenses diminuent quand je répète la même activité.
+```
+
+### P53 — Récompenser une quête commune
+
+Choix ouvert · Distinct de l’attribution d’une relique de boss.
+
+```text
+Quand plusieurs joueurs terminent ensemble une quête, comment voudrais-tu partager sa récompense ?
+
+A — Une récompense personnelle équivalente pour chaque participant.
+B — Un butin commun que le groupe répartit lui-même.
+C — Une part personnelle et une part pour le projet collectif.
+D — Une récompense proportionnelle à la contribution de chacun.
+```
+
+### P54 — Les rendez-vous du serveur
+
+Choix ouvert · Le cycle « six jours puis une fête » n’est pas fixé.
+
+```text
+Quel rythme donnerait envie de participer aux événements sans transformer Sanctuary en obligation ?
+
+A — Un grand rendez-vous régulier par semaine.
+B — Plusieurs petites activités proposées à des horaires différents.
+C — Des événements ouverts plusieurs jours, chacun avance quand il peut.
+D — Des événements spontanés organisés par les habitants.
+```
+
+### P55 — Jouer surtout de nuit
+
+Retour sur l’existant · Le temps réel et le sommeil sans saut de nuit sont déjà livrés.
+
+```text
+Dans la bêta, le ciel suit l’heure réelle du serveur et dormir ne fait plus passer la nuit. Si tu joues surtout le soir, tu peux donc voir souvent le monde de nuit.
+
+Quel aménagement te conviendrait le mieux ?
+
+A — Aucun : l’ambiance nocturne et les éclairages font partie du jeu.
+B — Des lieux habités suffisamment sûrs pour les activités ordinaires.
+C — Des moyens de mieux voir la nuit, accessibles assez tôt.
+D — Un horaire solaire du serveur choisi pour offrir aussi du jour pendant nos soirées.
+```
+
+## 6. Produire, automatiser et construire
+
+Sources : [machines et villageois](vision.md), [installation d’expansion](structures-conception.md), [plans](blocodex-navigation.md), [carte actuelle](atlas-beta005.md).
+
+### P56 — La première automatisation utile
+
+Choix ouvert · Les pouvoirs de familiers ne constituent pas ces futures machines.
+
+```text
+Quel premier système de production automatisée utiliserais-tu vraiment ?
+
+A — Des villageois rémunérés pour récolter et transformer.
+B — Des convoyeurs pour déplacer les objets entre installations.
+C — Un terminal pour retrouver les objets de plusieurs coffres.
+D — Un ordinateur programmable pour inventer mes propres automatismes.
+```
+
+### P57 — Les villageois d’un voisinage
+
+Choix ouvert · Cloche et bannière sont les interactions envisagées.
+
+```text
+Une future cloche surmontée d’une bannière pourrait rattacher des villageois à un voisinage ou une faction.
+
+Qui devrait pouvoir leur confier du travail ?
+
+A — Tous les membres du groupe associé à la cloche.
+B — Seulement les personnes désignées pour gérer le village.
+C — Tout habitant qui paie, avec priorité aux travaux du village.
+D — Le responsable de chaque atelier pour ses propres travailleurs.
+```
+
+### P58 — Produire pendant son absence
+
+Choix ouvert · Les clés de chunk sont envisagées ; l’horloge réelle ne simule pas les heures hors ligne.
+
+```text
+Des machines pourraient continuer à fonctionner dans des zones maintenues actives par une clé rare trouvée en exploration.
+
+Quelle place voudrais-tu donner à la production pendant l’absence des joueurs ?
+
+A — Elle s’arrête quand personne ne se trouve dans le secteur.
+B — Elle continue dans quelques ateliers choisis, avec une limite par groupe.
+C — Elle continue tant qu’on fournit un combustible ou un entretien.
+D — Elle continue librement une fois les installations et clés obtenues.
+```
+
+### P59 — Le grand stockage
+
+Choix ouvert · Le plafond de 128 coffres est une piste de fin de progression.
+
+```text
+Un futur terminal en titane permettrait de consulter de nombreux coffres depuis un seul endroit.
+
+Quelle contrainte voudrais-tu conserver pour que le rangement et la logistique restent intéressants ?
+
+A — Les coffres doivent être physiquement reliés au terminal.
+B — Le réseau reste limité à un bâtiment ou un quartier.
+C — Chaque extension coûte des matériaux rares.
+D — Une fois débloqué, le réseau peut réunir les stocks autorisés sans contrainte de distance.
+```
+
+### P60 — Programmer dans Minecraft
+
+Choix ouvert · Concerne l’ordinateur général, distinct de l’installation d’expansion P24.
+
+```text
+Pour le futur ordinateur programmable de Sanctuary, quelle manière de créer un automatisme te donnerait envie d’essayer ?
+
+A — Choisir et ajuster des programmes déjà prêts.
+B — Assembler visuellement des conditions et des actions.
+C — Relier des signaux et composants comme en redstone.
+D — Écrire un petit programme dans un langage à apprendre.
+```
+
+### P61 — Transmettre un plan de construction
+
+Choix ouvert · Aucun plan reçu ne vaut possession de ses matériaux.
+
+```text
+Comment voudrais-tu échanger de futurs plans de construction entre habitants ?
+
+A — Les copier librement, avec le nom de leur auteur.
+B — Les offrir ou les vendre comme des objets du jeu.
+C — Alimenter une bibliothèque publique de plans choisis par leurs auteurs.
+D — Laisser chaque auteur choisir entre partage libre, cadeau et vente.
+```
+
+### P62 — Se repérer sous l’île
+
+Choix déduit · La carte actuelle représente la surface supérieure.
+
+```text
+Sous l’île, les cavernes et installations se superposent. Quel outil t’aiderait le plus à t’orienter sans retirer le plaisir de l’exploration ?
+
+A — Des marqueurs personnels de lieux et d’entrées.
+B — Des cartes séparées pour les profondeurs déjà explorées.
+C — Des panneaux, balises et plans construits dans les galeries.
+D — Un fil de trajet temporaire permettant de retrouver mon chemin.
+```
+
+## 7. Découvertes, cartes et mémoire des habitants
+
+Sources : [navigation et vitrine](blocodex-navigation.md), [carte](atlas-beta005.md), [collections](collections-beta035.md), [fiches d’obtention](collections-minecraft.md), [histoire et site](progression-beta003.md).
+
+### P63 — Partager une carte
+
+Choix ouvert · Partage volontaire prévu ; pas de synchronisation automatique actuelle.
+
+```text
+La carte garde aujourd’hui les découvertes de chaque personne. Pour transmettre un territoire exploré à quelqu’un, quel geste aimerais-tu ?
+
+A — Lui remettre une copie de ma carte.
+B — Lui montrer la région sur une table cartographique commune.
+C — Choisir les zones à partager dans le Blocodex.
+D — Déposer mes relevés dans un atlas de faction auquel ses membres ont accès.
+```
+
+### P64 — Ce que montre la mini-carte
+
+Choix ouvert · Mini-carte et marqueurs restent des aptitudes futures distinctes.
+
+```text
+Quelle information voudrais-tu sur une future mini-carte ?
+
+A — Le relief déjà exploré et ma direction.
+B — Le relief et mes propres repères enregistrés.
+C — Le relief, mes repères et les alliés qui acceptent d’être visibles.
+D — Tout cela, plus les créatures proches.
+```
+
+### P65 — Découvrir une créature
+
+Choix ouvert · Ne reprend pas un ancien statut « blocs uniquement » comme description de tout Discovery actuel.
+
+```text
+Pour un futur bestiaire, quel geste devrait suffire à ouvrir la fiche d’une créature ?
+
+A — La voir à proximité.
+B — La viser et l’observer quelques secondes.
+C — Interagir avec elle d’une manière adaptée à son espèce.
+D — La rencontrer ouvre une fiche sommaire ; les interactions la complètent.
+```
+
+### P66 — Apprendre ce qui n’est pas une recette
+
+Choix ouvert · Les collections de recettes existent déjà ; les fiches d’obtention restent futures.
+
+```text
+Le carnet de recettes existe, mais certaines choses s’obtiennent par le commerce, la croissance, le combat ou une manipulation dans le monde.
+
+Quelle aide voudrais-tu pour ces méthodes ?
+
+A — Une explication complète après la première découverte.
+B — Un indice, puis des détails si je demande de l’aide.
+C — Des notes à trouver auprès de personnages ou dans des lieux.
+D — Un carnet rempli par mes propres expériences et les conseils reçus des autres.
+```
+
+### P67 — Découvrir l’alchimie
+
+Retour sur l’existant · Une évolution ne retire pas les recettes déjà apprises.
+
+```text
+La bêta révèle actuellement une grande collection d’alchimie dès les premiers progrès correspondants, même si les ingrédients rares restent à trouver.
+
+Pour de futures découvertes, quel rythme préférerais-tu ?
+
+A — Connaître tôt les possibilités pour préparer mes recherches.
+B — Apprendre les grandes familles de potions en plusieurs étapes.
+C — Découvrir chaque effet en expérimentant.
+D — Apprendre surtout grâce à des pages et aux autres joueurs.
+```
+
+### P68 — La créature favorite
+
+Choix ouvert dans un cadre retenu · Favori découvert, changement toutes les 24 heures réelles.
+
+```text
+Le futur profil permettrait de choisir une créature favorite déjà découverte et d’en changer au plus une fois toutes les 24 heures. Une petite affinité est envisagée.
+
+Quel effet te semblerait cohérent avec ce choix ?
+
+A — Faciliter légèrement l’apprivoisement quand l’espèce s’y prête.
+B — Améliorer un peu ses productions, comme la laine ou les œufs.
+C — Donner des réactions et interactions particulières, sans avantage économique.
+D — Modifier légèrement le butin obtenu en la chassant.
+```
+
+### P69 — La vitrine de l’habitant
+
+Choix ouvert · Les trophées et favoris ne sont pas annoncés comme déjà livrés.
+
+```text
+Dans une future fiche que les autres peuvent consulter, qu’aimerais-tu surtout montrer de ta vie sur Sanctuary ?
+
+A — Mes constructions et les projets auxquels j’ai participé.
+B — Mes exploits et collections.
+C — Mes découvertes, voyages et lieux favoris.
+D — Mes liens avec les autres, ma faction et mes souvenirs.
+```
+
+### P70 — Sanctuary entre deux connexions
+
+Choix ouvert · Gazette et connexion au site restent futures.
+
+```text
+Si Sanctuary avait un site relié à la vie du serveur, quelle fonction utiliserais-tu le plus entre deux connexions ?
+
+A — Lire la Gazette et les événements récents.
+B — Préparer un projet ou m’inscrire à une activité.
+C — Consulter les cartes, découvertes et archives que les joueurs ont partagées.
+D — Passer des commandes et échanger avec les autres habitants.
+```
+
+## 8. Familiers, objets rares et capes
+
+Sources : [refonte v2](spawn-eggs-refonte-v2.md), [livraison des 88 pouvoirs](spawn-eggs-v2-beta032.md), [catalogue courant](spawn-eggs-v2-catalogue-beta032.md), [pistes d’acquisition antérieures](companion-powers-balance-proposal.md), [accessoires](companions-cosmetics-beta018.md). Les propositions v1 d’acquisition ne sont pas des décisions acquises.
+
+### P71 — Obtenir son premier familier
+
+Choix explicitement ouvert · Les pouvoirs sont livrés, leur acquisition normale en survie reste à concevoir.
+
+```text
+La bêta possède déjà les pouvoirs de 88 familiers, mais leur obtention en survie reste à construire.
+
+Comment voudrais-tu obtenir ton premier œuf de familier ?
+
+A — En prenant soin d’un animal ou en réussissant une interaction avec son espèce.
+B — En terminant une petite quête dont la récompense est connue.
+C — En découvrant un lieu ou un coffre, avec une part de surprise.
+D — En choisissant un compagnon de départ, puis en recherchant les suivants.
+```
+
+### P72 — Échanger des familiers
+
+Choix ouvert · Économie des œufs non arrêtée.
+
+```text
+Quelle place voudrais-tu donner aux échanges de futurs œufs de familiers obtenus en survie ?
+
+A — Les vendre, les offrir et les prêter librement.
+B — Les offrir et les prêter, sans marché monétaire.
+C — Les échanger avant de les lier à un habitant ; le lien devient ensuite personnel.
+D — Les garder personnels, chacun développe ses propres liens.
+```
+
+### P73 — Perdre son compagnon
+
+Choix ouvert · L’œuf équipé suit actuellement les pertes d’inventaire à la mort.
+
+```text
+Le familier est invulnérable, mais son œuf reste un objet : avec les règles actuelles, il peut tomber à la mort.
+
+Quelle règle voudrais-tu pour un œuf difficilement obtenu en survie ?
+
+A — Une perte définitive possible, comme les autres objets précieux.
+B — Une récupération par une quête ou une expédition.
+C — Recréer l’œuf contre un coût, en conservant le lien déjà acquis.
+D — Le conserver toujours avec le personnage.
+```
+
+### P74 — Les rangs des familiers
+
+Choix explicitement ouvert · Les rangs I–III n’existent pas encore.
+
+```text
+Des rangs I à III ont été évoqués pour les familiers, sans règle arrêtée.
+
+Quelle évolution te donnerait envie de garder le même compagnon longtemps ?
+
+A — Un pouvoir un peu plus efficace.
+B — Des variantes de pouvoir à choisir selon mon activité.
+C — Des comportements, apparences et souvenirs liés à nos aventures.
+D — Aucune montée en rang ; chaque espèce garde son intérêt dès l’obtention.
+```
+
+### P75 — Les objets Anomaly
+
+Choix explicitement ouvert · Distinguer rareté, puissance et comportement inhabituel.
+
+```text
+La vision prévoit des objets et familiers « Anomaly », très inhabituels et rares.
+
+Qu’est-ce qui devrait surtout les rendre désirables ?
+
+A — Une apparence et une histoire uniques.
+B — Un pouvoir étrange qui permet de nouvelles idées.
+C — Une version plus puissante d’un objet ou compagnon connu.
+D — Une capacité collective exceptionnelle, utilisable à de rares occasions.
+```
+
+### P76 — Le retour après essai d’un pouvoir
+
+Retour en jeu · À utiliser après un essai, avec œufs fournis dans un monde de test si nécessaire.
+
+```text
+Après avoir essayé un familier de la bêta, dans quelle situation précise son pouvoir t’a-t-il donné envie de le garder — ou de le remplacer ?
+
+Pour qu’on puisse comparer : indique la version, l’espèce, ton stade de progression, l’activité, la durée approximative de l’essai et si tu étais seul ou en groupe. Décris un usage réussi ou un moment où le pouvoir n’a servi à rien.
+```
+
+### P77 — Ce que raconte une cape
+
+Choix ouvert · La Cape Zéro actuelle a un visuel provisoire.
+
+```text
+Quelle histoire voudrais-tu qu’une cape raconte sur la personne qui la porte ?
+
+A — Sa participation à un événement du serveur.
+B — Un exploit personnel difficile.
+C — Son appartenance à une faction ou à un projet.
+D — Son goût esthétique, avec des modèles qu’on peut collectionner librement.
+```
+
+## 9. Ruines, donjons et fin de l’histoire
+
+Sources : [cosmologie et canon](cosmologie.md), [lieux et donjon majeur](structures-conception.md), [vision des sept boules](vision.md). Les posts publics n’exposent pas le sacrifice de Steve ni une solution d’énigme. Les identités et relations déjà retenues des personnages ne sont pas soumises au vote.
+
+### P78 — Découvrir l’histoire des disparus
+
+Choix ouvert · Porte sur la mise en scène, pas sur la réécriture du canon.
+
+```text
+Sanctuary imagine les ruines d’une partie de Minecraft habitée avant nous. Comment voudrais-tu découvrir l’histoire de ces anciens habitants ?
+
+A — Par les lieux eux-mêmes, leurs machines et leurs travaux interrompus.
+B — Par des journaux et lettres à retrouver puis à comparer entre joueurs.
+C — Par des apparitions et rencontres avec des personnages.
+D — Par des quêtes qui relient progressivement les indices.
+```
+
+### P79 — La première utilité d’une ruine
+
+Choix ouvert · À décliner ensuite pour chaque lieu retenu.
+
+```text
+Quand tu découvres une ancienne installation, qu’est-ce qui te donnerait le plus envie d’y revenir ?
+
+A — Pouvoir la réparer et l’utiliser.
+B — Y trouver un mystère qui se dévoile progressivement.
+C — En faire un abri ou un lieu de rendez-vous.
+D — Y apprendre une technique que je pourrai reproduire chez moi.
+```
+
+### P80 — Ce que débloque le grand donjon
+
+Choix explicitement ouvert · Les quatre expéditions initiales restent ouvertes.
+
+```text
+Un grand donjon avec un boss et un objet essentiel est envisagé. Quelle étape voudrais-tu qu’il débloque ?
+
+A — La capacité de créer de nouveaux continents après les quatre îles initiales.
+B — Une technologie ou un type de production avancé.
+C — L’accès à une nouvelle dimension.
+D — Une étape du grand objectif final du serveur.
+```
+
+### P81 — Le type de défi d’un boss
+
+Choix ouvert.
+
+```text
+Quel type de difficulté rendrait un boss de Sanctuary mémorable pour toi ?
+
+A — Observer ses attaques et apprendre à bien les éviter.
+B — Se répartir des rôles et coordonner le groupe.
+C — Préparer le terrain, les ressources et les équipements avant le combat.
+D — Comprendre une énigme ou modifier son environnement pendant la rencontre.
+```
+
+### P82 — À qui appartient la victoire
+
+Choix ouvert · Distinguer l’objet collectif du butin personnel.
+
+```text
+Quand un groupe vainc pour la première fois un boss indispensable à la progression, qui devrait profiter de son grand déblocage ?
+
+A — Tout le serveur immédiatement.
+B — Sa faction ou son groupe, les autres peuvent réussir leur propre épreuve.
+C — Les participants, avec un parcours accessible ensuite aux nouveaux arrivants.
+D — Tout le serveur pour l’accès, avec une récompense personnelle pour les participants.
+```
+
+### P83 — Rejouer un grand événement
+
+Choix ouvert · La répétition ne suppose pas de régénérer la structure.
+
+```text
+Après la première victoire contre un grand boss, quelle place voudrais-tu donner aux nouveaux combats ?
+
+A — Une rencontre unique ; les nouveaux joueurs découvrent ses conséquences.
+B — Un combat rejouable à la demande, sans redonner la relique collective.
+C — Un rituel coûteux permet de refaire la rencontre.
+D — Un événement périodique permet aux absents et nouveaux venus de participer.
+```
+
+### P84 — Le donjon après la victoire
+
+Choix ouvert · Cohérent avec les installations réappropriables de la vision.
+
+```text
+Une fois un donjon exploré et son boss vaincu, que voudrais-tu pouvoir faire du lieu ?
+
+A — Le restaurer et y installer des activités.
+B — Exploiter ses ressources et certains spawners conservés.
+C — Le préserver comme monument de l’histoire du serveur.
+D — Le transformer librement avec les autres participants.
+```
+
+### P85 — Une réussite collective
+
+Choix ouvert · Ne révèle pas le dénouement narratif.
+
+```text
+Quel accomplissement te ferait dire « on a vraiment réussi cette aventure Sanctuary ensemble » ?
+
+A — Avoir transformé les îles en un monde habité et relié.
+B — Avoir résolu le grand mystère de ce monde.
+C — Avoir rendu possibles des constructions et techniques extraordinaires.
+D — Avoir créé une communauté dont les projets continuent sans objectif final imposé.
+```
+
+### P86 — Les trois grands accomplissements manquants
+
+Choix explicitement ouvert · Idéation, sans promettre qu’un résultat deviendra une boule.
+
+```text
+La vision comporte sept grands objets à réunir ; trois de leurs épreuves restent à inventer. Quel accomplissement encore peu représenté mériterait sa place ?
+
+A — Construire un ouvrage exceptionnel à plusieurs.
+B — Restaurer un territoire et sa vie animale ou végétale.
+C — Relier des habitants par un réseau de transport ou d’entraide.
+D — Créer un grand festin à partir de productions venues de plusieurs îles.
+
+Une idée précise peut remplacer les propositions.
+```
+
+## 10. Autres dimensions, Backrooms et Indoors
+
+Sources : [dimensions de la vision](vision.md), [matière, espaces et mémoire](cosmologie.md). Tous ces espaces restent futurs. P92–P93 exposent une idée de cosmologie : les garder pour un groupe volontaire si l’on veut préserver cette découverte en jeu.
+
+### P87 — La première nouvelle dimension
+
+Choix ouvert de priorité.
+
+```text
+Quel futur espace de Sanctuary aurais-tu le plus envie d’explorer en premier ?
+
+A — Des cavernes profondes dédiées à l’exploration et au minage.
+B — Un monde qui retrouve l’ambiance des anciennes versions de Minecraft.
+C — Des Backrooms étranges, où des lieux semblent avoir perdu leur fonction.
+D — Des Indoors : des intérieurs plus grands que le bâtiment qui les contient.
+```
+
+### P88 — Entrer dans les Backrooms
+
+Choix ouvert · Le lit est l’accès envisagé, pas une mécanique actuelle.
+
+```text
+Pour les futures Backrooms, quelle manière d’entrer créerait le bon mélange de curiosité et d’appréhension ?
+
+A — Une action volontaire depuis un lit.
+B — Un passage étrange découvert dans une ruine.
+C — Une machine à préparer avec d’autres habitants.
+D — Un événement rare annoncé par des signes, avec le choix de le suivre.
+```
+
+### P89 — Revenir des Backrooms
+
+Choix explicitement ouvert.
+
+```text
+Une fois dans les futures Backrooms, quelle règle de retour te donnerait envie d’explorer ?
+
+A — Revenir à tout moment par l’accès d’entrée.
+B — Retrouver un point de sortie sûr dans le lieu.
+C — Préparer un objet de retour avant de partir.
+D — Trouver une sortie, avec un retour d’urgence coûteux si je suis bloqué.
+```
+
+### P90 — Retrouver les affaires de quelqu’un
+
+Choix explicitement ouvert · Distinct des matériaux produits par le ballast.
+
+```text
+La vision imagine que certains objets perdus dans le vide ou brûlés puissent réapparaître dans les Backrooms.
+
+Si tu trouves les affaires d’un autre habitant, quelle règle voudrais-tu ?
+
+A — Elles restent réservées à leur propriétaire.
+B — Je peux les rapporter contre une récompense prévue pour le sauvetage.
+C — Elles deviennent récupérables par tous après un délai de priorité.
+D — Ce qui est trouvé appartient à l’explorateur.
+```
+
+### P91 — Le délai pour récupérer une perte
+
+Choix explicitement ouvert · Rétention à confronter aux habitudes de P04–P06.
+
+```text
+Si les Backrooms permettent un jour de récupérer des affaires perdues, quelle durée minimale voudrais-tu pour avoir une chance de les chercher ?
+
+A — Jusqu’à la fin de ma session suivante.
+B — Plusieurs jours réels.
+C — Plusieurs semaines réelles.
+D — Sans limite de temps pour les objets personnels importants.
+```
+
+### P92 — Le prix caché des technologies
+
+Choix explicitement ouvert · Cosmologie, à publier auprès de joueurs volontaires.
+
+```text
+Une idée de Sanctuary : certaines technologies, comme téléporter ou créer un espace, laissent des résidus qui deviennent les paysages des Backrooms.
+
+Quelle conséquence voudrais-tu ressentir dans le jeu ?
+
+A — Une trace de nos actions, sans coût matériel supplémentaire.
+B — Un petit coût annoncé dont on retrouve ailleurs les traces.
+C — Une conséquence que les joueurs peuvent apprendre à réduire ou orienter.
+D — Un risque collectif qui oblige à choisir quelles technologies utiliser.
+```
+
+### P93 — Reconnaître l’histoire dans le paysage
+
+Choix ouvert · Le principe d’une géologie liée à l’activité est retenu, sa lecture reste à concevoir.
+
+```text
+Dans les futures Backrooms, une période où le serveur a beaucoup utilisé le cuivre pourrait laisser des lieux techniques ; une autre période laisserait d’autres traces.
+
+Comment voudrais-tu reconnaître l’histoire du serveur dans ces paysages ?
+
+A — Par l’ambiance et les matériaux, sans explication directe.
+B — Par des dates et archives à retrouver sur place.
+C — Par des lieux reconnaissables liés à nos grands projets.
+D — Par un carnet qui relie les découvertes aux événements vécus.
+```
+
+### P94 — À quoi sert un Indoor
+
+Choix ouvert · Priorité d’usage avant les dimensions exactes.
+
+```text
+Un futur Indoor serait un intérieur artificiel plus grand que son accès extérieur.
+
+Quel premier usage te donnerait envie d’en construire un ?
+
+A — Une maison ou un atelier personnel.
+B — Un grand lieu collectif : salle des fêtes, musée, marché.
+C — Un espace d’expérience pour les machines et la redstone.
+D — Une salle d’épreuve ou d’aventure créée pour les autres joueurs.
+```
+
+### P95 — Les droits dans un Indoor partagé
+
+Choix explicitement ouvert.
+
+```text
+Pour un futur Indoor construit à plusieurs, qui devrait contrôler les accès et les modifications ?
+
+A — Un propriétaire qui donne des permissions aux autres.
+B — Plusieurs copropriétaires avec les mêmes droits.
+C — La faction, selon ses rôles.
+D — Le propriétaire de l’entrée extérieure, comme une extension de son bâtiment.
+```
+
+### P96 — Quand l’entrée d’un Indoor disparaît
+
+Choix explicitement ouvert · Ne décide aucune destruction réelle d’espace sauvegardé.
+
+```text
+Si l’accès à ton futur Indoor est perdu ou détruit, quel parcours voudrais-tu pour le retrouver ?
+
+A — Récupérer une nouvelle clé dans ma boîte aux lettres.
+B — Fabriquer un accès de remplacement avec un coût annoncé.
+C — Passer par un registre collectif des espaces existants.
+D — Faire une petite expédition pour rétablir le lien.
+```
+
+### P97 — La sensation d’espace d’un Indoor
+
+Choix explicitement ouvert · Porte sur l’expérience, pas sur le choix technique d’un tore 3D.
+
+```text
+Quelle sensation voudrais-tu dans un futur Indoor ?
+
+A — Une pièce de taille connue, facile à aménager.
+B — Un espace qui peut s’agrandir par extensions payées.
+C — Un lieu impossible où certaines portes ou directions ramènent ailleurs dans la pièce.
+D — Plusieurs catégories : des pièces pratiques et des espaces étranges distincts.
+```
+
+## 11. Cuisine, vivant et surprises
+
+Sources : [vision — It’s Alive !, Only Fun, faune et équipements](vision.md), [pouvoirs et limites](spawn-eggs-v2-beta032.md). Les modules de cuisine et d’humour gardent leur autonomie ; ils ne sont pas une condition pour jouer au socle.
+
+### P98 — La profondeur de la cuisine
+
+Choix ouvert.
+
+```text
+Pour le futur module de cuisine It’s Alive !, quel niveau de préparation t’intéresserait ?
+
+A — Beaucoup de plats, avec des recettes simples.
+B — Quelques ustensiles et deux ou trois étapes par plat.
+C — De vraies chaînes de préparation, fermentation et affinage.
+D — Des bases simples, puis une cuisine élaborée pour ceux qui veulent en faire leur activité.
+```
+
+### P99 — Les cultures et les climats
+
+Choix ouvert dans un principe retenu · Les cultures doivent avoir une origine géographique.
+
+```text
+La vision veut que certaines cultures dépendent du climat de leur île. Jusqu’où voudrais-tu pouvoir les déplacer après leur découverte ?
+
+A — Les faire pousser partout une fois les graines rapportées.
+B — Les cultiver ailleurs avec une serre ou un aménagement adapté.
+C — Les déplacer seulement vers des climats compatibles.
+D — Garder certaines productions locales pour créer des échanges entre îles.
+```
+
+### P100 — Le temps dans la nourriture
+
+Question déduite · L’affinage est prévu ; le pourrissement général ne l’est pas.
+
+```text
+Le vin, le fromage et certaines préparations pourraient gagner en qualité avec le temps. Quelle place voudrais-tu donner au vieillissement des aliments ?
+
+A — Seulement des améliorations : une absence ne gâche pas mes stocks.
+B — Des étapes à surveiller, mais une préparation terminée se conserve.
+C — Des produits périssables, avec des moyens de conservation accessibles.
+D — Le temps intervient seulement pendant mes sessions ou quand quelqu’un gère l’atelier.
+```
+
+### P101 — Les pages de cuisine
+
+Choix ouvert · Pages perdues et pages secrètes sont des pistes de la vision.
+
+```text
+Des pages de recettes perdues et des techniques secrètes sont envisagées pour la cuisine.
+
+Comment voudrais-tu transmettre une technique rare à quelqu’un ?
+
+A — Lui montrer la recette suffit à lui apprendre.
+B — Copier ou offrir une page de mon livre.
+C — Cuisiner ensemble au moins une fois.
+D — Il doit trouver sa propre page ; je peux seulement lui donner des indices.
+```
+
+### P102 — L’humour d’Only Fun
+
+Choix ouvert · Le module peut être distinct du socle Sanctuary.
+
+```text
+Only Fun envisage des interactions potaches, des anniversaires et des blagues physiques, y compris de l’humour de toilettes.
+
+Quel cadre te conviendrait pour ce contenu ?
+
+A — Il fait partie de l’ambiance générale du serveur.
+B — Chacun peut désactiver les interactions qui le visent.
+C — Il est réservé à des événements ou espaces prévus pour ça.
+D — Je préfère garder ce module en dehors de notre partie Sanctuary.
+```
+
+### P103 — Les nouveaux monstres
+
+Choix ouvert · Les zombies spéciaux et boss ne sont pas encore livrés.
+
+```text
+La vision prévoit des zombies aux comportements particuliers et de nouveaux boss. Où voudrais-tu surtout les rencontrer ?
+
+A — Dans des donjons et expéditions annoncés comme dangereux.
+B — Dans certaines régions sauvages, reconnaissables avant d’y entrer.
+C — Lors d’événements ponctuels qui menacent les lieux habités.
+D — Dans la survie ordinaire, pour renouveler les rencontres de tous les jours.
+```
+
+### P104 — Les armes nouvelles
+
+Choix ouvert · Les munitions et valeurs viendront après leur rôle.
+
+```text
+Des armes de précision et explosives sont envisagées pour Sanctuary. Quel rôle devraient-elles surtout jouer ?
+
+A — Offrir des alternatives aux armes Minecraft, avec des usages différents.
+B — Donner des outils spécialisés contre certains monstres ou obstacles.
+C — Former un palier puissant de fin de progression, avec des munitions coûteuses.
+D — Rester des objets d’événement ; les armes Minecraft suffisent pour la survie ordinaire.
+```
+
+### P105 — Les objets qui créent des activités
+
+Choix ouvert de priorité · Caméra, particuleur et œil de lumière restent futurs.
+
+```text
+Quel petit objet inédit te donnerait le plus d’idées d’activités avec les autres ?
+
+A — Une caméra pour exposer nos photos sur des cartes dans le monde.
+B — Un émetteur de particules réglable pour décorer des lieux et événements.
+C — Un objet qui montre les niveaux de lumière pour aménager nos constructions.
+D — Un jukebox capable de jouer les morceaux choisis par les habitants.
+```
+
+### P106 — S’attacher aux animaux rares
+
+Choix ouvert · Poules précieuses, Mooblooms et rencontres fantomatiques restent des contenus distincts.
+
+```text
+Qu’est-ce qui te donnerait le plus envie de chercher et protéger des animaux rares de Sanctuary ?
+
+A — Des apparences singulières à découvrir et montrer.
+B — Des comportements et interactions qui créent de l’attachement.
+C — Des productions rares utiles à la communauté.
+D — Une histoire liée à leur lieu ou aux circonstances de leur rencontre.
+```
+
+## 12. Ciel, calendrier et vie commune
+
+Sources : [ciel et calendrier](vision.md), [étoiles et histoire commune](progression-beta003.md), [temps réel livré](realtime-beta037.md). Le soleil réel existe ; le ciel mémoriel reste à concevoir.
+
+### P107 — Lire le ciel du serveur
+
+Choix explicitement ouvert · Les arrivées et accomplissements doivent créer les étoiles.
+
+```text
+La vision imagine un ciel qui s’enrichit avec l’arrivée des habitants et leurs accomplissements.
+
+Quelle organisation t’aiderait le mieux à y lire notre histoire ?
+
+A — Des groupes d’étoiles autour de chaque habitant.
+B — Des groupes autour des grandes aventures et projets collectifs.
+C — Des régions du ciel associées aux différentes périodes du serveur.
+D — Un ciel libre à explorer à la longue-vue, avec l’histoire de chaque étoile.
+```
+
+### P108 — Dessiner une constellation commune
+
+Choix explicitement ouvert · Droits à déterminer avant les prix des points et segments.
+
+```text
+Si les habitants peuvent un jour relier des étoiles en constellations visibles par tous, qui devrait pouvoir modifier un dessin ?
+
+A — Son auteur et les personnes qu’il invite.
+B — Sa faction, dans une partie du ciel qui lui est attribuée.
+C — Tout le monde, avec un historique permettant de restaurer les dessins.
+D — La communauté valide les dessins destinés au ciel public.
+```
+
+### P109 — Le calendrier de Sanctuary
+
+Choix explicitement ouvert · Concerne la présentation narrative, pas le fonctionnement de l’horloge.
+
+```text
+Pour dater les événements dans le jeu et les archives, quel calendrier aimerais-tu lire ?
+
+A — Les dates réelles habituelles.
+B — Le nombre de jours depuis l’ouverture de notre serveur.
+C — Un calendrier commun à Sanctuary, lié à une origine historique de Minecraft.
+D — La date réelle accompagnée de l’âge de notre monde.
+```
+
+### P110 — La musique des autres
+
+Choix ouvert · Comportement d’écoute avant le choix technique de lecture des disques.
+
+```text
+Si les futurs jukebox permettent de jouer des morceaux choisis par les habitants, quelle règle d’écoute te conviendrait ?
+
+A — Un son local qui s’atténue avec la distance, comme un jukebox.
+B — Il faut accepter d’écouter le jukebox la première fois.
+C — La musique personnalisée reste dans des lieux et événements annoncés.
+D — Chacun lance le morceau pour soi depuis le jukebox.
+```
+
+## 13. Rendre les essais accessibles
+
+Sources : [essais et limites](testing.md), [chargement](initial-loading.md), [profil rapide](test-rapide-beta033.md), [distribution](packwiz.md), [vision du pack](vision.md). Ces questions recueillent des besoins ; elles ne promettent ni compatibilité de mod externe ni date d’ouverture.
+
+### P111 — Le premier obstacle à enlever
+
+Question déduite · Le vocal, les effets et les interfaces doivent rester utilisables selon les joueurs.
+
+```text
+Quel obstacle faudrait-il enlever en premier pour que tu puisses profiter d’une nouvelle partie Sanctuary ?
+
+A — Une installation ou des mises à jour trop compliquées.
+B — Les performances et les temps de chargement.
+C — Les petits textes, commandes nombreuses ou effets visuels difficiles à supporter.
+D — Les activités qui demandent d’être en vocal ou disponible à une heure précise.
+
+Tu peux décrire ton besoin sans donner d’information personnelle.
+```
+
+### P112 — Le premier essai ensemble
+
+Question déduite · À publier après avoir identifié les décisions prioritaires.
+
+```text
+Quel petit essai concret serais-tu le plus curieux de faire pour guider la suite de Sanctuary ?
+
+A — Recommencer une heure de survie pour juger le départ et la progression.
+B — Essayer quelques familiers dans une activité précise, seul puis en groupe.
+C — Faire une expédition et un chantier pour juger les trajets et les ressources.
+D — Jouer une situation de voisinage ou d’échange pour préciser nos règles communes.
+```
+
+## 14. Questions de second tour
+
+Ces questions précisent une direction après les premières réponses. Sources : [vision](vision.md), [cosmologie](cosmologie.md), [aptitudes et ciel](progression-beta003.md). Elles sont ouvertes ; P115 et P120 complètent aussi les règles de présentation et de publication.
+
+### P113 — Les offres qui changent toutes les heures
+
+Choix ouvert · À rapprocher du commerce et des habitudes de connexion.
+
+```text
+La vision envisage des offres de boutique renouvelées toutes les heures. Quelle règle te permettrait d’en profiter sans surveiller le jeu toute la journée ?
+
+A — Des offres communes visibles à l’avance dans un programme.
+B — Des offres qui restent réservées pendant un délai après ma première consultation.
+C — Un lot d’offres personnel disponible pour plusieurs jours.
+D — Des offres communes qui changent vraiment chaque heure ; les bonnes occasions se racontent entre joueurs.
+```
+
+### P114 — Les braquages pendant une absence
+
+Choix ouvert · Uniquement si le groupe retient des braquages entre joueurs en P38.
+
+```text
+Si nous retenons des braquages entre joueurs volontaires, quand un coffre-fort devrait-il pouvoir être attaqué ?
+
+A — Seulement quand son propriétaire est connecté.
+B — Quand au moins un membre de sa faction peut le défendre.
+C — Pendant des créneaux annoncés et acceptés à l’avance.
+D — À tout moment ; cacher le coffre et préparer ses défenses fait partie du risque accepté.
+```
+
+### P115 — Connaître la vie d’une créature
+
+Choix explicitement ouvert · L’affichage des noms existe, l’aptitude de lecture des PV est distincte.
+
+```text
+Une aptitude permettant de lire la vie des créatures est envisagée. Quelle information voudrais-tu obtenir en les observant ?
+
+A — Leur nombre exact de points de vie.
+B — Une barre sans nombre précis.
+C — Un état général : indemne, blessée, très affaiblie.
+D — Seulement la vie des animaux alliés ; les adversaires gardent leur incertitude.
+```
+
+### P116 — Les étoiles après un nouveau cycle
+
+Choix explicitement ouvert · Le prestige actuel ne recrée pas les advancements.
+
+```text
+Le futur ciel doit garder les traces des habitants et de leurs accomplissements. Comment voudrais-tu y voir les nouveaux cycles d’un même joueur ?
+
+A — Son étoile d’origine évolue avec son prestige.
+B — Chaque cycle ajoute un petit groupe distinct autour d’elle.
+C — Les cycles apparaissent dans les archives, sans ajouter d’étoiles.
+D — Seuls les nouveaux événements réellement vécus pendant le cycle enrichissent le ciel.
+```
+
+### P117 — Extraire la matière des Backrooms
+
+Choix explicitement ouvert · Ne concerne pas les affaires personnelles à restituer.
+
+```text
+Si les activités du serveur laissent des matériaux dans les futures Backrooms, quelle place voudrais-tu donner à leur récupération ?
+
+A — Ce sont surtout des décors et des traces historiques.
+B — On peut en extraire des ressources ordinaires, avec les risques du lieu.
+C — On y récupère des résidus à transformer par un métier particulier.
+D — Quelques ressources exceptionnelles récompensent des expéditions préparées.
+```
+
+### P118 — Les biomes propres à Sanctuary
+
+Choix ouvert de direction · Le catalogue historique est une réserve, pas cent biomes promis à la prochaine livraison.
+
+```text
+Pour de futurs biomes propres à Sanctuary, avec de nouveaux bois, fleurs ou matériaux, quelle direction voudrais-tu privilégier ?
+
+A — Des paysages naturels crédibles mais absents de Minecraft.
+B — Des environnements étranges liés aux mystères du monde.
+C — Des lieux qui permettent surtout de nouvelles constructions et productions.
+D — Réinterpréter les paysages marquants des anciennes parties Sanctuary.
+```
+
+### P119 — Le premier passage entre deux îles
+
+Question déduite · Les quatre îles initiales sont ouvertes mais leurs trajets restent un enjeu de survie.
+
+```text
+Pour ton premier vrai passage entre deux îles de Sanctuary, quelle expérience aimerais-tu vivre ?
+
+A — Construire prudemment un passage avec les moyens du début.
+B — Fabriquer rapidement un véhicule simple et voyager à plusieurs.
+C — Préparer un équipement personnel puis tenter une traversée risquée.
+D — Rejoindre une expédition organisée par des habitants déjà équipés.
+```
+
+### P120 — Publier ses traces sur le site
+
+Choix ouvert · Une découverte personnelle n’est pas automatiquement publique.
+
+```text
+Si un futur site montre l’histoire du serveur, quelles traces de ton personnage devrait-il publier par défaut ?
+
+A — Seulement ma présence et les événements collectifs auxquels je participe.
+B — Mes accomplissements publics, sans mes trajets ni mes stocks.
+C — Ce que je sélectionne moi-même pour ma fiche et mes projets.
+D — Rien par défaut ; je choisis d’activer ma présence sur le site.
+```
+
+## Après chaque discussion : noter une décision provisoire
+
+Ce modèle sert à rédiger un retour, après lecture des réponses. Remplacer tous les champs avant publication. Ne pas déduire l’accord des absents et ne pas annoncer une implémentation avant livraison.
+
+```text
+Retour sur « [sujet] »
+
+Vous avez surtout demandé : [attente et raisons exprimées].
+Le point de désaccord à garder : [préoccupation précise].
+
+Pour le prochain essai, je retiens : [règle provisoire concrète].
+On vérifiera : [situation de jeu et résultat observable].
+Ce qui reste ouvert : [une limite ou décision suivante].
+
+C’est le choix pour cet essai ; je vous montrerai ce qui a réellement été réalisé.
+```
diff --git a/docs/consultation-discord-premiere-serie.md b/docs/consultation-discord-premiere-serie.md
new file mode 100644
index 0000000..2b3b757
--- /dev/null
+++ b/docs/consultation-discord-premiere-serie.md
@@ -0,0 +1,181 @@
+# Sanctuary — les 12 premiers posts Discord
+
+Extrait préparé le 14 septembre 2026. Copier uniquement les blocs de texte. Publier les deux souvenirs en premier, puis avancer avec un ou deux sujets ouverts à la fois. Les numéros renvoient à la [banque complète de 120 posts](consultation-discord-posts.md).
+
+L’[analyse documentaire](consultation-discord-analyse.md) donne les sources, les décisions encore ouvertes et les points déjà réglés dans la bêta. Aucun message n’a été publié.
+
+## Message d’ouverture
+
+```text
+J’aimerais construire la suite de Sanctuary avec les personnes qui y ont joué.
+
+Je vais poster des questions petit à petit. Certaines parlent de vos souvenirs ; d’autres présentent une mécanique envisagée pour la suite. Quand quelque chose existe déjà dans la bêta, je le préciserai.
+
+Vous pouvez répondre avec une lettre, proposer autre chose ou raconter une situation vécue. « Je ne sais pas » et « ça ne m’intéresse pas » sont aussi utiles. Si vous parlez d’une ancienne partie, indiquez laquelle si vous vous en souvenez.
+
+Ce qui m’aide le plus : votre préférence et ce qu’elle changerait dans votre façon de jouer. Les votes me guideront ; les désaccords et les essais en jeu compteront aussi.
+```
+
+### P01 — Le souvenir à retrouver
+
+Souvenir libre · À publier avant les sondages de fonctionnalités.
+
+```text
+Quel moment vécu sur Sanctuary aimerais-tu pouvoir revivre dans la nouvelle version ?
+
+Raconte une scène précise : ce que tu faisais, avec qui, et ce qui l’a rendue mémorable. Ça peut être une construction, une découverte, un accident, une rencontre ou un truc complètement idiot.
+```
+
+### P02 — Ce qui te faisait décrocher
+
+Souvenir libre.
+
+```text
+Qu’est-ce qui t’a déjà fait perdre l’envie de te connecter à Sanctuary ?
+
+Une situation concrète m’aidera plus qu’une liste de fonctionnalités manquantes. Ça peut venir du jeu, du rythme du serveur ou de la vie du groupe. Pas besoin de nommer quelqu’un.
+```
+
+### P03 — La priorité à protéger
+
+Question déduite · Arbitrage entre les piliers de la vision.
+
+```text
+Si je dois protéger une seule qualité de Sanctuary dans les prochains développements, laquelle doit passer en premier ?
+
+A — L’exploration et les découvertes surprenantes.
+B — La construction et les grands projets communs.
+C — La progression, la production et les échanges.
+D — La vie du groupe, les rencontres et les moments absurdes.
+```
+
+### P06 — Revenir après une absence
+
+Question déduite · À traiter avant les coûts collectifs et le calendrier.
+
+```text
+Si tu reviens après deux semaines d’absence et que les autres ont beaucoup avancé, qu’est-ce qui t’aiderait le plus à reprendre ?
+
+A — Profiter immédiatement des infrastructures et des accès ouverts par le serveur.
+B — Avoir un parcours personnel de rattrapage plus court.
+C — Retrouver des activités utiles au groupe dès mon niveau actuel.
+D — Rejouer les grandes étapes manquées avec d’autres volontaires.
+```
+
+### P08 — Le départ fragile
+
+Retour sur l’existant · Ne propose pas de rétablir l’ancien minage ralenti.
+
+```text
+Dans la bêta, on commence avec 3 cœurs, une petite réserve de faim et d’air, et 9 cases d’inventaire. Le minage de départ garde la vitesse Minecraft normale ; les premiers gestes donnent un peu d’XP.
+
+Quel ressenti voudrais-tu pendant la première heure ?
+
+A — Une vraie fragilité qui rend l’entraide nécessaire.
+B — Un départ exigeant, avec un abri et les premiers progrès rapidement accessibles.
+C — Une mise en route assez confortable ; le danger vient ensuite de l’exploration.
+D — Il me faut un essai de cette bêta pour me prononcer.
+```
+
+### P12 — Fonder une faction avant le prestige
+
+Retour sur une règle livrée · Une évolution demanderait une décision explicite.
+
+```text
+Aujourd’hui, fonder une faction demande une charge gagnée en terminant un cycle. La faction commence à deux places ; ses invités n’ont pas besoin de prestige.
+
+À quel moment voudrais-tu pouvoir créer ton groupe officiel ?
+
+A — Dès l’arrivée ; le prestige servirait ensuite à le développer.
+B — Après un petit objectif collectif accessible en début de partie.
+C — Après le premier cycle, comme dans la bêta actuelle.
+D — Les groupes informels me suffisent ; la faction peut rester tardive.
+```
+
+### P19 — Ce que doit apporter une nouvelle île
+
+Choix ouvert · Distinction essentielle avec les quatre expéditions déjà ouvertes.
+
+```text
+Les nouveaux mondes ont déjà quatre îles d’expédition accessibles : boréale au nord, tropicale à l’est, aride au sud et océanique à l’ouest. Leur accès ne demande pas de déblocage collectif.
+
+Qu’est-ce qui justifierait le mieux de financer ensuite un nouveau continent ?
+
+A — Des ressources impossibles à trouver ailleurs.
+B — Un lieu de mystère, un donjon ou une nouvelle étape de l’histoire.
+C — Un nouveau territoire pour construire et s’installer.
+D — Un environnement qui permet de nouvelles cultures et productions.
+```
+
+### P21 — Qui choisit l’expansion
+
+Choix ouvert · Gouvernance à fixer avant les dépenses collectives.
+
+```text
+Une future installation permettra aux joueurs d’ouvrir un continent. Qui devrait décider du projet à financer ?
+
+A — L’ensemble du serveur, par un vote ouvert.
+B — Les personnes qui contribuent à ce projet.
+C — Chaque faction, pour ses propres expéditions.
+D — N’importe quel groupe qui construit l’installation et réunit les moyens.
+```
+
+### P31 — Une maison sans faction
+
+Choix ouvert · Demeure n’est actuellement pas une protection.
+
+```text
+La bêta sait montrer les lieux habités avec Demeure, mais ne protège pas encore les constructions par des claims.
+
+Comment voudrais-tu protéger ta première maison ?
+
+A — Un petit terrain personnel gratuit dès l’arrivée.
+B — Un terrain personnel gagné après quelques activités de début de partie.
+C — Une protection fournie par une faction que je rejoins.
+D — Des règles de voisinage et une intervention humaine en cas de problème.
+```
+
+### P37 — La place du PvP
+
+Choix ouvert · À trancher avant armes, familiers et braquages.
+
+```text
+Quelle place voudrais-tu donner aux combats entre joueurs dans Sanctuary ?
+
+A — Des duels acceptés par les deux personnes.
+B — Des arènes ou événements annoncés.
+C — Des conflits de factions déclarés, dans des zones et périodes précises.
+D — Un danger possible dans les territoires sauvages, avec des refuges protégés.
+```
+
+### P71 — Obtenir son premier familier
+
+Choix explicitement ouvert · Les pouvoirs sont livrés, leur acquisition normale en survie reste à concevoir.
+
+```text
+La bêta possède déjà les pouvoirs de 88 familiers, mais leur obtention en survie reste à construire.
+
+Comment voudrais-tu obtenir ton premier œuf de familier ?
+
+A — En prenant soin d’un animal ou en réussissant une interaction avec son espèce.
+B — En terminant une petite quête dont la récompense est connue.
+C — En découvrant un lieu ou un coffre, avec une part de surprise.
+D — En choisissant un compagnon de départ, puis en recherchant les suivants.
+```
+
+### P85 — Une réussite collective
+
+Choix ouvert · Ne révèle pas le dénouement narratif.
+
+```text
+Quel accomplissement te ferait dire « on a vraiment réussi cette aventure Sanctuary ensemble » ?
+
+A — Avoir transformé les îles en un monde habité et relié.
+B — Avoir résolu le grand mystère de ce monde.
+C — Avoir rendu possibles des constructions et techniques extraordinaires.
+D — Avoir créé une communauté dont les projets continuent sans objectif final imposé.
+```
+
+## Après ces premiers retours
+
+Choisir la prochaine série selon les réponses : progression, voisinage, expansion, familiers ou récit. Le [modèle de retour sur une discussion](consultation-discord-posts.md) permet ensuite d’annoncer une règle provisoire et l’essai qui servira à la vérifier.
diff --git a/docs/corrections-beta036.md b/docs/corrections-beta036.md
new file mode 100644
index 0000000..11c4616
--- /dev/null
+++ b/docs/corrections-beta036.md
@@ -0,0 +1,121 @@
+# HUD-036 — notifications choisies, vol porté et discrétion
+
+Branche `codex/hud-gliding-stealth-beta036`, base beta.035.
+Cible inchangée : Minecraft 26.3-pre-2, Java 25.
+
+## Contrat jouable
+
+Les notifications démarrent **en haut à droite**. Options → Notifications propose
+les neuf ancrages : quatre coins, quatre milieux de bord et centre. La préférence
+locale existante est conservée ; les fichiers antérieurs sans position prennent
+le nouveau défaut. Aucun réglage de quête n’est ajouté.
+
+Les colonnes F3 et les annonces natives réservent leur surface réelle, mesurée
+sur la même image, y compris avec une échelle F3 différente. Le fil reste dans
+la colonne choisie, se place dans une zone libre et réduit le nombre de lignes
+si nécessaire. Barres de boss, sous-titres et ensemble vie/XP/hotbar réservent aussi
+leur place. Quand aucun emplacement lisible ne reste, les messages attendent ;
+le profiler circulaire et la visualisation de chunks suspendent le fil. F1, les
+menus et les écrans de chargement le masquent également.
+
+Un advancement commencé ne reste **plus suivi automatiquement à vie**. Ses
+nouveaux progrès s’effacent comme les découvertes (durée réglable 3–15 secondes,
+5 par défaut, plus deux secondes pour les progrès). Un état inchangé ne relance
+pas le délai. La mort et la réapparition effacent le fil temporaire sans rejouer
+les anciens critères ni les découvertes.
+
+Dans **Advancements**, cliquer sur une icône visible et incomplète l’épingle ;
+cliquer à nouveau retire le suivi. Un `+` doré marque l’icône et une indication
+FR/EN explique le geste. Glisser la carte de l’arbre ne bascule jamais le suivi.
+Jusqu’à **trois objectifs** sont choisis ; un quatrième remplace le plus ancien.
+Les critères reçus du serveur alimentent les jauges, même avant le premier
+critère réussi. Une réussite complète retire automatiquement l’épingle.
+Le choix survit à la mort et à la reconnexion, séparément pour chaque compte
+et monde local ou adresse de serveur. Les préférences restent uniquement dans
+`config/sanctuary-notifications.json` ; les clés de contexte sont hachées.
+L’option Objectifs épinglés masque ces suivis sans oublier la sélection.
+Les progrès récents non épinglés gardent leur bref affichage temporaire.
+
+## Déplacements
+
+Porter un animal volant sur sa tête permet de **planer automatiquement pendant
+la descente** : vitesse verticale plafonnée à 0,12 bloc/tick, sans impulsion vers
+le haut. Les poules comptent parmi les oiseaux qui amortissent la chute. Les
+familiers utilisent l’espèce de leur œuf. Le compagnon doit réellement être porté ;
+un animal proche ou simplement suiveur ne suffit pas. Le ralentissement dû au
+portage reste appliqué. Le vol créatif, les élytres, l’eau et la lave conservent
+leur fonctionnement. Une pile de joueurs ne transmet pas ce pouvoir à son socle.
+
+La distance de chute accumulée est effacée pendant le plané. Relâcher l’animal
+rétablit immédiatement la gravité et l’accumulation d’une nouvelle chute : aucune
+potion persistante ni immunité générale aux dégâts.
+
+Allongé ou au repos **au sol**, le joueur réduit de moitié sa visibilité dans
+les tests natifs de détection des mobs. L’effet se combine avec les règles
+Minecraft déjà présentes. À proximité, les mobs peuvent encore détecter et
+frapper. Le plongeon, la nage et le vol ne procurent pas ce bonus. Les cibles
+existantes ne sont pas effacées de force. La petite silhouette native demeure,
+y compris son intérêt face aux flèches ; aucun changement de visée des squelettes.
+
+## Compatibilité
+
+Aucun changement de schéma des sauvegardes serveur, des identifiants de jeu, des
+récompenses, des recettes, des générations ou des expansions. Les deux préférences
+client nouvelles et le suivi restent hors des mondes. Aucune installation de jeu
+personnelle n’est mise à jour par ce ticket.
+
+## Suite, non implémentée ici
+
+- Quêtes en haut à gauche : après les blocs rubis/saphir et leur contrat serveur.
+- Temps réel : horloge et fuseau système automatiques en solo ; fuseau choisi
+ dans la configuration serveur en multijoueur. Une région solaire approximative
+ choisie séparément doit déterminer latitude/saison/date, lever et coucher du
+ soleil. Le fuseau seul ne suffit pas à déterminer la géographie. Ne pas demander
+ d’adresse personnelle, ne pas collecter la géolocalisation ni publier une position
+ précise. Définir avant implémentation le profil solaire de secours en solo,
+ les changements d’heure, le passage de minuit et la migration du temps des mondes.
+
+## Vérifications
+
+`./gradlew check build assemblePack assembleTestPack` réussi avec les suites
+ciblées `corrections,notifications,carry,movement,progression,recipes,collections` :
+**36 tests serveur**, dont la matrice des 126 advancements de collections.
+Le contrôle de géométrie couvre 180 combinaisons et les collisions F3/toasts/boss/HUD.
+
+Deux parcours client natifs réussissent sur des mondes plats de développement :
+- « A Balanced Diet » après rotten flesh expire ; clic/re-clic/glissement de
+ l’arbre, épingle conservée après respawn et retirée après réussite.
+- 36 combinaisons GUI/position avec F3, son échelle indépendante, boss, hotbar,
+ réglages enregistrés et écrans FR/EN inspectés visuellement.
+- Vraie descente du joueur avec un perroquet porté et atterrissage sans dégâts.
+- Sorties des collections Archery, Woodworking, Boats and Rafts et Books and
+ Storage trouvées dans le filtre JEI ; ouverture des vraies recettes arc,
+ établi, bateau et livre sans possession préalable, avec inconnus préservés.
+
+La persistance du choix est contrôlée dans le fichier client ; la mort/respawn
+est exercée en jeu. La conservation à la reconnexion utilise ce même contexte
+chargé au retour, sans parcours supplémentaire de redémarrage pour ce ticket.
+La discrétion est contrôlée par les tests de détection natifs côté serveur ;
+l’équilibre face à plusieurs mobs en partie reste à apprécier.
+
+Exports locaux vérifiés : [normal](../build/Sanctuary-beta.036.mrpack) et
+[monde plat rapide](../build/Sanctuary-Test-beta.036.mrpack). Les 1 335 classes du
+mod, les trois classes du module de test, les JAR imbriqués et les ressources
+correspondent au build. Les collections et générations restent identiques ;
+les packs beta.034/beta.035 sont inchangés.
+Reçu : `build/corrections036-artifact.json` ; captures :
+`build/corrections036-evidence/`. Logs : `build/corrections036-check-final.log`,
+`build/corrections036-client-options-final.log`, `build/blueprint036-client.log`.
+
+## Retour JEI intégré
+
+Une collection pouvait enseigner ses recettes tout en laissant leurs sorties
+masquées dans le catalogue d’objets de JEI. La recherche de l’arc, d’un bateau ou
+d’un livre restait donc impossible tant que le joueur ne l’avait pas possédé.
+
+Les sorties des recettes connues deviennent désormais consultables dans JEI.
+Leur index se reconstruit sur changement du carnet ou du runtime, pas à chaque
+image. Cela n’ajoute aucune possession au carnet Discovery, ne révèle pas les
+matières premières inconnues et ne débloque aucune autre recette. L’aptitude
+Catalogue reste nécessaire. Les définitions de collections côté serveur,
+récompenses et format de carnet restent inchangés ; pas de changement du fork JEI.
diff --git a/docs/cosmologie.md b/docs/cosmologie.md
index a02f1af..e3221dc 100644
--- a/docs/cosmologie.md
+++ b/docs/cosmologie.md
@@ -10,7 +10,7 @@ pour des mécaniques futures, avec les limites décrites dans [son contrat](bloc
## Minecraft comme archéologie d’un autre Minecraft
-Steve, Ari, Sunny, Kai, Zuri, Alex, Mefe, Makena et Noor sont les neuf personnages
+Steve, Ari, Sunny, Kai, Zuri, Alex, Efe, Makena et Noor sont les neuf personnages
disparus de la partie. Ils ont découvert quelque chose de révolutionnaire et ont
continué parce que ça marchait. Jusqu’au moment où ça n’a plus marché.
@@ -26,11 +26,61 @@ de raconter son interruption.
> Chaque ruine de sanctuary doit être une trace d’une ancienne solution pas
> seulement la trace d’un problème.
-La découverte précise, la succession des expériences et les rôles des neuf
-personnages restent à écrire. Aucun métier, aucune responsabilité individuelle
-ni cause définitive de leur disparition ne leur sont attribués ici. On conserve
-en particulier **Mefe**, tel que nommé par l’auteur. Il n’est pas décidé que
-chacun possède un bâtiment, une machine, une gemme ou une quête correspondante.
+Le dialogue fourni par le créateur pour la bêta.001 précise leurs rôles.
+Il emploie **Efe**, retenu ici à la place du « Mefe » de la première note.
+Kai et Efe sont jumeaux ; Steve et ses amis sont des héros trans. Cette dimension
+doit rester présente avec naturel, dans leurs relations et leurs actes.
+
+| Personnage | Rôle ou lien retenu dans les notes |
+| --- | --- |
+| Makena | Cuisine |
+| Sunny | Redstone |
+| Kai et Efe | Construction |
+| Zuri | « Exploitation », dont le sens et les limites restent à préciser |
+| Ari | Villageois |
+| Noor | Aventure |
+| Alex | Lien avec Steve, à développer |
+| Steve | Sacrifice et protection des joueurs |
+
+Dans cette fiction, Steve a contenu Notch en s’enfermant avec lui. Le
+**Galactium**, vide qui structure les mécaniques de Minecraft, est envisagé
+comme le lieu de son isolement. Alpha, Backrooms et un double laissé derrière
+lui participent aux pistes de dissimulation. Les compagnons disparus pourraient
+subsister comme apparitions et interlocuteurs de marchés particuliers.
+
+Des pages de journaux propres à chaque personnage permettraient de rapprocher
+des récits incomplets : leur lecture collective ferait comprendre l’histoire.
+Le retour de Steve, la transformation ou la défaite de Notch et les conséquences
+pour les joueurs restent des questions ouvertes. Les discussions autour du
+créatif ou d’un nouveau cycle ne fixent pas encore une fin de partie.
+Ces éléments concernent les personnages de la fiction Sanctuary.
+
+Les [quatre anciennes expéditions](expeditions-beta001.md) reprennent pour leurs
+noms Noor, Makena, Kai et Efe, Ari. Cela n’attribue pas automatiquement à chacun
+une machine, une gemme ou une quête. Les journaux à collecter, apparitions, boss
+et pouvoirs décrits ici ne sont pas implémentés dans ce ticket.
+
+## Les doubles de Steve — conception du 15 septembre 2026
+
+Les [trois doubles décrits dans ANO-01](steve-anomalies.md) sont des anomalies
+de Sanctuary. **Ils ne sont pas encore implémentés.** Leur origine détaillée
+reste à découvrir au fil de l'histoire.
+
+- **Herobrine** se cache dans les Backrooms. Il peut frapper, poser des blocs,
+ enfermer un joueur, avancer ou attendre derrière lui qu'il se retourne.
+ Au moment où le joueur le voit, il disparaît.
+- **Le mineur fantôme** creuse des galeries dans l'Overworld **et** les Backrooms,
+ surtout dans les chunks les moins marqués par Demeure. De face comme de dos,
+ on voit l'arrière de sa tête.
+- **Le Steve bugué** est extrêmement rare, en T-pose avec des UV mal placés.
+ Une fois trouvé, il reste ; on peut l'embarquer dans un bateau et le déplacer.
+ Cette présence durable constitue une preuve que Steve existe. Ses lieux
+ d'apparition restent à définir.
+
+Herobrine laisse des effets puis disparaît ; le mineur laisse des galeries ;
+le Steve bugué peut être conservé et montré aux autres. Ces comportements
+n'attribuent pas d'avance à l'un des doubles l'identité du Steve originel.
+ANO-01 distingue les décisions du créateur des modalités techniques proposées.
## Deux devenirs de la même technologie
@@ -243,6 +293,19 @@ La même ligne relie le passé et la partie actuelle :
## Accès, retour et récupération : règles encore ouvertes
+**Décisions du 15 septembre 2026 :** le [ticket BR-01](backrooms-implementation.md)
+fixe désormais le parcours des salles personnelles : sommeil complet depuis son
+lit de l'Overworld, arrivée dans un Indoor de protection intégré aux Backrooms,
+passage caché par Steve derrière un tableau et une porte ouverte. Les Indoors
+interconnectés forment le dysfonctionnement où le ballast du monde est redirigé.
+Chaque salle est liée à un joueur et peut être découverte et visitée à pied par
+les autres. Tout lit utilisable dans les Backrooms ramène chaque joueur à son
+propre lit d'origine dans l'Overworld. Les lieux se cartographient manuellement,
+sans coordonnées affichées. **Implémentation prévue seulement après le suivi
+du ballast.** Les règles d'accès ci-dessous sont le cadrage antérieur ; BR-01
+les précise pour ce parcours. Récupération d'objets perdus, économie et Indoors
+généraux conservent leurs contrats à établir.
+
La vision antérieure envisage le lit pour entrer dans les Backrooms, des sources
de lumière pour les explorer et des coffres recueillant des objets perdus dans
le vide ou brûlés. Elle prévoit aussi des accès d’Indoors liés à la mailbox de
diff --git a/docs/cycle-factions-beta011.md b/docs/cycle-factions-beta011.md
new file mode 100644
index 0000000..3ebedaf
--- /dev/null
+++ b/docs/cycle-factions-beta011.md
@@ -0,0 +1,172 @@
+# CYCLE-01 · PROG-03 · FACTION-01 · NGP-01 — beta.011
+
+Contrat commun établi avant l'évolution des sauvegardes. Branche :
+`codex/factions-new-game-plus`. Minecraft reste **26.3-pre-2**.
+
+> Historique beta.011. Depuis [beta.038](graves-food-beta038.md), la fondation
+> coûte 10 niveaux et crée une seule place, sans prestige ; les charges servent
+> aux places supplémentaires. Les anciennes fondations restent inchangées.
+
+## Règles de cette livraison
+
+Un cycle est personnel. Le premier porte le numéro 1. Seul un habitant vivant
+ayant les **six compétences au maximum** peut confirmer son New Game+.
+Les aptitudes ne conditionnent pas ce passage. Une mort ordinaire ne termine
+jamais un cycle et ne distribue aucune récompense de prestige.
+
+Valeurs initiales de `config/sanctuary/cycle-factions.json` : chaque passage
+accorde **1 prestige et 1 charge personnelle**. Créer une faction coûte
+**1 charge**, donne **2 places**, fondateur compris. Chaque membre peut payer
+**1 charge pour ajouter 1 place**. Le plafond de cette première version est
+100 membres. Le prestige compte les cycles achevés ; ce n'est pas une monnaie.
+Les charges inutilisées appartiennent à l'habitant. Les places financées
+appartiennent à la faction et restent après un départ ou un New Game+.
+Aucun remboursement lors d'une dissolution. Les règles monétaires sont
+configurables au redémarrage, mais ne recalculent jamais les anciens événements.
+
+Une personne appartient à une seule faction à la fois. Le propriétaire invite,
+révoque les invitations, exclut, transmet la responsabilité et dissout.
+Les invitations durent 7 jours réels ; elles ne réservent pas de place.
+L'acceptation revalide place, invitation et absence d'autre faction. Les membres
+peuvent partir ; le propriétaire doit transmettre ou dissoudre. Son absence
+ne transfère pas automatiquement la propriété. Le changement de propriétaire
+annule les invitations en cours. La couleur de faction est copiée de celle du fondateur à sa création ;
+la couleur personnelle reste indépendante.
+Les factions n'accordent encore ni claims ni protection de blocs.
+
+## Matrice du New Game+
+
+| Donnée | Traitement |
+| --- | --- |
+| Six compétences | Rang zéro : 3 cœurs, 3 nourritures, 3 bulles, gestes individuels, 9 cases |
+| XP non dépensée | Remise à zéro par défaut ; option serveur `keepXp` |
+| Identité, UUID, pseudo enregistré, bio, couleur | Conservés |
+| Aptitudes et preuves de leurs achats | Conservées |
+| Achats du cycle terminé | Archivés intégralement, coûts et dates compris |
+| Objets, équipement, main secondaire, coffre de l'Ender | Conservés, sans déplacement |
+| Rangées d'inventaire redevenues verrouillées | Occupées et visibles en récupération : retrait autorisé, nouveau dépôt interdit |
+| Recettes, découvertes, Atlas personnel, statistiques et advancements | Conservés |
+| Demeure, faction, rôle, invitations encore valides | Conservés |
+| Prestige et charges personnelles | Conservés, puis récompense du cycle ajoutée une seule fois |
+| Monde, constructions, coordonnées du joueur | Conservés ; aucune régénération ou téléportation |
+| Santé, faim, air courants | Bornés aux nouvelles capacités, sans soin gratuit |
+| Argent, coffre-fort, favoris, mémoire Web | Systèmes futurs ; ce ticket ne crée ni ne migre de données fictives |
+
+Exemple : Alice termine son premier cycle, obtient prestige 1 / charge 1 et
+fonde une faction de deux places. Bob la rejoint sans payer. Son propre premier
+New Game+ lui donne une charge : il l'investit et la faction passe à trois
+places. Au deuxième New Game+ d'Alice, son prestige devient 2 ; sa nouvelle
+charge reste personnelle et son rôle de propriétaire demeure. Ses 54 cases
+pleines ne sont ni effacées ni jetées : les 45 cases désormais verrouillées
+restent retirables jusqu'à leur prochain déblocage.
+
+## Contrat de migration et reprise
+
+L'adoption des progressions beta.003–010 est additive : schémas 1–5 vers
+**schéma 6**, avec `cycle: 0` (zéro cycle terminé). Aucun achat n'est perdu.
+La révision réseau vaut `cycle * 64 + nombre d'achats actifs`, empêchant qu'un
+ancien clic d'achat soit accepté après remise à zéro. Les identifiants existants
+restent stables. La capacité de configuration `sanctuary:cycle_factions_v1`
+exige la mise à jour des deux côtés avant de rejoindre.
+
+Le nouveau fichier `data/sanctuary-cycle-factions.json` est un journal de monde
+versionné (schéma 1), lié à la graine, contenant UUID de monde et événements
+datés identifiables. Il reconstruit soldes, factions et fins de cycle. Chaque
+mutation est validée sur une copie, écrite par remplacement atomique puis
+publiée en mémoire. Une erreur ou un fichier modifié extérieurement bloque les
+mutations ; aucune remise à zéro silencieuse.
+
+Un New Game+ écrit **d'abord** sa fin de cycle et sa récompense dans ce journal,
+puis applique la nouvelle progression au joueur. Au prochain chargement, si
+le journal a un cycle d'avance, sa décision est rejouée sur le joueur (avec
+sa politique XP enregistrée). Si les numéros concordent, rien n'est réinitialisé.
+Un joueur en avance sur le journal, ou en retard de plus d’un cycle, est
+refusé, ce qui détecte une restauration partielle incompatible. Le délai entre clics utilise une horloge monotone réelle (250 ms), indépendante
+des ticks suspendus dans le menu pause solo. Lire une page ne consomme pas
+le délai réservé à une mutation. Le solde d’XP est envoyé explicitement avec
+la progression pour rester exact pendant cette pause. Les tentatives répétées d'un même cycle ne donnent
+pas de charge supplémentaire. La sauvegarde vanilla conserve les objets et
+la progression dans le même fichier joueur, comme en beta.010. Les acquisitions
+d’objets entre deux sauvegardes restent soumises à l’autosauvegarde Minecraft :
+ce journal ne remplace pas la sauvegarde générale du monde.
+
+Faire une copie complète du monde arrêté avant adoption ; restaurer ensemble
+le journal, les fichiers des joueurs et les autres données du monde. Le retour
+aux anciens binaires après adoption n'est pas supporté. Ce contrat autorise
+l'évolution du code de lecture/écriture ; aucun monde personnel n'est modifié
+par les travaux de développement.
+
+## Livraison et vérifications
+
+Les contrôles du contrat pur passent : reprise beta.010, cinq/six compétences,
+conservation des aptitudes, deux cycles successifs, double requête, dépenses,
+propriétaire absent, invitations concurrentes, expiration exacte à sept jours,
+transmission, dissolution, relecture du journal et refus d'une écriture après
+corruption ou modification externe.
+
+Les sept scénarios natifs propres à beta.011 passent (8/8 avec l'admission
+Fabric), puis la suite progression/carte/Demeure/minage/construction/inventaire/
+cycles passe à **45/45**. Ils couvrent notamment :
+
+- Inventaire réellement rempli de 54 piles avant passage ; récupération dans
+ le menu natif, équipement et coffre de l'Ender conservés.
+- Retour à 3/3/3 sans soin offert, XP zéro, nouvel achat possible et ancien
+ paquet d'achat refusé grâce à la révision de cycle.
+- Identité, statistiques, observations Atlas et influence Demeure conservées.
+- Décision de fin écrite puis relecture d'un ancien fichier joueur ; après une
+ seconde sauvegarde, l'XP et les achats du nouveau cycle ne sont plus remis
+ à zéro. L'option de conservation XP est prise dans la décision enregistrée.
+- Mort ordinaire après New Game+ : même progression, prestige et faction.
+- Création, invitations par pseudo/UUID, contribution d'un membre, changement
+ de responsable, conteneurs ouverts et mode spectateur refusés.
+
+Le test de reprise a d'abord détecté un problème de fixture : chaque joueur
+simulé possède un nouveau profil d'authentification, que Minecraft rétablit au
+chargement. La fixture de reconnexion utilise maintenant le même UUID.
+
+Le parcours client natif `Cycle011ClientChecks` est compilé : ouvert depuis
+le menu pause, cycle verrouillé, aperçu/annulation, passage confirmé, création
+et journal, à deux échelles GUI. **Il n'a pas été exécuté graphiquement : le
+Mac est verrouillé. Aucun rendu beta.011 n'est déclaré vérifié.**
+
+La commande de livraison est :
+
+```sh
+JAVA_HOME=/Users/koka/Library/Java/JavaVirtualMachines/temurin-25.jdk/Contents/Home \
+./gradlew check build assemblePack \
+ -PsanctuaryFocusedTests=progression,map,demeure,mining,building,inventory,cycle
+```
+
+Les logs restent ignorés dans `build/cycle011-delivery-final.log`,
+`build/cycle011-smoke.log` et `build/cycle011-client-final-compile.log`.
+La commande finale réussit en **3 min 55 s**, avec les 45 tests natifs et
+les contrôles purs. Le reçu `build/cycle011-artifact.json` confirme l'intégrité
+ZIP, l'identité des JAR compilés et embarqués, les versions beta.011, les
+87 libellés FR/EN des cycles, les dépendances exactes et les assets conservés.
+Les exports beta.003–010 gardent leurs empreintes précédentes.
+
+Artefact local : `build/Sanctuary-beta.011.mrpack` — 2 479 614 octets.
+SHA-256 : `dae0d1fbd8f410fd73b2d3053ef0f17539e24639c3d338ce84dbfca6e4f43ec0`.
+
+L'export local ne publie pas le canal packwiz et ne modifie aucune instance Prism.
+
+## Essai en jeu
+
+Installer la même beta.011 côté client et serveur. Dans un monde de test,
+`/experience add @s 1000 levels` permet à un opérateur de tester les achats
+sans attendre. Acheter les quarante rangs des six compétences dans le Blocodex,
+puis ouvrir **Progression → Cycle**. Acheter les aptitudes reste facultatif.
+Remplir aussi les dernières rangées avant de confirmer pour vérifier leur
+récupération. Un second joueur peut rejoindre la faction via son invitation
+sans avoir lui-même fait de New Game+.
+
+Les invitations et les actions de faction se trouvent dans **Monde → Factions**,
+l'historique dans **Monde → Journal des cycles et factions**. Les montants
+s'éditent dans `config/sanctuary/cycle-factions.json`, serveur arrêté : `reward`,
+`creationCost`, `initialCapacity`, `expansionCost`, `maxMembers`, `keepXp`.
+Le prestige reste toujours le nombre de cycles terminés. Le journal est borné
+à 100 000 événements / 16 Mio ; il refuse d'effacer les anciennes archives
+pour faire de la place. Une future rotation nécessitera son propre contrat.
+
+Le journal fournit une ontologie locale (`sanctuary:cycle_completed`, `sanctuary:faction_*`) ; aucune
+connexion au site ni distribution de droits opérateur n'est incluse.
diff --git a/docs/daily-weather-beta057.md b/docs/daily-weather-beta057.md
new file mode 100644
index 0000000..1f11683
--- /dev/null
+++ b/docs/daily-weather-beta057.md
@@ -0,0 +1,119 @@
+# beta.057 — météo quotidienne Sanctuary
+
+Branche `codex/daily-weather-beta057`. Contrat avant implémentation : une météo
+commune par date civile du monde, en réutilisant le fuseau du système Real Time.
+La météo est indépendante du mode du soleil : `vanilla` rend le cycle à
+Minecraft ; `realtime` conserve le profil choisi pour la journée. Les profils
+pluie et orage sont continus ; le profil averses alterne pluie et pauses sèches.
+
+Le tirage local est déterministe selon la graine du monde et la date, sans
+réseau ni géolocalisation. Les proportions de départ configurables sont :
+65 % beau temps, 15 % ciel gris, 5 % brouillard, 10 % averses, 3 % pluie
+continue et 2 % orage continu. Ce sont des probabilités par journée.
+Les proportions se règlent dans `config/sanctuary/weather.json` ; la prévision
+déjà enregistrée pour aujourd'hui ne change pas lors d'un rechargement.
+Les transitions d'intensité, sons, neige par biome et éclairs restent natifs.
+Le profil averses place une pluie de 35 à 75 minutes dans chaque tranche de
+3 heures réelles, avec des pauses sèches. La position de chaque passage est
+calculée depuis la graine/date/tranche ; une reconnexion ne la déplace pas.
+Les tranches comptent le temps réellement écoulé depuis le minuit local,
+y compris lors des journées de 23 ou 25 heures au changement d'heure.
+
+Le ciel gris et les averses atténuent les couleurs du ciel et la lumière solaire
+sans transformer artificiellement l'état sec en pluie. Le brouillard utilise
+les distances natives (fin atmosphérique 80 blocs, ciel 120, nuages 96) et se
+compose avec le réglage Vanilla Light. La transition des ambiances prend environ
+10 secondes de jeu. Eau, lave, cécité et obscurité gardent leurs attributs natifs.
+La présentation se branche sur les couches d'environnement de Minecraft :
+pas de nouvelle passe shader, texture de bruit, reconstruction des chunks ni
+rechargement des shaders. Les moteurs de shaders externes restent à vérifier.
+
+Le serveur transmet le profil aux clients à la connexion et lors de ses
+changements, y compris le retour à Vanilla. La pluie et l'orage utilisent leurs
+paquets natifs. Le calcul météo se fait une fois par seconde ; l'écriture du
+journal n'a lieu qu'au changement de date ou de mode.
+
+## Contrat de sauvegarde et de retour
+
+Un fichier additif `data/sanctuary-weather.json`, schéma 1, conserve le mode,
+la date civile et sa prévision. Il est écrit atomiquement avant toute action
+météorologique. Aucun ancien schéma ni paramètre de génération n'est modifié.
+Un document invalide est conservé ; le chargement refuse la prise de contrôle.
+
+Le mode quotidien est activé par défaut seulement dans les mondes Sanctuary
+et Sanctuary Test. Un monde vanilla ordinaire garde son comportement natif.
+Les jours manqués ne sont pas simulés. La reconnexion et le redémarrage gardent
+le profil du jour ; un nouveau profil est choisi à minuit dans le fuseau du
+serveur intégré ou dédié, et non à chaque journée Minecraft.
+
+Le cycle aléatoire et l'effacement de la pluie par le sommeil sont suspendus
+uniquement quand cette météo contrôle le monde. Les règles de jeu sauvegardées
+ne sont pas modifiées. Le retour à Vanilla reprend les durées natives depuis
+l'état météo courant, sans conserver une durée de 24 heures en ticks.
+Sans le mod, Minecraft peut reprendre ses durées usuelles. Les autres systèmes
+de simulation et les dimensions sans météo restent natifs.
+
+## Commandes et extension future
+
+Configuration serveur `config/sanctuary/weather.json` :
+
+```json
+{
+ "enabled": true,
+ "overcastPercent": 15,
+ "fogPercent": 5,
+ "showersPercent": 10,
+ "rainPercent": 3,
+ "thunderPercent": 2
+}
+```
+
+Le beau temps représente le reste jusqu'à 100 %. Une somme supérieure à
+100 %, un entier négatif ou fractionnaire et un document invalide sont refusés.
+Les nouveaux poids s'appliquent au prochain jour ; `enabled: false` rend
+immédiatement la météo au moteur après rechargement. Le retour explicite
+à Vanilla dans un monde est conservé même après réactivation globale.
+
+- `/sanctuary weather` : consulter le mode, la prévision et sa date/fuseau.
+- `/sanctuary weather realtime` : activer le mode quotidien (opérateur).
+- `/sanctuary weather vanilla` : reprendre le cycle Minecraft (opérateur).
+- `/sanctuary weather reload` : recharger les proportions/configuration (opérateur).
+
+En Real Time, `/weather` indique comment reprendre le mode Vanilla pour
+changer la météo librement. Les mutations passent par l'autorité serveur.
+La Weather TNT qui force ou arrête la pluie appartient au prochain ticket ;
+aucun nouveau bloc, explosif ni recette n'est ajouté ici.
+
+## Vérifications
+
+Le contrôle `weather057Smoke`, intégré à `check`, passe 222 058 assertions :
+50 000 tirages quotidiens, proportions, bornes, persistance, rechargement,
+refus des données invalides, retours de mode et absence de rattrapage hors ligne.
+Il vérifie aussi les tranches d'averses, leurs pauses sèches, les journées de
+23/25 heures et le 29 février. Les proportions observées sont respectivement
+32 451 / 7 558 / 2 478 / 4 914 / 1 522 / 1 077 jours pour beau temps / gris /
+brouillard / averses / pluie / orage.
+
+Le client intégré `Weather057ClientChecks` vérifie les six profils natifs,
+les paquets de pluie/orage et du nouveau profil, le brouillard réellement
+utilisé par le rendu, l'absence de pluie en gris/brouillard, l'alternance humide
+et sèche pendant une même journée d'averses, et les passages de date.
+Les durées aléatoires sont suspendues sans modifier la règle de jeu ; le reset
+météo utilisé par le sommeil est bloqué en Real Time et disponible en Vanilla.
+Les commandes, leurs permissions, l'indépendance du soleil, les configurations
+invalides, les retours Vanilla et les libellés FR/EN sont vérifiés.
+
+`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
+réussit sur Minecraft 26.3-pre-2 avec Java 25. Les essais natifs utilisent
+`-PsanctuaryClientTests=true -PsanctuaryWeather057ClientTests=true
+-PsanctuaryQuickTests=true`. Aucun serveur dédié ni EULA supplémentaire accepté.
+Les frontières horaires sont simulées avec une horloge injectée dans le monde
+de test ; plusieurs journées d'hébergement réel ne sont pas revendiquées.
+Aucun monde personnel, canal public ou instance de jeu n'est modifié.
+
+Logs : `build/weather057-check.log`, `build/weather057-client.log`.
+Captures : `build/weather057-evidence/`. La confirmation d'intégrité des exports
+est consignée dans `build/weather057-artifact.json`.
+
+[Pack normal](../build/Sanctuary-beta.057.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.057.mrpack).
diff --git a/docs/demeure-beta006.md b/docs/demeure-beta006.md
new file mode 100644
index 0000000..80b79aa
--- /dev/null
+++ b/docs/demeure-beta006.md
@@ -0,0 +1,41 @@
+# Ticket beta.006 — Atlas exploré et premières empreintes Demeure
+
+Résultat livré localement : déplacer la carte par glisser-déposer répété, découvrir un paysage progressivement, afficher une grille de chunks et consulter les empreintes d'habitation. L'utilisateur a choisi les empreintes et la grille ; réservation, factions et protection des chunks restent au prochain ticket.
+
+## Source et périmètre
+
+La référence signalée par l'inventaire 26.2 se trouve dans `../Structures/demeure` (sources ciblant Minecraft 26.1.2, auteur KOKA99CAB, GPL-3.0-or-later). Aucun changement dans ces dépôts historiques. Demeure reste un mod autonome sous `mods/demeure`, embarqué dans Sanctuary et adapté aux API exactes de 26.3-pre-2. Il fournit les empreintes, Sanctuary décide de l'accès à leur représentation dans l'Atlas.
+
+Ce premier port reprend les influences par chunk, la nature initiale, les états neutre/sauvage/habité/partagé/contesté/dominé/abandonné et les traces des déplacements, de l'activité, des blocs réellement posés ou cassés, des récoltes, du sommeil et des morts. La présence animale, villageoise et hostile est agrégée autour des habitants actifs. Nourrissage, naissance, cuisson, propriété animale individuelle et commandes historiques attendent leurs hooks de réussite et leur ticket. Aucun clic infructueux n'est crédité, aucune XP n'est distribuée par Demeure.
+
+## Contrat de migration et de confidentialité
+
+- Aucun changement de génération, de graine, de chunks, d'expansion, de progression ou d'identité d'habitant.
+- L'Atlas passe du schéma JSON 1 au schéma 2 lors de sa prochaine sauvegarde. Les 256 pixels de chaque ancien chunk restent connus. Dans le même tableau de couleurs, 0 signifie désormais inconnu, 1 représente le vide effectivement observé, 4..255 conservent les couleurs Minecraft. Les anciens pixels 0..3 sont convertis en 1. Cette conversion est idempotente et ne peut révéler aucun nouveau chunk. Les lecteurs beta.005 refusent le schéma 2 : retour arrière exige de restaurer une sauvegarde préalable.
+- Les nouveaux relevés découvrent les pixels dans un rayon de 32 blocs avec une bordure tramée, en lisant uniquement les chunks déjà chargés. L'union des observations est conservée dès l'arrivée, même avant l'aptitude Grande carte. Une absence de couleur n'efface jamais une ancienne découverte. Il s'agit d'une révélation cartographique de surface, pas d'un calcul de visibilité optique.
+- Les identifiants et le format des paquets `sanctuary:atlas_request` / `sanctuary:atlas_response` restent stables. Les empreintes utilisent un nouveau paquet séparé et borné. Le client et le serveur doivent utiliser le même pack pour la nouvelle interprétation du pixel inconnu.
+- Demeure crée un fichier distinct `data/demeure/footprints-v1.json`, indexé par dimension et chunk, lié à la graine. Aucun ancien fichier Demeure n'est importé ni modifié. Les premiers scores commencent à zéro lors du portage.
+- Les jours sont des jours UTC réels (horloge serveur), indépendants de `/time`. Une interruption ne simule pas d'activité. La décroissance après 14 jours d'absence est appliquée avant toute nouvelle contribution ; revenir ne ressuscite pas les anciens scores. Un recul d'horloge ne crée ni nouvelle journée ni nouveau budget.
+- Les contributions par sujet et chunk sont plafonnées à 8 par jour réel, conservées avec leur budget et date. Maximum 16 384 chunks, 32 influences par chunk, fichier limité à 64 Mio. Aucun effacement silencieux en cas de limite ou de corruption ; le service refuse d'écraser un fichier corrompu ou modifié extérieurement.
+- Une grille géométrique peut couvrir le brouillard ; aucun état Demeure n'est transmis pour un chunk inconnu. Le survol d'un pixel inconnu ne révèle pas l'empreinte. Une empreinte ne constitue ni propriété ni protection et ne dit pas qu'un chunk est juridiquement réservable.
+- La vue opérateur demeure explicite et vérifiée sur le serveur ; elle n'ajoute aucune découverte personnelle. Changement de rôle, de dimension ou de permission efface les données de la vue précédente.
+
+## Validation effectuée
+
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,map,demeure` : réussi en 4 minutes, **8/8 tests natifs serveur**. Le test Demeure vérifie les placements réussis/refusés, la destruction native, l'aptitude carte, les chunks inconnus, les permissions opérateur et les spectateurs. Un premier lancement avait échoué dans le montage du faux joueur spectateur (connexion absente) ; ce montage a été corrigé puis les huit tests ont été relancés avec succès.
+- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryProgressionClientTests=true` : réussi en 1 min 37 s. Les glissements passent par `MouseHandler.onButton/onMove`, avec les constantes SDL de Minecraft, et non par des appels directs au gestionnaire de la carte. Plusieurs gestes, relâchement hors carte, zoom natif, nouvelle requête, rafraîchissement, deux tailles de fenêtre, activation de la grille, vrais paquets Demeure, inspection opérateur et révocation sont vérifiés.
+- Les tests de contrats vérifient les anciens pixels de vide conservés, la migration idempotente, l'union de visites, les pixels inconnus, les fichiers corrompus, la capacité et l'isolation. Demeure vérifie aussi le budget après rechargement, le recul d'horloge, la décroissance avant retour, les états du territoire et les dimensions distinctes.
+- Les fichiers produits par le client natif contiennent **16 chunks / 2 913 pixels connus** dans l'Atlas schéma 2, et un chunk Demeure schéma 1. La sauvegarde est constatée dans le monde de développement ; aucun monde personnel n'a été ouvert.
+- Captures inspectées dans `build/beta006-preview/`, notamment les interfaces à 640 et 1280 pixels, le brouillard et la carte verrouillée. FR/EN alignés ; aucun changement aux icônes ni aux noms de couleurs approuvés.
+- La sélection native cible ce ticket et la progression. Les deux assertions worldgen générales déjà connues en beta.003/005 ne sont pas revendiquées corrigées. Réservation/protection des claims, factions et port complet des interactions Demeure restent hors de cette livraison.
+
+## Artefact d'essai
+
+[Sanctuary-beta.006.mrpack](../build/Sanctuary-beta.006.mrpack), **2 298 722 octets**.
+Le mod Demeure autonome est inclus dans le JAR Sanctuary via la déclaration Fabric `jars` ; l'assemblage contrôle son identité et sa licence. Aucun second fichier n'est à installer manuellement.
+
+- SHA-256 MRpack : `ef8499de78461e7eab7bafe3d1ccf38a2d06ec3a0e0884e8e10ffe30fc63071f`.
+- SHA-256 Sanctuary : `40f674ca491c1f7e12c0d5dd8994e01e7f998a079fa1853960cfc609c5d0fec6`.
+- SHA-256 Demeure : `8993d777ca47f0a7a7a9425532606f772dfdb5db07d400c2adb4207346acd674`.
+
+ZIP, versions internes, dépendances exactes, JAR imbriqué identique au build, notices, ressources et index packwiz vérifiés. Les MRpacks beta.003, .004 et .005 gardent leurs empreintes. Reçu `build/demeure006-artifact.json` ; journaux `build/beta006-delivery-final.log` et `build/beta006-client.log`. Export local uniquement : aucune publication de canal ni synchronisation Prism.
diff --git a/docs/demeure-map-performance-beta026.md b/docs/demeure-map-performance-beta026.md
new file mode 100644
index 0000000..bc27271
--- /dev/null
+++ b/docs/demeure-map-performance-beta026.md
@@ -0,0 +1,72 @@
+# beta.026 — affichage Demeure sans recalcul par image
+
+Ticket sur `codex/demeure-map-performance-beta026`.
+
+## Diagnostic et contrat
+
+La couche Demeure parcourait ses chunks puis les 256 blocs de chaque chunk
+visible à chaque image. Chaque bloc connu produisait un rectangle GUI distinct,
+y compris quand rien ne changeait. Le nombre de dessins dépendait donc de la
+surface explorée visible ; le bouton Demeure désactivait ce travail.
+
+La correction prépare une texture transparente de 1 024 × 1 024 pixels, alignée
+sur celle du terrain. Son rendu demande un seul dessin. Seuls les chunks dont
+les pixels connus ou la couleur Demeure changent sont repeints. Déplacer ou
+zoomer transforme l’image existante ; changer la fenêtre de chunks la reprojette.
+Les lots réseau identiques ne doivent provoquer ni effacement, ni nouvel envoi
+de texture au GPU. Une empreinte absente du lot suivant doit disparaître.
+
+Demeure reste activé par défaut. Ses règles serveur, contributions, scores,
+dates et sauvegardes restent inchangés. Aucun format de sauvegarde ou protocole
+ne change, aucun chunk n’est généré ou modifié par ce ticket. Les zones inconnues
+et le vide restent transparents, le changement de vue/dimension et la révocation
+des droits effacent également la couche Demeure. Les textures sont libérées à
+la fermeture de l’écran. Mémoire supplémentaire bornée : 4 Mio de pixels côté
+CPU et une texture RGBA8 de 4 Mio côté GPU.
+
+## Vérifications et limites
+
+Mesure native avant/après avec une même surface de 256 chunks de laboratoire,
+en comptant les rectangles, les transferts de textures et le temps CPU de
+soumission de la couche. Cas fixes, déplacement maintenu, couche masquée/réactivée,
+lots identiques et modifiés, brouillard, permissions, redimensionnement et fermeture.
+Les mesures de soumission ne sont pas une promesse de FPS sur chaque machine.
+
+`./gradlew check build assemblePack -PsanctuaryClientTests=true
+-PsanctuaryMapPerfClientTests=true -PsanctuaryFocusedTests=progression,map,demeure`
+réussit : **9 tests natifs serveur**, contrôles de contrats, compilation du
+parcours graphique et assemblage du pack. Journal : `build/atlas026-check.log`.
+
+Le parcours graphique préparé s’exécute avec :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
+ -PsanctuaryMapPerfClientTests=true
+```
+
+Il **reste à exécuter sur une session macOS déverrouillée**. Les essais de
+référence ont été interrompus au démarrage graphique, avant les mesures ;
+`IOConsoleLocked` était vrai. Aucun gain chiffré de FPS, temps CPU mesuré ou
+résultat graphique n’est revendiqué pour cette livraison. Le code Atlas de
+référence a été contrôlé identique au JAR beta.025 et conservé dans
+`build/atlas026-before.class` pour une comparaison ultérieure ; les autres
+contrôles serveur ne dépendent pas de l’affichage.
+
+La couche réutilise les mêmes couleurs et alpha par pixel : 0 pour l’inconnu
+et le vide, `0xA0` au bord des chunks, `0x25` à l’intérieur. Un changement de
+métadonnées sans changement de couleur met à jour le survol sans repeindre.
+Les paquets terrain et Demeure sont appariés dans l’ordre déjà garanti par
+`AtlasService.reply` ; le résumé Demeure, même vide, clôt son lot terrain.
+Le lot en attente est borné à 16 chunks et annulé au changement de requête.
+
+## Artefact local
+
+[Sanctuary-beta.026.mrpack](../build/Sanctuary-beta.026.mrpack), **4 766 675 octets**.
+SHA-256 : `f564aabbee977d48e8f3d0d2ea4178b23b18f33f42110ab0dba733f2ae008fbb`.
+Reçu : `build/atlas026-artifact.json`.
+
+ZIP, version, 1 253 classes Sanctuary, absence de tests embarqués et identité
+des JAR Demeure/JEI vérifiés. Seule la classe de l’écran Atlas diffère dans le
+code compilé Sanctuary par rapport à beta.025 ; les classes du module Demeure
+sont identiques. L’export beta.025 conserve son empreinte. Aucun canal, instance
+Prism ou monde personnel n’a été modifié.
diff --git a/docs/dive-until-collision-beta028.md b/docs/dive-until-collision-beta028.md
new file mode 100644
index 0000000..eba77c6
--- /dev/null
+++ b/docs/dive-until-collision-beta028.md
@@ -0,0 +1,42 @@
+# beta.028 — plongeon jusqu’à collision
+
+## Contrat
+
+Un saut en sprint suivi de **S’allonger** (W par défaut, touche configurable)
+déclenche toujours une seule impulsion vers l’avant. La posture de plongeon
+reste active jusqu’au contact du sol, d’un mur, d’un plafond ou à l’entrée dans
+l’eau ou la lave. La limite automatique de 30 ticks (1,5 seconde) est retirée.
+Relâcher ou réappuyer sur la touche, utiliser Maj ou tourner la caméra ne fait
+pas sortir de la posture en plein vol. Le serveur décide de sa fin.
+
+La gravité, les dégâts de chute et les règles de nage restent natifs. Le
+plongeon ne permet pas une nouvelle impulsion après avoir touché un mur :
+il faut à nouveau partir d’un saut en sprint depuis le sol. Mort, vol, monture
+et autres états incompatibles interrompent toujours la posture. Les règles
+de collision natives empêchent de se relever à travers un plafond trop bas.
+
+Le même attachement temporaire `sanctuary:lying` et la même valeur 3 sont
+réutilisés. Aucun nouveau format de sauvegarde, aucune migration et aucun
+changement des mondes. Client et serveur beta.028 ensemble.
+
+## Vérifications
+
+**32 tests serveur natifs réussis**, dont 5 nouveaux tests : maintien pendant
+240 ticks avec posture native et demandes répétées,
+collisions réelles via le moteur (mur, plafond, sol), redépart après un nouveau
+saut, arrêt dans l’eau et conservation de la nage verrouillée, mort et vol.
+Régressions familiers, repos et piles de joueurs incluses.
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=carry,familiar,movement`
+réussit en 3 min 47 s. La vérification visuelle reste à confirmer : session Mac
+verrouillée pendant cette livraison.
+
+Export local vérifié : [Sanctuary-beta.028.mrpack](../build/Sanctuary-beta.028.mrpack).
+Versions, intégrité ZIP, 1 253 classes compilées et JAR imbriqués vérifiés.
+Seuls DiveService, LyingService et LyingClient (et leurs classes internes)
+changent dans le code du jeu ; les aides FR/EN sont actualisées. Le portage,
+la carte et les autres systèmes conservent leur code beta.027.
+L’export beta.027 reste intact. Aucun déploiement effectué.
+
+SHA-256 MRpack : `531386776236e08cbeef8a26b77ccaf04533cce6bb47f695777190d0e94e5793`.
+Reçu : `build/dive028-artifact.json` ; vérificateur : `build/verify-dive028.py`.
diff --git a/docs/dynamic-lights-beta030.md b/docs/dynamic-lights-beta030.md
new file mode 100644
index 0000000..70154ed
--- /dev/null
+++ b/docs/dynamic-lights-beta030.md
@@ -0,0 +1,107 @@
+# LIGHT-01 — éclairage dynamique, beta.030
+
+## Contrat
+
+Les objets lumineux tenus en main principale ou secondaire, les cosmétiques de
+tête et les objets jetés éclairent le décor et les entités. Les autres joueurs
+sont des sources au même titre que soi. Un gâteau coiffé d'une bougie n'éclaire
+que si sa bougie est allumée ; les états des blocs font autorité.
+
+L'effet est visuel côté client, sans bloc artificiel, modification de sauvegarde
+ou changement des règles de spawn. La lumière utilise la teinte native de la
+lightmap ; ce ticket ne crée pas de lumière colorée ni d'ombres dynamiques.
+
+Les objets jetés dans une cellule de 4 × 4 × 4 blocs partagent une source :
+l'objet le plus lumineux la représente, à une position arrondie au demi-bloc.
+Les quantités des piles ne multiplient pas la lumière. Les sources immobiles
+ne provoquent pas de reconstructions répétées du terrain. Les calculs utilisent
+un instantané immuable indexé par section et une file de sections dédupliquée,
+traitée avec un budget par tick, sans chargement de chunks.
+
+## Utilisation et réglages
+
+L'effet est activé de base, sans aptitude. Il suit aussi les objets lumineux
+tenus par les créatures, leurs équipements de tête et les entités en feu.
+Les inventaires fermés, coffres et rangées non sélectionnées ne sont pas des
+sources. Les spectateurs n'éclairent pas les autres joueurs.
+
+`config/sanctuary-dynamic-lights.json` est créé localement au premier lancement.
+Le redémarrage du client applique les changements :
+
+| Réglage | Valeur initiale | Rôle |
+| --- | ---: | --- |
+| `enabled` | `true` | Active l'éclairage visuel |
+| `updateIntervalTicks` | 4 | Collecte des objets cinq fois par seconde à 20 TPS client |
+| `dropClusterSize` | 4 | Taille des cellules de regroupement des objets jetés |
+| `maxSources` | 32 | Sources les plus proches ; celle du joueur local est prioritaire |
+| `range` | 48 | Distance maximale de collecte autour de la caméra, en blocs |
+| `sectionUpdatesPerTick` | 12 | Maximum de sections marquées à mettre à jour par tick |
+| `itemOverrides` | `{}` | Intensité 0–15 par identifiant d'objet, par exemple `"mod:lampe": 12` |
+
+Les réglages numériques sont bornés pour éviter un coût accidentel démesuré.
+Contrôles → Sanctuary propose également « Activer / désactiver les lumières
+dynamiques », sans touche attribuée par défaut. Ce raccourci agit pour la session.
+Une désactivation éteint aussi les sections précédemment éclairées.
+
+Les objets-blocs utilisent leur émission native et leur composant `BLOCK_STATE`.
+Les bougies colorées et les gâteaux composites utilisent exactement l'état
+synchronisé du cosmétique. Hors blocs, le seau de lave émet 15, bâton/poudre de
+Blaze 10, poudre lumineuse/baies lumineuses 8, poche d'encre luisante/cadre
+luminescent 6. Une surcharge d'objet remplace explicitement ce comportement,
+y compris son état allumé/éteint ; 0 permet d'exclure un objet.
+
+## Portée technique
+
+Le rendu natif de Minecraft **26.3-pre-2** reçoit des coordonnées de lightmap
+enrichies : terrain et occlusion ambiante, eau, particules, blocs-entités et
+entités. La lumière du ciel et les surfaces déjà lumineuses sont préservées.
+Les tâches de reconstruction reçoivent chacune un instantané immuable.
+Les changements de monde et déconnexions abandonnent les sources précédentes.
+
+La décroissance est radiale, avec la lightmap habituelle de Minecraft : elle
+ne calcule pas d'occultation par les murs ni de couleur propre à chaque lampe.
+Les sources distantes au-delà du rayon ou du budget sont ignorées ; beaucoup
+de sources mobiles peuvent rendre leur suivi plus progressif. Un mouvement
+de caméra seul ne change pas la position d'une lampe portée.
+
+## Vérifications
+
+- Contrôles CPU : 10 000 objets dans une même cellule donnent une seule source,
+ plafonnement des sources, priorité locale, coordonnées négatives, sections
+ dédupliquées, budget strict, aucune invalidation après 500 mises à jour
+ immobiles, déplacement, suppression et conservation des instantanés des workers.
+- 100 000 requêtes d'éclairage comparées à une référence brute sans index :
+ mêmes résultats. Sur ce Mac M1, un million de requêtes sur l'index de stress
+ prennent 20–26 ms sur les exécutions de vérification ; c'est une mesure CPU
+ isolée, pas un résultat FPS.
+- Client natif réussi (`build/dynamic030-client-final.log`) : pièce fermée
+ sombre, torche principale, lanterne des âmes secondaire, bloc lumineux sur
+ la tête, gâteau allumé puis éteint, autre joueur avec lanterne, déplacement
+ du porteur et suppression des objets. Les coordonnées de lumière du moteur
+ de monde restent à zéro alors que le maillage et les entités s'éclairent.
+- 200 torches jetées fusionnent naturellement en quatre piles, toutes servies
+ par une seule source. Le courant déplace les objets ; l'éclairage les suit.
+ Après leur retrait, l'image redevient sombre. Neuf captures sont conservées
+ dans `build/dynamic030-evidence/`.
+
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=lights,cosmetics,familiarhit -PsanctuaryExpansionReload=true`
+ réussit : **21 tests serveur**, dont le registre complet des objets-blocs,
+ les états de bougies, le gâteau manipulé par un habitant et sa synchronisation.
+ Les régressions des cosmétiques et réactions des familiers passent également.
+ Log : `build/dynamic030-check.log`.
+- Le client natif est lancé avec `./gradlew :sanctuary:runClientGameTest
+ -PsanctuaryClientTests=true -PsanctuaryLightsClientTests=true
+ -PsanctuaryFocusedTests=lights`. Le dernier parcours complet réussit.
+
+## Livraison locale
+
+[Sanctuary-beta.030.mrpack](../build/Sanctuary-beta.030.mrpack), 4 792 058 octets.
+Version interne `beta.030`. Les 1 269 classes compilées correspondent au JAR
+embarqué ; aucune classe de test n'est distribuée. Le JAR JEI et les classes
+Demeure sont identiques à beta.029. Les classes existantes de Sanctuary restent
+identiques, hors l'initialiseur qui enregistre l'éclairage. L'archive beta.029
+est préservée.
+
+SHA-256 : `7942c661cbb01b5d0e4b6a59d9839e450223ab57aa3d45297c6816e884d70e80`.
+Reçu : `build/dynamic030-artifact.json`. Export local vérifié, sans publication
+du canal ni installation dans Prism.
diff --git a/docs/expansion.md b/docs/expansion.md
index 8af7322..975cf41 100644
--- a/docs/expansion.md
+++ b/docs/expansion.md
@@ -7,6 +7,25 @@ les commandes d’expansion dans le choix public **Sanctuary**. Le
alpha.12/12.1 et leur [laboratoire](alpha12-laboratory.md) restent historiques.
Le déblocage par recherches, XP et contributions n’est pas encore implémenté.
+## beta.001 — anciennes expéditions proches
+
+Les nouveaux mondes bêta commencent avec Sanctuary et quatre régions ouvertes
+de 512 blocs : nord glacial/Peaks, est tropical/Volcan, sud aride/Canyon et
+ouest humide/Océan. Leur [contrat de création](expeditions-beta001.md) est
+distinct de celui des anciennes sauvegardes.
+
+Dans ces mondes, les commandes cherchent au plus près sur l’axe choisi, par
+pas de 16 blocs, en conservant les marges écrites et les contrôles d’occupation.
+Le dernier argument `distance` reste une distance minimale **entre centres** ;
+`0` demande le placement automatique le plus proche admissible. Les commandes
+dans les mondes alpha conservent le placement précédent et son secteur aléatoire.
+Les quatre régions initiales se génèrent à l’exploration, comme Sanctuary ;
+les futures régions créées par commande gardent la préparation progressive.
+
+Avec les permissions opérateur : `/sanctuary expansion list` affiche les noms
+et identifiants ; `/sanctuary expansion visit noor_vigil` visite la région du
+nord. Les autres identifiants sont `makena_embers`, `kai_efe_rift` et `ari_waters`.
+
## Taille initiale et extensions — alpha.13
Le bouton natif **Personnaliser** propose **Petit** (512 blocs de diamètre
diff --git a/docs/expeditions-beta001.md b/docs/expeditions-beta001.md
new file mode 100644
index 0000000..0dfa3d8
--- /dev/null
+++ b/docs/expeditions-beta001.md
@@ -0,0 +1,155 @@
+# beta.001 — les quatre anciennes expéditions
+
+Ticket local **EXP-01**, branche `codex/ancient-expeditions-beta001`. Ce contrat
+décrit la création des quatre anciennes expéditions. La préparation de versionnement BETA-01 est conservée
+dans cette première bêta, encore non publiée.
+
+## Les régions
+
+Répartition confirmée par le créateur :
+
+| Direction | Nom | Climat | Relief |
+| --- | --- | --- | --- |
+| Nord | La Veille de Noor / Noor’s Vigil | glacial | peaks |
+| Est | Les Braises de Makena / Makena’s Embers | tropical | volcano |
+| Sud | La Faille de Kai et Efe / Kai and Efe’s Rift | arid | canyon |
+| Ouest | Les Eaux d’Ari / Ari’s Waters | humid | ocean |
+
+Ces régions sont les traces d’expéditions des anciens personnages, ouvertes
+avant l’arrivée des joueurs actuels. Le relief de Sanctuary reste celui de
+l’alpha.30.7 ; les quatre régions utilisent les générateurs d’expansion existants.
+Leur diamètre nominal est **512 blocs chacune**, confirmé par le créateur.
+
+## Contrat de création et de sauvegarde
+
+- Réserver les quatre régions ensemble lors de la création d’un **nouveau
+ monde Sanctuary bêta**, avant de publier les régions au générateur.
+- Enregistrer leur identité, graine dérivée, direction, climat, relief, taille
+ et position. Les îles sont ouvertes dès le départ ; leurs chunks se génèrent
+ à l’exploration, comme ceux de l’île initiale. Elles ne demandent pas de coût
+ ni de déblocage par les joueurs.
+- Distinguer ce contrat dans les paramètres du nouveau monde. L’absence du
+ paramètre `ancient_expeditions` conserve le contrat alpha.24/30.7 ; aucune ancienne sauvegarde ne
+ reçoit automatiquement les quatre îles.
+- Utiliser un journal bêta distinct, versionné, vérifié et atomique. Aucun
+ journal existant n’est converti : `data/sanctuary-world-beta001/expansions.json`,
+ schéma `3`, disposition `expedition_layout=1`. En cas de journal incomplet ou invalide,
+ arrêter avec une erreur au lieu de recréer les régions.
+- Conserver les identifiants des codecs et du terrain existants. Le nouveau
+ paramètre doit survivre à Personnaliser, à l’écriture du monde et au rechargement.
+- Une sauvegarde bêta n’est pas rétrocompatible avec les binaires alpha :
+ ne pas la rouvrir avec eux. Les mondes alpha restent sur leur ancien contrat.
+
+## Placement proche
+
+Chercher d’abord la position admissible la plus proche sur l’axe cardinal,
+sans l’écart supplémentaire ni le décalage aléatoire des anciennes commandes.
+Conserver les emprises écrites, la marge de décoration et la séparation des
+chunks ; les bateaux et autres réservations aériennes restent prioritaires.
+Avancer par pas d’un chunk si une réservation interdit la première position.
+La distance entre centres comprend les rayons des deux îles : ce n’est pas
+la longueur du vide à traverser entre leurs rivages.
+
+Le même placement proche est disponible pour les nouvelles demandes d’expansion
+dans les mondes bêta. Les contrôles d’occupation et de reprise du vide certifié
+restent obligatoires ; une distance `0` ne donne aucune permission d’écraser
+un chunk existant. Les positions et règles des mondes alpha sont conservées.
+
+Le plan utilise les identifiants stables `noor_vigil`, `makena_embers`,
+`kai_efe_rift`, `ari_waters`. Leurs noms FR/EN sont affichés dans la liste
+opérateur ; les identifiants restent présents pour les commandes. Les structures
+vanilla suivent les possibilités naturelles de chaque région : les quatre
+ouvertures initiales n’imposent pas un bâtiment particulier. Les futures
+expansions par commande gardent leur précontrôle de structure existant.
+
+## Périmètre narratif
+
+Le lore fourni associe Noor à l’aventure, Makena à la cuisine, Kai et Efe à la
+construction et Ari aux villageois. Les noms des expéditions prolongent ces rôles.
+Steve et ses amis sont des héros trans de cette fiction. Steve s’est sacrifié
+pour contenir Notch ; Galactium, Alpha, Backrooms et les récits fragmentaires
+des personnages nourrissent la suite. Le dénouement et les pouvoirs restent à
+définir. Ces éléments ne décrivent pas des mécaniques déjà livrées : aucun boss,
+pouvoir, journal à collecter ou déblocage créatif n’est ajouté par ce ticket.
+
+## Essai local
+
+Importer `build/Sanctuary-beta.001.mrpack` dans le lanceur, puis créer
+un **nouveau monde de type Sanctuary**. Pour une visite rapide, choisir Créatif
+et autoriser les commandes. La graine `0`, taille Moyen (724), permet de retrouver
+les mesures natives ci-dessous.
+
+`/sanctuary expansion list` affiche les cinq régions et les noms localisés.
+Les commandes de visite sont :
+
+```text
+/sanctuary expansion visit noor_vigil
+/sanctuary expansion visit makena_embers
+/sanctuary expansion visit kai_efe_rift
+/sanctuary expansion visit ari_waters
+```
+
+Le pack cible Minecraft `26.3-pre-2`, Fabric Loader `0.19.5` et Fabric API
+`0.160.0+26.3`, avec Java 25. Cet export local n’avance pas le canal packwiz
+publié et n’installe rien dans une instance personnelle.
+
+## Vérifications
+
+Plans déterministes sur les trois tailles et les graines `0`, `42` et
+`-7228211907433324401` ; quatre directions exactes, identités et profils attendus,
+emprises distinctes, réservations aériennes respectées et distances mesurées.
+La suite pure `ancientExpeditions001Smoke` passe : persistance des cinq régions,
+reprise, refus de corruption et d’un bootstrap interrompu, ajout d’une expansion
+ultérieure et ancien journal alpha inchangé octet pour octet. Les neuf plans
+utilisent un ciel vide ; un cas supplémentaire impose de vraies réservations
+de bateaux de fixture et vérifie leur priorité.
+
+`./gradlew assemblePack -x :sanctuary:check` réussit. L’export packwiz/Modrinth
+est vérifié : versions, index et empreintes, intégrité des deux archives,
+JAR embarqué identique au JAR construit et activation des expéditions dans
+le preset. Le reçu est dans `build/expeditions001-artifact.json` (ignoré).
+`./gradlew check build -PsanctuaryFocusedTests=expeditions` réussit en 4 min 29 s :
+tous les contrôles purs du cycle et les **3 GameTests ciblés** passent sur
+Moyen 724, graine `0`. Les quatre régions génèrent leurs vrais chunks, leurs
+biomes correspondent aux climats attendus, l’océan contient un bassin d’eau,
+et chaque visite trouve un point d’arrivée sûr. Un premier lancement avait
+expiré à cause d’un callback de test qui se replanifiait sous la même clé ;
+le scénario corrigé passe sans modifier le code de jeu.
+
+La seconde exécution, avec `-PsanctuaryExpansionReload=true`, réussit en
+1 min 40 s (**3/3**) : mêmes régions et codec, journal inchangé, et quatre
+blocs témoins posés lors du premier processus toujours présents après reprise.
+Les paramètres des trois tailles et des deux états de structures expérimentales
+passent aussi une écriture/lecture native sur disque ; un codec alpha sans
+le nouveau paramètre conserve son ancien journal.
+
+Mesures sur Moyen 724, graine `0`, disposition `1` :
+
+| Région | Centre X/Z | Ancienne distance entre centres | Nouvelle distance | Écart estimé de terrain sur l’axe |
+| --- | --- | --- | --- | --- |
+| Noor | 0 / -784 | 933 | 784 | 416 |
+| Makena | 784 / 0 | 1 076 | 784 | 360 |
+| Kai et Efe | 0 / 784 | 1 013 | 784 | 404 |
+| Ari | -784 / 0 | 1 096 | 784 | 348 |
+
+Distances en blocs. Le dernier relevé échantillonne les hauteurs du générateur
+tous les quatre blocs sur l’axe des centres ; ce n’est pas la distance minimale
+entre deux rivages ni un tracé de pont. Le placement gagne ici 149 à 312 blocs
+entre centres, mais les emprises conservées et la forme réelle des paysages
+laissent encore un vide important. Un rapprochement supplémentaire qui ferait
+se chevaucher leurs chunks demanderait une évolution du contrat de propriété.
+
+Après ces essais, la version interne a été simplifiée à `beta.001` à la demande
+du créateur. `./gradlew build assemblePack -x :sanctuary:check` réussit en 7 s.
+Le JAR final est identique au JAR testé, sauf le champ `version` de
+`fabric.mod.json`. Les 12 contrôles du nouveau format passent ; le MRpack
+final contient uniquement `sanctuary-beta.001.jar` comme JAR Sanctuary.
+
+Les preuves ignorées sont dans `build/expeditions001-check-build-retry.log`,
+`build/expeditions001-reload.log`, `build/beta001-clean-naming-assemble.log`,
+`build/expeditions001-artifact.json` et
+`mods/sanctuary/build/run/gameTest/diagnostics/expeditions001/724-0-{native,reload}.json`.
+Ce ciblage ne revendique pas la réussite de toute la suite native générale :
+les deux assertions déjà relevées pendant [BETA-01](versioning.md) restent
+ouvertes. Les chunks natifs sont vérifiés sur Moyen/graine `0` ; l’inspection
+visuelle client et les essais Windows restent à effectuer.
diff --git a/docs/expeditions-beta002.md b/docs/expeditions-beta002.md
new file mode 100644
index 0000000..f56441b
--- /dev/null
+++ b/docs/expeditions-beta002.md
@@ -0,0 +1,101 @@
+# beta.002 — climats et temples des expéditions
+
+Ticket local **EXP-02**, branche `codex/expedition-biomes-temples-beta002`.
+Le créateur a validé visuellement la génération de beta.001 et demande :
+
+- Nord : boréal/Peaks, avec taïga, neige et biomes de glace dans la palette boréale.
+- Ouest : conserver l’île océanique humide, avec un bassin `warm_ocean` et du corail vivant.
+- Sud : une pyramide du désert sur l’île aride/Canyon.
+- Est : un temple de la jungle dans l’île tropicale/Volcan, à une position qui varie avec la graine.
+
+Les quatre noms, directions, diamètres de 512 blocs et règles de rapprochement
+de beta.001 sont conservés. Les deux bâtiments utilisent les structures vanilla,
+avec leurs coffres, pièges, sauvegarde et localisation natives.
+
+## Contrat des nouveaux mondes
+
+Les nouveaux mondes enregistrent `ancient_expeditions=true` et
+`expedition_revision=2` dans le générateur. L’absence de révision reste `1`
+pour beta.001 ; l’absence d’expéditions garde le contrat alpha. Le journal
+beta.002 utilise `data/sanctuary-world-beta002/expansions.json`, schéma `3`,
+`expedition_layout=2`. Aucun ancien journal n’est converti et aucun chunk
+d’une sauvegarde jouée n’est régénéré.
+
+Les nouveaux biomes ne s’appliquent qu’à cette révision. Les deux temples sont
+planifiés avant l’exploration, à partir de la graine et du terrain, puis leurs
+starts sont enregistrés par Minecraft lors de la génération des chunks.
+Ils respectent l’option vanilla de génération des structures. La disposition
+et ses règles restent versionnées pour éviter de déplacer un site au rechargement.
+
+## Placement des temples
+
+Les candidats sont des chunks de l’intérieur de chaque île, à au moins 48 blocs
+de son centre, ordonnés par un tirage déterministe. La recherche filtre le type
+voulu, puis conserve les conditions natives de biome, de pente et de fondation.
+Elle ne modifie pas le relief pour imposer un bâtiment. Elle partage un cache
+borné à 32 768 colonnes par temple. Si aucun site admissible n’est trouvé dans
+ce budget, la création refuse explicitement cette disposition au lieu d’annoncer
+un temple absent. Les essais de graines ci-dessous donnent le périmètre vérifié.
+
+Les starts utilisent `minecraft:desert_pyramid` et `minecraft:jungle_pyramid`.
+Ils sont connus de `/locate structure` et sauvegardés au format natif. Leur
+réservation protège aussi les salles enfouies, les coffres et les pièges contre
+les structures voisines et la décoration ultérieure. Seules les pièces du
+temple concerné peuvent écrire dans son volume protégé pendant sa génération.
+
+Le bassin d’Ari conserve ses contours, profondeur et eau de beta.001 ; son
+biome chaud active les véritables features de récif vanilla. Aucune décoration
+artificielle de corail mort ne remplace ce récif.
+
+## Vérifications
+
+Sur macOS / Java 25, avec les mêmes dépendances Minecraft `26.3-pre-2`,
+Fabric Loader `0.19.5` et Fabric API `0.160.0+26.3` :
+
+- `./gradlew check build -PsanctuaryFocusedTests=expeditions` réussit en
+ 4 min 38 s : contrôles purs du cycle et **4/4 GameTests ciblés**.
+- Les plans des trois tailles sur les graines `0`, `42` et
+ `-7228211907433324401` conservent les centres et les graines des quatre îles.
+ Les journaux des deux révisions sont testés : relecture, refus de migration
+ implicite et octets beta.001 conservés. Les réglages beta.001 sans révision
+ explicite gardent leur valeur `1` ; les réglages beta.002 survivent à
+ Personnaliser et à une vraie sauvegarde/relecture des trois tailles.
+- Sur Moyen 724 / graine `0`, les chunks des deux temples sont générés et
+ inspectés, leurs starts passent un aller-retour NBT et `/locate` les trouve.
+ La pyramide possède **4 coffres et 9 TNT**, le temple de jungle **2 coffres
+ et 2 distributeurs**. La palette boréale échantillonnée contient les six
+ biomes : taiga, grove, snowy_taiga, snowy_plains, ice_spikes et frozen_peaks.
+ **69 blocs de corail vivant** sont comptés dans les trois premiers chunks
+ océaniques inspectés ; ce nombre n’est pas un inventaire de toute l’île.
+- La reprise dans un deuxième processus réussit, **4/4**, en 1 min 56 s.
+ Les rapports des temples/récif avant et après reprise sont identiques ;
+ le journal, les paramètres et les quatre blocs témoins des joueurs sont
+ conservés. Le refus de chevauchement inclut les chambres enfouies des temples.
+- Un nouveau monde Moyen 724 / graine `42` passe aussi **4/4**, en 2 min 15 s,
+ avec les six biomes boréaux et 178 blocs de corail vivant dans les trois
+ chunks inspectés. Les deux sites diffèrent de ceux de la graine `0` :
+ pyramide au chunk `[3, 48]` au lieu de `[2, 46]`, temple au chunk `[50, -7]`
+ au lieu de `[56, -3]`. Les coffres et pièges attendus sont présents.
+- `./gradlew assemblePack -x :sanctuary:check` réussit en 6 s après les
+ contrôles. L’export `build/Sanctuary-beta.002.mrpack` est vérifié : intégrité
+ ZIP, versions et dépendances, index packwiz, JAR embarqué identique au JAR
+ construit, révision `2` du nouveau preset et présence du lecteur beta.001.
+ Le reçu est dans `build/expeditions002-artifact.json`.
+
+MRpack SHA-256 : `8a7f956b554385362f70a6b535504ecf5223d3ee86bf3c81bce2913060c104ca`.
+JAR SHA-256 : `1d7ff72567aa6d9807332917368a13c1e72bcbb3b6d6620c4a0f72907978b4fb`.
+
+Importer **`Sanctuary-beta.002.mrpack`**, puis créer un nouveau monde de type
+**Sanctuary**, avec les structures activées pour visiter les temples. Les
+commandes `/sanctuary expansion visit kai_efe_rift` et
+`/sanctuary expansion visit makena_embers` amènent sur les îles concernées ;
+`/locate structure minecraft:desert_pyramid` et
+`/locate structure minecraft:jungle_pyramid` localisent les bâtiments natifs.
+
+Les logs ignorés sont dans `build/expeditions002-check-build.log`,
+`build/expeditions002-reload-0.log` et `build/expeditions002-native-42.log`.
+Les relevés détaillés sont dans
+`mods/sanctuary/build/run/gameTest/diagnostics/expeditions002/`.
+La suite native générale garde les deux assertions historiques décrites dans
+`docs/versioning.md` ; cette livraison ne les présente pas comme corrigées.
+L’inspection graphique du nouveau pack et les essais Windows restent à faire.
diff --git a/docs/faction-two-slots-beta046.md b/docs/faction-two-slots-beta046.md
new file mode 100644
index 0000000..abbf258
--- /dev/null
+++ b/docs/faction-two-slots-beta046.md
@@ -0,0 +1,46 @@
+# FACTION-046 — deux places dès la fondation
+
+Branche `codex/sky-flight-zoom-beta046`, à partir de beta.045.
+
+## Contrat
+
+Une nouvelle faction coûte toujours dix niveaux par défaut, sans prestige.
+Elle dispose de deux places : le fondateur responsable et un invité, qui rejoint
+sans payer et sans prestige. La troisième place et les suivantes demandent une
+charge de prestige chacune par défaut. Le serveur revalide la capacité à chaque
+acceptation : deux invitations simultanées ne permettent pas de dépasser la limite.
+
+## Migration
+
+La configuration `config/sanctuary/cycle-factions.json` passe au schéma 3.
+Les schémas 1 et 2 sont lus puis convertis une seule fois au chargement :
+`initialCapacity` devient 2 ; un ancien `maxMembers: 1` devient 2. Les prix,
+récompenses, autres plafonds et le réglage de conservation d'XP sont préservés
+(la conversion historique de `creationCost` vers dix niveaux reste applicable
+au schéma 1). Cette règle vaut uniquement pour les fondations futures.
+
+Le journal `data/sanctuary-cycle-factions.json` accepte les schémas 1, 2 et 3.
+La lecture ne réécrit pas les anciens événements : capacités, membres, dépenses,
+charges et prestiges existants restent identiques. La prochaine mutation écrit
+le schéma 3 avec l'historique conservé. Les créations anciennes à une place
+restent valides, les nouvelles créations en proposent deux. Les identifiants
+d'événements et de sauvegarde ne changent pas. Ne pas faire relire un journal
+schéma 3 par un ancien binaire ; restaurer ensemble journal et joueurs pour
+revenir en arrière. Aucun monde personnel n'est modifié pendant ce ticket.
+
+Le marqueur réseau additif `sanctuary:faction_founding_pair_v1` évite qu'un
+ancien client lise une configuration qu'il ne reconnaît pas. Les libellés FR/EN
+présentent les deux places, dont une pour le fondateur.
+
+## Vérifications et livraison
+
+Le test de contrat `cycle011Smoke` passe : conversion des configurations
+schémas 1/2, idempotence, conservation d’une ancienne faction à une place,
+fondation à deux places, invitation immédiate et écritures interrompues.
+Les scénarios natifs font partie des 20 GameTests serveur ciblés réussis :
+un invité rejoint sans prestige, un troisième membre est refusé jusqu’au
+financement d’une place, puis peut rejoindre. Les nouveaux instantanés client
+reflètent la capacité et les dépenses exactes.
+
+Build, exports et parcours client sont décrits dans
+[le ticket beta.046](sky-flight-zoom-beta046.md) et [Distribution](packwiz.md).
diff --git a/docs/familiar-combat-beta054.md b/docs/familiar-combat-beta054.md
new file mode 100644
index 0000000..17ca10c
--- /dev/null
+++ b/docs/familiar-combat-beta054.md
@@ -0,0 +1,180 @@
+# beta.054 — familiers de combat, duels et mises
+
+Branche `codex/familiar-combat-beta054`. Cette version ajoute le combat physique,
+les profils des 88 espèces, les ordres et les duels avec mises en objets. Le
+catalogue de conception reste [la refonte](familiar-combat-design.md). La
+validation des duels ci-dessous ne constitue pas une validation exhaustive de
+chacun des comportements et services de vie commune des 88 fiches.
+
+## Compatibilité et migration additive
+
+- Les identifiants d'objets, d'entités, d'aptitudes et de sauvegardes existants
+ restent stables. Aucun monde personnel n'est ouvert ou modifié par les tests.
+- Un ancien œuf équipé reçoit une identité individuelle au premier usage du
+ nouveau système. Son objet, son nom, sa taille et ses autres composants sont
+ conservés. Les œufs non utilisés ne sont pas réécrits.
+- L'identité utilise une entrée séparée de `custom_data` ; la santé, le K.-O.,
+ les techniques et souvenirs utilisent un registre versionné séparé du monde.
+ Les registres historiques de progression et de pouvoirs restent lisibles.
+- Un identifiant ou schéma inconnu est conservé et désactivé, jamais converti
+ silencieusement en un nouveau compagnon. Une copie du même œuf représente le
+ même individu ; elle ne crée pas une réserve de santé supplémentaire.
+- Retrait, transfert, mort, prestige et reconnexion ne réinitialisent ni la vie
+ ni les délais. Un seul exemplaire d'un individu peut être actif sur le serveur.
+- Les futures espèces disposent de profils nommés et versionnés ; leur retrait
+ temporaire conserve les données de leurs individus pour une réinstallation.
+- Revenir à beta.053 masque les données nouvelles sans les supprimer. Les
+ modifications faites avec une ancienne version ne sont pas une synchronisation
+ inverse du combat : sauvegarder le monde avant tout retour de version.
+
+## Règles de cette livraison
+
+Un compagnon actif, combat en temps réel, actions émises depuis sa position.
+Suivre, engager, protéger et rappeler ; G lance la technique équipée et une
+touche configurable ouvre les ordres. Les signatures sont immédiatement
+accessibles ; le Lien actif déjà acheté garde sa valeur pour les variantes.
+Les nouveaux menus réutilisent les contrôles et textures natifs.
+
+Le combat termine après une période sans engagement réel. Rappeler reste
+possible immédiatement ; changer d'individu attend la fin de cette période.
+Les dégâts ennemis peuvent provoquer un K.-O., jamais détruire l'œuf. Les
+caresses et coups amicaux gardent leur retour visuel et sonore sans dégâts.
+Les soins utilisent des ressources réelles et le repos hors combat.
+
+Les attaques autonomes et les nouvelles techniques de combat préservent le
+terrain. Les anciens usages de travail, dont démolition et allumage, sont
+séparés et explicitement sélectionnés hors combat ; leurs droits et ressources
+restent applicables. Les anciens bonus de combat ne s'ajoutent pas aux nouveaux.
+Le consentement allié, les factions et le réglage PvP s'appliquent côté serveur.
+
+## Duels entre joueurs
+
+### Duels et mises — contrat ajouté le 15 septembre
+
+Les deux joueurs acceptent une invitation locale, puis valident chacun les deux
+mises affichées. Une mise est facultative et contient au plus une pile d'objets,
+avec ses composants natifs. Les cases de l'inventaire débloqué servent à choisir
+la pile (clic) ou un objet (clic droit). L'emplacement de mise est un aperçu :
+les objets restent dans l'inventaire jusqu'à la double validation. Toute
+modification annule les validations ; une ancienne révision ne peut accepter
+une nouvelle offre. Les quantités sont revérifiées et retirées côté serveur.
+
+Trois secondes de préparation, trois minutes de combat maximum. Les permissions
+du duel concernent exclusivement les deux familiers, même entre alliés ou quand
+le PvP général est désactivé. Les propriétaires peuvent donner les ordres et
+utiliser leurs techniques, mais ne frappent pas directement le familier adverse.
+Le terrain reste soumis au contrat du combat. Pas d'apprentissage par duels.
+Vie et recharges réelles persistent après le résultat ; aucune guérison de duel.
+
+K.-O., abandon explicite, rappel, changement d'individu ou sortie du périmètre
+de 24 blocs donnent la victoire à l'adversaire. Une interruption technique,
+déconnexion ou expiration sans gagnant restitue les mises. Le résultat indique
+les dégâts, impacts et techniques pour faciliter les essais. Une déconnexion
+volontaire reste indistinguable d'une coupure réseau : ces paris amicaux ne
+constituent pas un classement compétitif résistant à ce comportement.
+
+Le gagnant reçoit les deux mises. Un inventaire plein conserve le paiement dans
+le registre, accessible depuis Duel. Aucun gain n'est lâché au sol. Le nouveau
+fichier `sanctuary/familiar-stakes-v1.json` et le reçu joueur additif
+`sanctuary:familiar_duel_receipt` forment le contrat de transaction : journal
+écrit avant le débit, inventaire et reçu sauvegardés ensemble, résultat écrit
+avant paiement, puis accusé de réception sauvegardé avant purge du journal.
+Dans 26.3-pre-2, l'hôte solo est lui aussi sauvegardé dans `playerdata` ;
+`level.dat` conserve son `singleplayer_uuid`, pas son inventaire.
+La reprise d'un journal encore actif restitue les objets réellement débités ;
+un résultat déjà enregistré conserve son gagnant. Une erreur ou un schéma
+inconnu désactive les paris et préserve les données pour récupération.
+
+Retour à beta.053 : ne pas jouer avec des mises en attente. Terminer les duels
+et récupérer tous les objets avant un retour de version ; sinon restaurer la
+sauvegarde complète correspondante, jamais uniquement le journal ou un joueur.
+Ces ajouts ne réécrivent aucun monde historique hors de la migration paresseuse
+explicitée plus haut. Les tests utilisent exclusivement des mondes de développement.
+
+## Niveaux stockés — contrat du 15 septembre
+
+La fiche du familier permet de déposer ou retirer l'XP hors combat et hors
+invitation/duel. Les montants sont des points d'XP exacts : un cran correspond
+à la prochaine frontière de niveau du familier au dépôt et au niveau entier
+inférieur au retrait, suivant la courbe Minecraft.
+« Tout déposer » et « Tout retirer » conservent également les points partiels.
+Le niveau est attaché à l'individu dans l'œuf et suit les échanges, jamais au
+joueur. Chaque niveau donne +2,5 % de vie maximale et +1,25 % de dégâts,
+sans plafond de progression de jeu. La rareté monte d'un palier tous les dix
+niveaux, jusqu'à légendaire ; les statistiques continuent ensuite à progresser.
+Changer la charge conserve la vie courante absolue (bornée par le nouveau
+maximum) et ne soigne pas. Les délais et souvenirs sont conservés.
+
+Migration additive explicite : les individus de schéma 1 se lisent avec zéro
+XP ; leur prochaine écriture produit le schéma 2 avec `storedXp`. Le registre
+extérieur reste `familiars-v1.json`. Les schémas inconnus restent préservés.
+Un nouveau journal `sanctuary/familiar-xp-v1.json` contient uniquement les
+transferts en attente. Le reçu joueur `sanctuary:familiar_xp_receipt` est
+sauvegardé avec l'XP native avant de valider la nouvelle charge du familier.
+Une reprise termine uniquement une opération dont ce reçu existe ; autrement,
+elle l'annule sans crédit. Un individu avec transfert non résolu est gelé
+jusqu'à la reconnexion du joueur concerné. Une erreur conserve le journal et
+désactive les transferts. Le compteur utilise des entiers 64 bits ; une
+opération dépassant la représentation native du joueur est refusée sans perte.
+Les copies d'un œuf partagent le même solde serveur. Avant un retour à une
+ancienne version, retirer l'XP et terminer tous les transferts ; restaurer une
+sauvegarde complète plutôt que des fichiers individuels.
+
+Les points de vie sont conservés dans les unités du profil de base, puis
+convertis en réserve effective pour les dégâts et les affichages Sanctuary.
+Cela permet de dépasser la limite de l'attribut natif de santé, sans introduire
+un plafond caché à la progression. Les soins et le repos restaurent la même
+fraction de la réserve qu'avant cette extension.
+
+## Vérifications
+
+Sur Minecraft 26.3-pre-2, Java 25, dans un monde plat de développement :
+
+- `Combat054ClientChecks` : chargement des 88 profils, attaque autonome du loup
+ depuis son entité physique, blessure persistante, sauvegarde et vraie
+ reconnexion ; menus FR/EN.
+- `Duel054Checks` : deux entités `ServerPlayer`, invitation, double validation,
+ changement de mise et refus d'une ancienne validation, refus d'une case
+ verrouillée, aucun débit avant consentement mutuel.
+- Combat autonome, adversaire limité au familier, absence d'aide du rival,
+ dégâts directs du propriétaire refusés, K.-O., paiement exact, réclamation
+ répétée sans duplication et absence d'apprentissage par duel.
+- Duel sans mise, projectile natif, projectile non sauvegardable, abandon,
+ blessure conservée et refus d'un projectile arrivé après la fin du duel.
+- Clic natif sur la mise dans le menu, aller-retour réseau et mise effacée
+ côté serveur ; affichage et commandes FR/EN.
+- Inventaire plein, gain conservé, récupération ultérieure, reprise du journal
+ interrompu, restitution sans double paiement, coffre renommé avec contenu
+ natif conservé après le transit.
+
+Les tests serveur ci-dessus tournent dans le client intégré, sans accepter
+l'EULA d'un serveur dédié. Ils ne remplacent pas une session de charge avec
+deux clients réseau indépendants. Captures : `build/combat054-evidence/` ; journal
+client : `build/combat054-final-client-pack.log`.
+
+`Experience054Checks` vérifie également les frontières de la courbe XP native,
+les allers-retours d'XP même après une dépense de niveaux, les points partiels,
+le refus d'une requête périmée, le gel dès l'invitation de duel et pendant un
+combat. Un familier de niveau 120 inflige les dégâts supplémentaires réels ;
+sa réserve de santé reçoit les dégâts en unités effectives. Dépôt et retrait
+ne restaurent pas sa vie. La migration de schéma 1 conserve les inconnus.
+La reprise est testée avant débit, après reçu joueur et après écriture du
+familier ; les tentatives répétées ne multiplient pas les crédits. Un échange entre deux
+joueurs conserve la charge, permet au nouveau propriétaire de la retirer et
+refuse qu'une copie conservée par l'ancien propriétaire serve à la voler. Les boutons
+natifs, la rareté légendaire de l'œuf et une nouvelle ouverture du monde avec
+l'XP stockée sont vérifiés. Ce ne sont pas des tests d'arrachement physique du
+disque ou de panne électrique.
+
+## Limites de la refonte à vérifier par espèce
+
+La validation exhaustive conserve comme critères : comportement favorable et refus utile pour chaque espèce,
+munitions/consommables conservés, origine des attaques, protections finies,
+K.-O./soins, rappel/changement/transfert/reconnexion, PvP et factions, absence de
+destruction autonome, menus FR/EN et conservation des fonctionnalités historiques.
+Les valeurs restent des valeurs de test. Certains services de vie commune et certaines variantes utilisent encore des
+comportements génériques : plateforme commune pour plusieurs espèces,
+ancrage par ralentissement, suivi de cible simple et protection sans renvoi
+réel de projectile. Leur fidélité au catalogue reste à finaliser. Les duels
+rendent les essais possibles ; cette livraison ne constitue pas la finition
+exhaustive des 88 fiches ni la fin de leur équilibrage.
diff --git a/docs/familiar-combat-design.md b/docs/familiar-combat-design.md
new file mode 100644
index 0000000..ad6b62f
--- /dev/null
+++ b/docs/familiar-combat-design.md
@@ -0,0 +1,405 @@
+# Les 88 familiers — combattre et vivre ensemble
+
+**Proposition de conception du 15 septembre 2026. Aucun de ces changements n'est implémenté par ce document.**
+
+Ticket documentaire **FAM-DESIGN-03**, branche `codex/familiar-combat-design`.
+Référence du dépôt : **beta.052**, Minecraft **26.3-pre-2**. Catalogue fermé sur les
+88 entrées de [CompanionType.java](../mods/sanctuary/src/main/java/fr/koka/sanctuary/companion/CompanionType.java).
+Les raretés existantes sont conservées : **22 / 22 / 25 / 16 / 3**.
+
+## 1. Direction et portée
+
+Le créateur demande d'explorer le combat avec son familier, avec Pokémon comme
+point de départ modifiable, puis d'établir le rôle des 88 espèces. Cette proposition
+retient le **combat en temps réel en duo** : le joueur combat avec ses armes,
+ses outils et le terrain ; son compagnon agit physiquement à ses côtés.
+
+L'objectif est de pouvoir raconter « il m'a couvert pendant que je rechargeais »
+ou « je suis revenu le chercher », puis de retrouver ce même compagnon dans la
+vie quotidienne. Son rôle, ses initiatives et ses réactions doivent être visibles.
+
+État actuel : les [pouvoirs beta.044](spawn-eggs-catalogue-beta044.md) sont surtout
+déclenchés par le joueur ; les familiers sont invincibles et sans combat autonome.
+Le [nom, la taille et les interactions](familiar-interactions-beta020.md), ainsi que
+le [nom à l'arrivée](starter-name-beta052.md), constituent des bases déjà livrées.
+La présente proposition introduit un autre fonctionnement ; les anciens pouvoirs,
+bonus et dégâts natifs ne s'additionnent pas automatiquement à ces nouvelles fiches.
+Le catalogue beta.044 continue de décrire le jeu livré tant qu'un ticket de code
+n'a pas remplacé explicitement chaque comportement concerné.
+
+Les noms français ci-dessous servent la lecture du document ; les identifiants
+entre parenthèses font autorité. Une future interface devra avoir ses libellés FR/EN.
+
+## 2. Règles communes proposées
+
+### Un partenaire, quelques ordres
+
+- **Un seul compagnon actif par joueur.** Un combat engage ce duo. Remplacer le
+ compagnon attend la sortie de combat ; le rappeler reste possible immédiatement.
+- **Suivre** : accompagne, signale les menaces, attend l'engagement du joueur.
+ **Engager** : poursuit la cible désignée dans un périmètre local.
+ **Protéger** : reste près d'une personne ou d'un point désigné et intercepte.
+ **Rappeler** : abandonne son action et revient ; l'ordre peut aussi le ranger.
+- En défense, il réagit à une agression subie par le duo. Il ne choisit pas tout
+ seul des animaux neutres ou des inconnus comme ennemis et ne poursuit pas hors
+ du périmètre. Une cible perdue conduit au retour, avec un signal compréhensible.
+- **G déclenche une technique équipée**, choisie parmi la signature et une variante
+ apprise. Un sélecteur d'ordres configurable donne accès aux consignes. Les gestes
+ précis devront être essayés avec les raccourcis déjà présents dans Sanctuary.
+- L'initiative de chaque fiche est l'action de base autonome, répétée à cadence
+ mesurée tant que l'ordre le permet. La technique G demande un choix du joueur.
+ Les signatures sont accessibles dès le début de la relation ; le premier combat
+ doit enseigner leur usage. L'articulation avec l'achat actuel du Lien actif reste
+ à traiter explicitement dans le ticket de progression.
+
+### Des corps présents dans le combat
+
+- Attaques, écrans et aides partent **du compagnon**. Son trajet, sa position, sa
+ préparation et sa récupération comptent. Les animations accompagnent la réponse
+ sans imposer un long déplacement à chaque geste d'urgence.
+- Les cibles peuvent l'attaquer et interrompre ses actions annoncées. Les attaques
+ lourdes laissent le temps de s'écarter. La puissance dépend du rôle et de ces
+ fenêtres, jamais simplement de la rareté ou de la taille visuelle de l'œuf.
+- Les familiers gardent une circulation confortable autour des alliés. Une garde
+ intercepte les attaques dans son volume d'effet ; elle n'exige pas de bloquer
+ physiquement les portes avec le corps du compagnon.
+- Les poissons et autres aquatiques portent sur terre une **petite enveloppe d'eau
+ magique**, proche du sol, qui autorise leurs actions courtes. Elle ne crée pas
+ d'eau, ne permet pas de voler avec le joueur et ne le fait pas respirer. Dans
+ l'eau réelle, ces compagnons gagnent surtout de la liberté de mouvement. Tous
+ doivent avoir au moins un service de combat utilisable sur Sanctuary Island.
+- Un cheval familier reste un petit esprit compagnon. Les aides de déplacement
+ sont brèves ; les chevaux et bateaux réels gardent leur rôle de transport.
+- Les familiers morts-vivants restent utilisables en journée. Les propriétés des
+ mobs sauvages ne s'appliquent que lorsqu'elles servent un comportement prévu
+ dans la fiche. Ni combustion solaire ni mort de l'abeille après une piqûre ici.
+
+### Se protéger mutuellement
+
+- Chaque compagnon aurait sa propre vie et pourrait **tomber K.-O.** Il se replie
+ dans son œuf. Le combat continue avec le joueur encore debout.
+- Le repos hors combat permet sa récupération ; nourriture et soins réels peuvent
+ aider pendant une expédition. Le délai et les quantités restent à régler en
+ prototype. L'absence du joueur ne dégrade jamais la relation.
+- Rappel, changement d'œuf, déconnexion ou mort du joueur ne rendent pas gratuitement
+ la vie et les techniques. Blessures et récupération suivent le même individu.
+- Le K.-O. du compagnon ne détruit pas son œuf. La perte éventuelle de l'objet à la
+ mort du joueur reste un sujet distinct, selon les règles d'inventaire existantes.
+ Conserver une mémoire ne promet pas de recréer un œuf perdu ou détruit.
+- Le joueur peut continuer ses activités avec un compagnon au repos. Aucun métier
+ ni accès à la progression n'exige d'avoir un compagnon disponible en permanence.
+
+### Des règles qui permettent de combiner les espèces
+
+- Les provocations, leurres et déplacements d'ennemis concernent d'abord les
+ **monstres ordinaires**. Ils n'imposent jamais une visée ou un déplacement à un
+ joueur. Boss, grosses créatures et cibles immunisées ont une résistance adaptée.
+ Les protections, frappes et repères restent utiles contre eux.
+- Une entrave est courte, puis la cible bénéficie d'une résistance commune aux
+ nouvelles entraves. Alterner 30 familiers ne doit pas immobiliser une cible à vie.
+- Les gardes ont une capacité d'absorption finie et des angles lisibles. Une même
+ attaque ne peut pas être annulée successivement par une chaîne de familiers.
+- Les soins et l'air ont un budget commun **par bénéficiaire**. Tous les soins
+ rendent des PV réels, bornés par sa capacité. Ils consomment la ressource annoncée
+ et ne peuvent pas être multipliés en changeant de compagnon ou en traversant
+ plusieurs fois la même zone. Les recettes d'alchimie conservent leur intérêt.
+- Les armes de tir matérielles utilisent de vraies munitions préparées par le
+ joueur. Les projectiles intrinsèques, comme les crachats et rayons, restent des
+ effets sans butin récupérable. Les capacités n'ajoutent aucun inventaire général :
+ les livraisons déplacent des objets existants et accessibles. Échec de livraison :
+ retour au stock d'origine, ou dépôt réel récupérable si ce retour est impossible.
+- Les fournitures viennent des cases débloquées ou d'un stockage explicitement
+ autorisé et proche. Le compagnon n'ouvre pas des coffres distants ou inconnus.
+ Un joueur reçoit manuellement les potions et aliments livrés ; leur consommation
+ automatique est limitée aux compagnons alliés auxquels le soin est destiné.
+- Le PvE est la cible du premier prototype. En multijoueur, aides et déplacements
+ alliés respectent le consentement existant ; les attaques respectent les factions,
+ les permissions et le réglage PvP. Les chiffres PvP demanderont leurs essais.
+- **Terrain : décision proposée pour ce futur système** : les attaques autonomes
+ de combat n'endommagent pas les constructions. Démolition et allumage existent
+ comme ordres de travail explicites, avec leurs ressources et droits. Cela change
+ les explosions natives livrées en beta.044 ; ce changement devra être nommé dans
+ le futur ticket et sa migration, et n'est pas actif aujourd'hui.
+- Les repères utilisent des cibles ou indices locaux et une mémoire autorisée.
+ Un familier ne révèle ni une carte entière ni le contenu d'un coffre fermé.
+
+### Apprentissage et attachement
+
+Chaque fiche donne une **signature initiale** et une **variante à apprendre**.
+On en équipe une à la fois, hors combat. La variante propose une autre façon
+d'aider ; elle ne s'ajoute pas comme un second bouton obligatoire. Le comportement
+de base reste disponible avec l'une ou l'autre.
+
+La confiance grandit par des expériences variées et utiles : aider réellement,
+explorer, se protéger, se reposer ensemble. Temps AFK, ennemis invoqués pour farm,
+coups volontaires sur ses alliés et répétition de caresses ne font pas progresser
+la maîtrise. Les premiers apprentissages doivent être accessibles rapidement.
+
+Les petits gestes des fiches sont des **expressions possibles de l'espèce**.
+Chaque individu aurait un tempérament stable qui choisit leur fréquence et leur
+forme sans rendre un compagnon moins fiable. Il conserve son nom, sa taille,
+ses apprentissages et quelques souvenirs attestés : lieu de rencontre, expédition,
+secours réel, retour au foyer, prestige vécu. La fiche Habitant porterait cette
+mémoire ; la relation se manifesterait surtout dans le monde.
+
+Le têtard et la grenouille, ainsi que les variantes mortes-vivantes, restent ici
+des espèces distinctes. Une évolution ou une transformation serait une décision
+séparée, jamais une conversion automatique d'un individu auquel on s'est attaché.
+
+## 3. Catalogue complet
+
+Les actions ci-dessous sont des comportements proposés. Les mots « court »,
+« proche », « quelques » décrivent l'intention avant mesure. Les dégâts, PV,
+portées, recharges et coûts définitifs seront établis par famille de comportement,
+puis vérifiés par espèce. Aucun équilibrage chiffré des 88 n'est revendiqué.
+
+### Communs — 22
+
+| Nº · Familier | Rôle et initiative autonome | G · Signature | Variante à apprendre | Vie commune : service et petit geste |
+| --- | --- | --- | --- | --- |
+| 01 · Chauve-souris (`bat`) | **Éclaireuse.** Tourne à distance des coups et signale l'ennemi qui approche derrière le joueur. | **Écholocalisation** : une pulsation révèle brièvement les créatures proches qui bougent, avec des repères directionnels. | **Diversion** : passe devant une cible pour attirer brièvement son attention, puis revient se mettre à l'abri. | Guide vers une ouverture proche déjà détectée ; se suspend au plafond au repos. |
+| 02 · Chat (`cat`) | **Garde rapprochée.** Reste près du joueur et griffe les petits ennemis qui franchissent sa garde. | **Dos rond** : s'interpose et fait hésiter ou reculer les petits monstres devant lui. | **Patte vive** : bondit sur l'assaillant désigné pour interrompre une préparation interruptible. | Avertit des Creepers proches ; choisit un endroit chaud pour se coucher. |
+| 03 · Poule (`chicken`) | **Gêne de proximité.** Picore une cible engagée, recule et oblige les monstres ordinaires à se retourner. | **Grand battement** : une bourrasque courte écarte les petits ennemis autour du joueur. | **Réception** : rejoint un allié qui chute et freine une descente, puis doit se reposer. | Aide les petits franchissements et les réceptions de chantier ; accourt lorsqu'on sort des graines. |
+| 04 · Morue (`cod`) | **Secours individuel.** Suit le duo à couvert ; lance une petite giclée défensive sur l'ennemi qui s'approche. | **Bulle de secours** : apporte à une personne ou un familier une bulle qui éteint une brûlure ; sous l'eau, elle rend aussi un peu d'air. | **Éclaboussure** : projette un jet bref qui repousse une petite cible et mouille localement les créatures touchées. | Repère une poche d'air proche ; dessine un petit cercle autour du plongeur revenu à la surface. |
+| 05 · Vache (`cow`) | **Protectrice de contact.** Se place de côté devant son maître et donne un coup de tête à l'assaillant proche. | **Lait de secours** : apporte une dose du lait fourni pour retirer Poison et Faim à un bénéficiaire. | **Épaule solide** : encaisse une partie d'un prochain impact destiné à un allié, avec sa propre vie. | Transporte un seau préparé sur une courte distance ; vient frotter son front après un secours. |
+| 06 · Âne (`donkey`) | **Ravitailleur de position.** Reste derrière le duo, lui apporte ses munitions disponibles et rue sur ceux qui le poursuivent. | **Point de ravitaillement** : dépose près de lui un petit lot choisi du stock du joueur pour les alliés qui viennent le prendre. | **Dégage !** : une ruade repousse l'ennemi le plus proche et lui ouvre un trajet de retour. | Aide au rangement dans un coffre désigné ; attend patiemment au point de livraison. |
+| 07 · Renard (`fox`) | **Opportuniste.** Contourne une cible occupée par le joueur, mord puis se retire. | **Chapardage** : file chercher un objet au sol désigné, même près des ennemis, et le rapporte. | **Feinte** : fait mine d'engager une cible, l'attire de quelques pas puis esquive de côté. | Récupère les récoltes tombées ; rapporte fièrement un objet dans sa gueule. |
+| 08 · Grenouille (`frog`) | **Contrôle précis.** Bondit entre les appuis et frappe à courte portée avec sa langue. | **Coup de langue** : tire vers elle un objet ou un petit ennemi désigné. | **À la rescousse** : ramène un allié consentant vers son appui proche et sûr. | Récupère des objets tombés au bord de l'eau ; coasse après un saut réussi. |
+| 09 · Cheval (`horse`) | **Attaquant mobile.** Charge une cible isolée, frappe puis prend de la distance pour revenir. | **Percée** : traverse une courte ligne et heurte le premier ennemi, en conservant une sortie libre. | **Ouvrir la route** : passe devant le joueur et lui donne un bref sillage de déplacement au sol. | Accompagne les trajets entre lieux connus ; hennit en reconnaissant le point de départ. |
+| 10 · Lama (`llama`) | **Tireur défensif.** Garde sa distance et crache sur la cible désignée. | **Crachat d'arrêt** : un crachat appuyé interrompt une préparation légère et repousse peu. | **Volée** : répartit trois crachats entre les ennemis proches, avec moins d'effet par cible. | Aide à tenir une petite caravane regroupée ; relève la tête après un tir réussi. |
+| 11 · Mule (`mule`) | **Messagère mobile.** Circule derrière les alliés, avec une ruade de défense si son trajet est coupé. | **Livraison urgente** : apporte directement l'objet préparé au bénéficiaire désigné. | **Relais** : reprend chez un allié consentant un lot qu'il lui confie et le ramène au propriétaire. | Transporte un lot entre deux personnes ou stockages proches autorisés ; annonce son arrivée d'un bref cri. |
+| 12 · Cochon (`pig`) | **Débusqueur.** Flanque la cible et la bouscule du groin lorsqu'elle se cache derrière un petit obstacle. | **Truffe au sol** : suit l'odeur récente d'une cible observée et révèle sa dernière direction à proximité. | **Bousculade** : pousse de côté une petite cible pour l'exposer à la ligne de tir du joueur. | Cherche des plantes déjà connues ; s'assoit près du repas sans le consommer. |
+| 13 · Lapin (`rabbit`) | **Leurre agile.** Harcèle par de petits coups puis bondit hors de portée de mêlée. | **Fausse piste** : attire un monstre ordinaire vers le point proche choisi par le joueur, avec un trajet d'évasion. | **Bond complice** : rejoint le joueur puis accompagne un court franchissement dirigé, une fois par vol. | Aide à franchir un petit écart ; bondit de joie lorsque le joueur le rejoint. |
+| 14 · Saumon (`salmon`) | **Intercepteur linéaire.** Prend son élan et heurte une cible dans un passage court. | **Contre-courant** : un jet linéaire repousse les ennemis qui avancent face à lui. | **Sillage** : trace un passage bref que les alliés peuvent suivre pour se déplacer ; plus libre dans l'eau. | Guide une traversée aquatique locale ; saute à la sortie du courant. |
+| 15 · Mouton (`sheep`) | **Amortisseur.** Reste près d'un allié et absorbe une part limitée d'un contact avec sa toison, puis doit récupérer. | **Cocon de laine** : se blottit contre un bénéficiaire pour amortir le prochain impact, au prix de sa garde. | **Coussin** : utilise une laine fournie pour préparer une petite réception temporaire au sol. | Sécurise une réception de chantier ; se secoue après avoir protégé quelqu'un. |
+| 16 · Squelette (`skeleton`) | **Archer précis.** Tire des flèches fournies, depuis un angle dégagé, et recule si un ennemi l'approche. | **Tir visé** : prépare un tir précis sur une cible, annoncé et interrompu si elle casse la ligne de vue. | **Tir de couverture** : tire pour gêner brièvement la progression d'un monstre dans un passage. | Montre si sa ligne de tir est dégagée ; redresse sa posture après une bonne flèche. |
+| 17 · Slime (`slime`) | **Contrôle élastique.** Bondit contre les ennemis proches et les écarte légèrement. | **Rebond** : se comprime puis projette une petite cible dans une direction annoncée. | **Tremplin** : se place sur un appui et fournit un rebond au prochain allié consentant ; capacité finie. | Sert d'appui de chantier temporaire ; imite les sauts du joueur au repos. |
+| 18 · Araignée (`spider`) | **Attaquante verticale.** Grimpe les surfaces pour contourner une défense et mord au contact. | **Fil tendu** : pose entre deux appuis proches un fil temporaire qui freine le premier ennemi le traversant. | **Descente surprise** : depuis un vrai appui en hauteur, se laisse tomber sur la cible désignée. | Repère un parcours grimpable proche ; attend suspendue pendant les pauses. |
+| 19 · Calmar (`squid`) | **Protection de retraite.** Reste entre le joueur et ses poursuivants, donnant de petits coups de tentacule. | **Écran d'encre** : masque brièvement une zone et gêne le suivi visuel des monstres ordinaires. | **Jet arrière** : se propulse et repousse un poursuivant proche pour ouvrir une retraite. | Aide à repérer un objet dans l'eau proche ; s'étale calmement dans son enveloppe au repos. |
+| 20 · Têtard (`tadpole`) | **Guetteur de secours.** Reste à couvert et signale l'allié proche qui manque d'air ou n'a plus de sortie dégagée. | **Issue !** : rejoint et indique un petit passage local praticable pour la retraite du duo. | **Bulle de garde** : apporte un écran d'eau qui absorbe une petite attaque frontale puis éclate. | Guide vers une berge accessible ; revient vérifier que le joueur le suit. |
+| 21 · Poisson tropical (`tropical_fish`) | **Marqueur mobile.** Tourne autour d'une cible désignée à distance et maintient son repère tant qu'il la voit. | **Éclat coloré** : marque les ennemis visibles d'une petite zone pour coordonner les tirs. | **Fausse cible** : laisse un reflet coloré fragile qui attire brièvement un monstre ordinaire. | Pose un repère visuel local pour une expédition ; montre ses couleurs en retrouvant son propriétaire. |
+| 22 · Zombie (`zombie`) | **Combattant d'endurance.** Avance lentement, frappe régulièrement et garde sa cible au contact. | **S'accrocher** : retient brièvement une petite cible avec ses bras, en restant lui-même exposé. | **Second souffle** : recule auprès du joueur et consomme une nourriture fournie pour récupérer lentement sa propre vie. | Pousse un objet tombé hors d'un coin accessible ; lève les bras pour demander une caresse. |
+
+### Peu communs — 22
+
+| Nº · Familier | Rôle et initiative autonome | G · Signature | Variante à apprendre | Vie commune : service et petit geste |
+| --- | --- | --- | --- | --- |
+| 23 · Tatou (`armadillo`) | **Intercepteur compact.** Suit l'allié protégé et se met en boule sur le trajet d'un petit projectile ; sa garde s'épuise. | **Bouclier roulant** : roule jusqu'au point désigné et y absorbe quelques impacts frontaux, immobile. | **Ricochet** : roule contre une cible puis rebondit vers le joueur ; faible frappe de dégagement. | Inspecte un passage étroit praticable ; se déroule prudemment après le danger. |
+| 24 · Abeille (`bee`) | **Harcèlement aérien.** Pique puis se retire ; poison bref limité, sans cumul de doses. | **Pollen** : marque une cible visible d'un nuage qui permet de suivre son déplacement proche. | **Dard précis** : prépare une piqûre unique plus engagée, puis reste brièvement vulnérable au retour. | Replante une récolte désignée avec les graines du joueur ; se pose sur une fleur pendant les pauses. |
+| 25 · Dromadaire (`camel`) | **Franchisseur.** Frappe au contact et passe les petits obstacles pour rejoindre les alliés. | **Enjambée** : déplace brièvement un allié consentant par-dessus un obstacle bas, vers un appui visible. | **Rempart haut** : se campe devant un allié et encaisse une part limitée des coups venant de face. | Aide à franchir un fossé court ; s'agenouille près du lieu de repos. |
+| 26 · Araignée venimeuse (`cave_spider`) | **Assaillante de recoin.** Passe par les petits espaces et applique un poison bref par morsure. | **Morsure tenace** : reste un instant accrochée à une cible pour délivrer une seule dose, exposée aux coups. | **Nid de fils** : crée une petite zone temporaire qui freine les premiers monstres la traversant. | Inspecte un interstice proche sans casser la paroi ; se recroqueville après une caresse. |
+| 27 · Golem de cuivre (`copper_golem`) | **Auxiliaire mécanique.** Reste près des dispositifs du joueur et repousse d'un coup de bras un ennemi approchant. | **Déclencheur** : rejoint puis actionne une commande redstone autorisée et désignée, permettant d'utiliser un piège préparé. | **Parade de cuivre** : intercepte un projectile pour l'allié choisi, même en terrain sans installation. | Trie un lot dans des rangements proches appris ; tapote un coffre une fois sa livraison terminée. |
+| 28 · Dauphin (`dolphin`) | **Escorte rapide.** Frappe en passant et revient se placer entre un poursuivant et l'allié escorté. | **Sillage d'évacuation** : entraîne un allié consentant sur un court trajet dégagé ; nage dans l'eau, glissade brève au sol. | **Sauvetage** : rejoint un allié dans l'eau et l'accompagne vers une surface libre proche. | Guide les nageurs entre deux points locaux ; joue avec un objet qu'on lui lance. |
+| 29 · Noyé (`drowned`) | **Combattant d'ancrage.** Reste sur son appui, frappe au contact et résiste aux petits déplacements. | **Agrippe** : tient brièvement une cible proche pour empêcher qu'elle s'éloigne, tout en restant attaquable. | **Ancrage partagé** : protège un petit point des poussées et courants pour les alliés qui y prennent position. | Stabilise une courte séance de travail sous l'eau ; examine les objets ramenés du fond. |
+| 30 · Poulpe luisant (`glow_squid`) | **Éclaireur de cible.** Éclaire les silhouettes déjà visibles près du duo et tient ses distances. | **Encre lumineuse** : touche une cible d'une marque brillante persistante quelques instants, même si elle se cache à proximité. | **Signal de regroupement** : pose un halo au sol autour de lui qui indique un point sûr choisi, sans soin ni invulnérabilité. | Balise un chantier sombre ; pulse doucement lorsque le joueur revient. |
+| 31 · Chèvre (`goat`) | **Brise-position.** Cherche un angle latéral et donne des coups de corne brefs. | **Coup de boutoir** : charge un ennemi pour le déloger de son appui ; préparation visible et collision réelle. | **Contre-charge** : attend une attaque de mêlée annoncée et percute l'assaillant au moment de son entrée. | Repère un chemin de petits appuis ; gratte le sol avant de bondir. |
+| 32 · Zombie momifié (`husk`) | **Pression de proximité.** Avance avec constance et frappe les cibles qui restent dans sa portée. | **Poussière** : soulève un écran au sol qui gêne la poursuite des monstres ordinaires et se déplace avec le vent. | **Poigne sèche** : une frappe préparée applique une brève Faiblesse, puis le laisse récupérer. | Signale un abri proche déjà aperçu ; s'époussette à la fin d'une traversée. |
+| 33 · Ocelot (`ocelot`) | **Chasseur isolant.** Approche une cible par un flanc dégagé et revient après une griffe. | **Embuscade** : bondit depuis un couvert réel sur une cible qui regarde ailleurs ; engagement risqué. | **Pas de côté** : esquive l'attaque annoncée d'une cible puis riposte si elle reste à portée. | Suit la piste locale d'un animal observé ; s'approche lentement lorsqu'on s'accroupit. |
+| 34 · Panda (`panda`) | **Garde de zone proche.** Frappe lentement les ennemis autour de lui, avec une bonne tenue au sol. | **Roulade** : roule dans un passage, écarte de petits ennemis puis doit se relever. | **Assise solide** : s'assoit devant un allié et forme une garde courte, robuste mais immobile. | Aide à pousser un lot tombé vers un point de collecte ; s'assoit pour regarder le joueur manger. |
+| 35 · Perroquet (`parrot`) | **Sentinelle sonore.** Signale les préparations des ennemis proches avec un cri dirigé vers leur position. | **Imitation** : place un leurre sonore à un point visible pour détourner un monstre ordinaire. | **Alerte ciblée** : surveille une cible et annonce ses préparations pendant une courte fenêtre. | Reconnaît certains sons de dangers déjà rencontrés ; danse près d'une musique. |
+| 36 · Poisson-globe (`pufferfish`) | **Défense de contact.** Se place près d'un allié et pique les ennemis qui le touchent lorsqu'il est gonflé. | **Hérisson** : se gonfle sur place pour fermer un petit passage par des représailles de poison limitées. | **Gonflement brusque** : une expansion unique repousse les petits ennemis proches, puis le laisse dégonflé. | Signale les créatures qui s'approchent dans l'eau ; se gonfle de surprise devant un objet nouveau. |
+| 37 · Ours polaire (`polar_bear`) | **Protecteur d'un allié.** Se maintient entre la personne protégée et son dernier agresseur, frappant lourdement à courte portée. | **Couvre-moi** : accompagne la retraite d'un allié en interceptant une quantité limitée de dégâts de mêlée. | **Coup de patte** : large frappe de dégagement devant lui, avec une récupération exposée. | Escorte les trajets dans les reliefs enneigés ; s'allonge tout près du compagnon protégé. |
+| 38 · Golem de neige (`snow_golem`) | **Tireur de gêne.** Lance des boules de neige fournies : faibles impacts réguliers et gêne brève des monstres ordinaires. | **Bourrasque neigeuse** : consomme plusieurs boules pour couvrir un passage de tirs successifs. | **Refroidissement** : projette sa neige sur une petite zone pour éteindre les créatures en feu ; pas de soin. | Éteint de petits départs de feu explicitement visés avec le stock fourni ; penche sa tête après le dernier lancer. |
+| 39 · Arpenteur (`strider`) | **Escorte thermique.** Reste près du joueur sur terrain chaud et frappe à courte portée sans brûler le sol. | **Passage brûlant** : guide un allié consentant sur une courte surface de lave vers un appui sûr. | **Évacuation** : rejoint le point sûr désigné et aide un allié proche à quitter la zone en feu, même sans lave. | Sécurise une courte traversée au-dessus de lave ; tremblote près du froid puis se détend à la chaleur. |
+| 40 · Tortue (`turtle`) | **Garde directionnelle.** Avance lentement devant l'allié protégé et intercepte avec sa carapace une part limitée des attaques frontales. | **Carapace avancée** : maintient un écran mobile frontal pendant une retraite ou une approche. | **Verrou** : se campe dans un passage et gagne en garde au prix de toute mobilité. | Sert de repère stable pour une plongée ; enfouit légèrement ses pattes sur son lieu de repos. |
+| 41 · Villageois (`villager`) | **Intendant de combat.** Reste à couvert et présente les fournitures préparées aux alliés qui se rapprochent. | **Abri de fortune** : déploie un bouclier fourni en garde fixe, protégeant brièvement un angle pendant un soin manuel. | **Distribution** : remet à plusieurs alliés proches une portion de nourriture fournie par personne ; les joueurs choisissent de la manger. | Mémorise les offres consultées et aide les échanges de proximité ; acquiesce après une tâche réussie. |
+| 42 · Loup (`wolf`) | **Partenaire de mêlée.** Contourne l'ennemi engagé par le joueur, mord et reste attentif aux attaques sur son maître. | **Interception** : bondit sur une menace approchante et interrompt une préparation légère. | **Poursuite** : rejoint rapidement une cible qui s'éloigne puis la frappe, au risque de quitter sa protection. | Suit la piste d'une créature observée ; vient se frotter au joueur après un combat difficile. |
+| 43 · Embourbé (`bogged`) | **Archer d'usure.** Tire des flèches fournies depuis un couvert ; se déplace peu entre deux tirs. | **Flèche empoisonnée** : prépare une dose de poison sur une seule flèche ; l'immunité et les budgets d'effets s'appliquent. | **Tir rasant** : projette avec une flèche une petite éclaboussure de boue temporaire qui ralentit un passage. | Reconnaît les appuis dans un marais proche ; secoue ses champignons après la pluie. |
+| 44 · Squelette desséché (`parched`) | **Archer d'affaiblissement.** Tire des flèches fournies et privilégie la cible la plus proche de l'allié protégé. | **Trait épuisant** : une flèche préparée inflige une brève Faiblesse à une cible. | **Réserve de tir** : attend derrière l'allié et tire sur son assaillant à la première ouverture, puis se replie. | Observe les approches dégagées autour d'un camp ; se range dans l'ombre auprès du joueur. |
+
+### Rares — 25
+
+| Nº · Familier | Rôle et initiative autonome | G · Signature | Variante à apprendre | Vie commune : service et petit geste |
+| --- | --- | --- | --- | --- |
+| 45 · Allay (`allay`) | **Secours aérien.** Récupère un objet préparé ou correspondant à la consigne et le rapporte en évitant la mêlée ; fragile et limité à un petit lot. | **Livraison par les airs** : apporte une potion ou un aliment réel à un allié par un trajet aérien court, puis revient. | **Récupération** : va chercher un lot d'objets tombés dans une zone dangereuse désignée et les ramène. | Ramasse les objets du type montré ; danse au retour d'une livraison. |
+| 46 · Axolotl (`axolotl`) | **Combattant de secours.** Mord une cible proche puis se retire auprès d'un allié blessé. | **Faire le mort** : tombe immobile, poussant un monstre ordinaire à changer de cible ; reste vulnérable aux attaques de zone. | **Convalescence** : consomme une nourriture fournie et accompagne une récupération lente d'un compagnon allié au repos, interrompue par les coups. | Escorte les plongées et se pose près d'un blessé ; agite ses branchies après une caresse. |
+| 47 · Blaze (`blaze`) | **Tireur incendiaire.** Tire une petite flamme annoncée à cadence modérée, puis se décale. | **Salve** : prépare trois petites boules de feu vers une cible, avec des fenêtres d'esquive entre les tirs. | **Rideau de chaleur** : maintient devant lui un cône court qui brûle les ennemis traversant son approche. | Allume un foyer désigné par un ordre de travail explicite ; fait tourner ses bâtons lorsqu'il est content. |
+| 48 · Breeze (`breeze`) | **Manipulateur de trajectoires.** Bondit de côté et frappe d'une petite poussée à distance. | **Rafale** : déplace les créatures légères et les nuages mobiles dans la direction visée, sans renouveler leur contenu. | **Déviation** : prépare une rafale courte qui détourne un projectile compatible, une seule fois et sans augmenter ses dégâts. | Déplace des lots au sol et aide un court saut consenti ; tourne sur lui-même après une combinaison réussie. |
+| 49 · Dromadaire momifié (`camel_husk`) | **Escorte de percée.** Avance droit au contact et garde son cap malgré les petites poussées. | **Traversée poussiéreuse** : parcourt une ligne courte et laisse derrière lui un écran mobile qui couvre la progression des alliés. | **Extraction** : rejoint un allié consentant puis le guide vers un appui proche hors de la mêlée. | Ouvre un trajet dans les reliefs secs ; secoue une poussière visuelle après le voyage. |
+| 50 · Creeper (`creeper`) | **Menace de proximité.** S'approche prudemment et bouscule d'un petit coup de tête ; attend un ordre pour s'amorcer. | **Détonation** : annonce sa mèche, explose au point atteint, puis recule épuisé ; dégâts de zone, terrain protégé dans cette proposition. | **Embuscade** : attend sur un point désigné ; le joueur déclenche sa mèche lorsque des ennemis sont bien placés. | Signale les amorçages proches ; un ordre de démolition distinct pourrait réutiliser son explosion avec droits et coût. Se détend après la fin de sa mèche. |
+| 51 · Endermite (`endermite`) | **Perturbatrice de courte portée.** Effectue de petits déplacements instantanés entre deux morsures, avec récupération visible. | **Interversion** : échange sa place avec celle de son propriétaire consentant à très courte portée, si les deux arrivées sont sûres et autorisées. | **Détour** : passe de l'autre côté d'un petit obstacle autorisé pour attaquer une cible, puis revient par son trajet possible. | Inspecte une arrivée de l'autre côté d'une mince paroi autorisée ; se rapproche par de petits bonds quand on l'appelle. |
+| 52 · Gardien (`guardian`) | **Tireur à verrouillage.** Suit une cible du regard et lance des impulsions faibles en gardant sa ligne de vue. | **Rayon concentré** : canalise un tir puissant sur une cible ; le rayon visible annonce l'impact, et un obstacle ou un coup peut l'interrompre. | **Balayage** : fait pivoter lentement un rayon moins puissant sur un arc court pour contrôler une approche. | Inspecte une ligne sous l'eau sans révéler les blocs cachés ; replie ses pointes au repos. |
+| 53 · Hoglin (`hoglin`) | **Percuteur frontal.** Frappe lourdement avec ses défenses et pousse légèrement la cible qui lui fait face. | **Encornement** : une courte charge soulève un monstre ordinaire et le rejette derrière son point d'impact. | **Barrage** : reste sur une ligne et repousse les premiers ennemis qui tentent de la franchir. | Dégage un chemin parmi des objets au sol sans casser le terrain ; gratte le sol avant le départ. |
+| 54 · Cube de magma (`magma_cube`) | **Contrôle des appuis.** Bondit au contact et délivre un bref choc chaud à sa réception. | **Réception brûlante** : saute sur un point visible et produit une petite zone chaude temporaire qui gêne l'occupation de l'appui. | **Cœur chaud** : reste au sol et protège son voisinage par des impulsions courtes, au prix de sa mobilité. | Allume un foyer sur ordre explicite ; rougeoit doucement quand on se repose près de lui. |
+| 55 · Champimeuh (`mooshroom`) | **Soutien de repas.** Reste près des alliés et repousse d'un coup de tête les ennemis qui approchent du groupe. | **Soupe commune** : distribue les portions de soupe réellement préparées aux alliés proches ; faim et effets de l'aliment s'appliquent normalement. | **Voile de spores** : forme autour d'elle un nuage mobile qui gêne la vue des monstres ordinaires pendant une pause de soin. | Sert les repas préparés ; laisse tomber quelques spores visuelles en se secouant. |
+| 56 · Nautile (`nautilus`) | **Point de respiration.** Suit l'allié protégé et garde une courte impulsion d'eau pour écarter un assaillant. | **Cloche d'air** : maintient une petite poche d'air pour plusieurs bénéficiaires dans l'eau, tant que leur budget le permet. | **Coquille partagée** : crée un écran circulaire de faible capacité autour de lui, utile aussi sur terre. | Prépare une pause de plongée ; rentre un instant dans sa coquille après une caresse. |
+| 57 · Phantom (`phantom`) | **Attaquant en piqué.** Décrit une orbite basse, plonge sur une cible puis doit reprendre son élan. | **Piqué** : un long passage annoncé frappe une cible exposée et se termine par une remontée vulnérable. | **Aile de secours** : rejoint un allié en chute et accompagne une descente contrôlée vers un appui accessible. | Escorte les passages en hauteur ; vient se poser à proximité quand le joueur se couche. |
+| 58 · Piglin (`piglin`) | **Duelliste adaptable.** Utilise l'arme réelle confiée pour cette mission, mêlée ou arbalète, en privilégiant une cible isolée. | **Feinte armée** : simule une approche puis frappe ou tire lorsque la cible se découvre ; consomme sa munition si nécessaire. | **Repli couvert** : recule vers le joueur en maintenant une menace de tir, ou en parant avec son arme de mêlée. | Assiste les trocs avec les ressources du joueur ; examine un lingot montré sans le voler. |
+| 59 · Pillard (`pillager`) | **Arbalétrier de position.** Recharge à couvert et tire des carreaux fournis depuis un angle choisi. | **Couverture** : dépense plusieurs munitions pour battre un passage par des tirs successifs. | **Tir croisé** : rejoint un second angle visible, puis attend le signal pour tirer sur la cible du joueur. | Aide à préparer une position de tir et annonce quand il est prêt ; vérifie son arbalète pendant les pauses. |
+| 60 · Poisson d'argent (`silverfish`) | **Assaillant des interstices.** Se faufile par les passages disponibles au ras du sol et mord les pieds d'une cible. | **Accroche** : gêne brièvement le déplacement d'une petite cible en restant au contact, exposé aux coups. | **Sonde de paroi** : détecte une cavité immédiatement derrière une face proche et indique un autre angle d'approche possible. | Inspecte les infestations et cavités locales ; vient se cacher près des bottes du joueur. |
+| 61 · Cube de soufre (`sulfur_cube`) | **Contrôle corrosif.** Bondit à courte portée et projette une goutte acide intrinsèque sur sa cible. | **Flaque acide** : couvre un petit appui d'une zone de dégâts temporaires ; aucun bloc ni objet n'est dissous. | **Éclaboussure** : remplace la zone persistante par un cône court de gouttelettes, plus mobile mais moins durable. | Aide à nettoyer l'oxydation d'un cuivre désigné avec la vraie hache et les droits du joueur ; frémit et fait des bulles visuelles au repos. |
+| 62 · Lama de marchand (`trader_llama`) | **Garde de convoi.** Reste près de la personne ou de la monture escortée et crache sur ses poursuivants. | **Cercle de garde** : se déplace autour du bénéficiaire et répartit ses crachats entre les menaces qui approchent. | **Rassemblement** : rappelle vers lui les montures possédées et compagnons alliés consentants en désengagement, par des trajets praticables. | Regroupe une caravane ; compte visuellement ses compagnons de route en tournant la tête. |
+| 63 · Marchand ambulant (`wandering_trader`) | **Auxiliaire d'évacuation.** Reste mobile entre les couverts et indique une position locale de repli. | **Échappée discrète** : utilise une vraie potion d'invisibilité préparée sur lui-même et mène le duo vers le repli ; le joueur conserve sa visibilité normale. | **Cache de secours** : apporte puis pose un lot réel de fournitures au repli choisi pour le récupérer après la fuite. | Partage les lieux d'approvisionnement déjà rencontrés ; déplie un petit geste de présentation lorsqu'il arrive. |
+| 64 · Cheval-zombie (`zombie_horse`) | **Escorte tenace.** Avance au contact, frappe d'une ruade et résiste aux petites poussées pendant son déplacement. | **Route obstinée** : accompagne un allié consentant sur un court trajet au sol en l'aidant à résister au recul ; conserve murs et dangers. | **Ruade de retour** : revient vers son propriétaire et repousse le poursuivant qui lui coupe l'accès. | Escorte les trajets accidentés, de jour comme de nuit ; attend sans bouger au lieu convenu. |
+| 65 · Zombie-villageois (`zombie_villager`) | **Secouriste exposé.** Reste derrière le duo, repousse une menace au contact et rejoint un compagnon allié blessé sur ordre. | **Veille** : consomme une nourriture fournie et canalise un soin lent sur un compagnon allié ; les dégâts interrompent. | **Relève** : aide un compagnon K.-O. à récupérer plus vite lors d'un vrai repos hors combat, avec une ration ; aucun retour immédiat en plein combat. | Accompagne une guérison native engagée avec ses ingrédients ; reste assis à côté du convalescent. |
+| 66 · Piglin zombifié (`zombified_piglin`) | **Vengeur protecteur.** Garde un allié et riposte au contact contre son dernier agresseur, sans appeler de mobs sauvages. | **Désignation** : marque cet agresseur pour le groupe et se place entre lui et le bénéficiaire. | **Riposte commune** : attend un coup reçu par l'allié puis frappe une fois si l'agresseur est à portée ; pas de multiplication des dégâts alliés. | Alerte d'une colère proche observée ; se calme et baisse son arme au retour du joueur. |
+| 67 · Vagabond (`stray`) | **Archer de poursuite.** Tire des flèches fournies et cherche un angle pour gêner les ennemis qui s'éloignent. | **Flèche froide** : une flèche préparée ralentit brièvement une cible, sans immobilisation en chaîne. | **Ligne de givre** : frappe avec une munition un petit passage au sol, dont le givre visuel temporaire gêne les traversées ennemies. | Indique les appuis glissants proches ; laisse un souffle froid discret en attendant. |
+| 68 · Vindicateur (`vindicator`) | **Frappeur d'ouverture.** Attaque à la hache au contact, avec une préparation plus lisible qu'un loup. | **Fendre** : une frappe lourde balaie un petit arc et atteint plusieurs ennemis serrés. | **Brise-garde** : vise une garde frontale pour en réduire la capacité, avec un long geste esquivable. | Travaille une coupe de bois désignée avec le vrai outil et les droits du joueur ; plante symboliquement sa hache au repos. |
+| 69 · Zoglin (`zoglin`) | **Percuteur de groupe.** Enchaîne de courtes frappes devant lui et peine à tourner vite. | **Charge continue** : traverse un court groupe d'ennemis ordinaires en ligne droite, puis reste essoufflé. | **Retournement** : pivote lourdement et balaie les ennemis proches qui l'ont entouré. | Pousse un lot encombrant au sol sur ordre ; tourne sur place avant de s'allonger. |
+
+### Extraordinaires — 16
+
+| Nº · Familier | Rôle et initiative autonome | G · Signature | Variante à apprendre | Vie commune : service et petit geste |
+| --- | --- | --- | --- | --- |
+| 70 · Creaking (`creaking`) | **Gardien de regard.** Se déplace et frappe quand sa cible regarde ailleurs ; observé, il s'immobilise et durcit sa garde limitée. | **Enracinement** : même observé, projette des racines temporaires autour de lui qui retiennent brièvement les monstres ordinaires. | **Sentinelle** : reste au point choisi et signale les passages ; reprend la garde de proximité lorsqu'un ennemi arrive. | Veille près d'une entrée et signale une intrusion observable ; incline lentement sa tête vers son propriétaire. |
+| 71 · Grand gardien (`elder_guardian`) | **Ancre de groupe.** Tient sa position et lance de lentes impulsions défensives sur les ennemis proches. | **Sanctuaire** : maintient autour de lui une zone de garde partagée, de capacité finie, pendant que les alliés y restent. | **Onde de recul** : libère une impulsion large qui éloigne les petits ennemis ; abandonne sa garde pour l'émettre. | Sert de poste de rassemblement sous l'eau ; replie ses pointes lorsque tous sont revenus. |
+| 72 · Enderman (`enderman`) | **Duelliste de placement.** Frappe au contact puis change brièvement d'angle entre des arrivées libres autorisées. | **Extraction** : rejoint un allié consentant et le ramène vers le point de repli préparé, à portée locale. | **Passage** : maintient un court passage entre deux appuis autorisés pour un nombre limité d'alliés consentants. | Pose un bloc fourni à portée de travail et selon les droits acquis ; présente parfois son bloc au joueur. |
+| 73 · Évocateur (`evoker`) | **Mage de sol.** Fait surgir un croc faible et annoncé sous une cible proche, en restant derrière le duo. | **Mâchoires** : une ligne de crocs parcourt le sol jusqu'à un obstacle ; forte préparation, dégâts plafonnés par cible. | **Cercle de crocs** : prépare un cercle autour de sa position pour couvrir un allié encerclé, puis reste vulnérable. | Actionne un mécanisme de sol explicitement désigné et compatible, à portée normale ; referme les mains à la fin du rituel. |
+| 74 · Ghast (`ghast`) | **Bombardier lent.** Cherche une ligne aérienne dégagée et lance des projectiles lents, annoncés et renvoyables. | **Boule explosive** : prépare un projectile de zone puissant ; la cible a le temps de l'éviter ou de le renvoyer. | **Tir de barrage** : répartit quelques impacts plus faibles dans une zone annoncée pour en chasser les occupants. | Une démolition distante pourrait être un ordre de travail distinct avec droits et coût ; descend près du joueur pour recevoir une caresse. |
+| 75 · Ghast joyeux (`happy_ghast`) | **Protecteur aérien.** Se place au-dessus d'un allié et intercepte une quantité limitée d'attaques plongeantes ou de projectiles. | **Hissage** : soulève brièvement un allié consentant vers un appui proche, en restant exposé pendant le transport. | **Palier** : reste sur un point et offre un bref appui suspendu partagé, de capacité finie. | Aide à une mise en place en hauteur ; s'abaisse spontanément lors d'une pause pour se laisser approcher. |
+| 76 · Golem de fer (`iron_golem`) | **Défenseur de première ligne.** S'interpose contre une menace proche et frappe lentement au contact. | **Interposition** : traverse quelques pas pour encaisser avec sa vie une attaque destinée à l'allié choisi. | **Uppercut** : prépare une frappe qui soulève une cible ordinaire et la sépare brièvement du groupe. | Aide à organiser un point de garde ou de collecte ; offre une fleur visuelle après une action protectrice. |
+| 77 · Piglin barbare (`piglin_brute`) | **Duelliste de parade.** Reste face à sa cible et alterne garde courte et frappe lourde. | **Parade-riposte** : attend un impact frontal dans une fenêtre brève puis répond s'il l'a réellement bloqué. | **Défi** : attire l'attention d'un monstre ordinaire proche et le combat sur place ; les autres ennemis peuvent le contourner. | Garde un coffre ou une entrée sans contrôler les droits d'accès ; s'assoit à côté du joueur en gardant son arme près de lui. |
+| 78 · Ravageur (`ravager`) | **Briseur de formation.** Avance lentement et frappe en arc court, avec des préparations très lisibles. | **Bélier** : charge jusqu'au premier obstacle ou ennemi lourd, puis délivre un choc frontal ; récupération longue. | **Piétinement** : reste sur place et produit une onde courte qui écarte les petits ennemis autour de lui. | Dégage une zone d'objets tombés vers un point de travail, sans piétiner les cultures ; souffle doucement au repos. |
+| 79 · Shulker (`shulker`) | **Tourelle repliable.** Se fixe sur une surface autorisée, s'ouvre pour un petit tir et referme sa coque entre les attaques. | **Balle ascendante** : tire un projectile guidé destructible qui provoque une Lévitation brève, avec résistance aux contrôles successifs. | **Coque** : renonce au tir pour former une garde fixe de capacité limitée autour de son point d'ancrage. | Marque un appui de chantier et montre son orientation ; ouvre à peine sa coque pour saluer le joueur. |
+| 80 · Cheval-squelette (`skeleton_horse`) | **Escorte amphibie.** Frappe puis se déplace avec aisance entre rive et eau peu profonde. | **Traversée spectrale** : guide un allié consentant sur une courte surface d'eau vers une rive, sans permettre l'arrêt au milieu. | **Relais de rive** : attend à un appui accessible et aide à extraire un allié proche de l'eau ou d'une mêlée sur terre. | Accompagne les itinéraires mêlant berges et eau ; fait résonner brièvement ses sabots au retour. |
+| 81 · Renifleur (`sniffer`) | **Lecteur du terrain.** Signale les traces et déplacements proches au sol ; repousse du museau une cible qui l'approche. | **Labour défensif** : projette terre et racines visuelles sur un arc court, freinant les approches sans modifier le terrain sauvegardé. | **Flair** : localise brièvement une menace proche grâce à des traces récentes ; distingue position attestée et dernière piste. | Recherche les plantes et sites d'archéologie déjà connus ; se roule dans la végétation après une découverte. |
+| 82 · Vex (`vex`) | **Assaillant de traversée.** Effectue de courts passages au contact ; sa traversée autorisée des parois exige une récupération avant de recommencer. | **Percée spectrale** : traverse une mince paroi autorisée et frappe une cible déjà désignée, puis doit ressortir dans un espace libre. | **Retour-éclair** : abandonne l'engagement et revient vers le joueur en frappant une seule cible qui coupe sa trajectoire. | Exécute un geste de construction ou de minage avec les outils, ressources et droits acquis ; tourne autour de la main qui le caresse. |
+| 83 · Sorcière (`witch`) | **Alchimiste de terrain.** Reste à couvert et distribue ou lance les potions explicitement préparées, en évitant les alliés. | **Lancer précis** : utilise une vraie potion jetable sur la cible ou le point choisi ; son contenu réel détermine l'effet. | **Brume** : transforme une dose réelle compatible en petite brume mobile, avec quantité finie et attribution conservée. | Prépare une distribution de potions déjà fabriquées ; fait tinter doucement ses flacons au repos. |
+| 84 · Wither squelette (`wither_skeleton`) | **Duelliste d'attrition.** Frappe lentement à l'épée et tient sa portée, sans Wither permanent sur chaque coup de base. | **Entaille funèbre** : une attaque annoncée applique une dose limitée de Wither à sa cible. | **Garde funèbre** : renonce à avancer pour couvrir un allié et préparer une riposte contre le premier assaillant à portée. | Aide une tâche de coupe désignée avec le vrai outil ; abaisse son arme et incline la tête après un retour. |
+| 85 · Nautile-zombie (`zombie_nautilus`) | **Extracteur ancré.** Reste proche d'un allié et résiste aux courants ; donne un choc de coquille au poursuivant. | **Remontée** : accompagne un allié consentant vers une eau libre plus haute et proche, en restant exposé au trajet. | **Amarre** : forme entre lui et un allié consentant un lien court qui aide à résister aux poussées ; se rompt si l'un quitte sa portée. | Stabilise une récupération d'objets sous l'eau ; s'immobilise contre son propriétaire après un sauvetage. |
+
+### Légendaires — 3
+
+| Nº · Familier | Rôle et initiative autonome | G · Signature | Variante à apprendre | Vie commune : service et petit geste |
+| --- | --- | --- | --- | --- |
+| 86 · Ender Dragon (`ender_dragon`) | **Contrôle aérien de zone.** Effectue de larges passages avec une frappe de contact mesurée ; tourne difficilement dans un lieu étroit. | **Souffle draconique** : prépare un souffle au sol qui crée une zone dangereuse annoncée, dont les occupants peuvent sortir. | **Ailes protectrices** : descend devant un groupe et souffle pour écarter les petites menaces et certaines brumes ; s'expose en couvrant la retraite. | Observe un trajet déjà reconnu depuis les airs, sans découverte distante gratuite ; vient poser sa tête près du joueur au repos. |
+| 87 · Warden (`warden`) | **Sentinelle des vibrations.** Localise les mouvements proches puis frappe lourdement ; une cible immobile peut lui faire perdre sa piste. | **Onde sonique** : charge une attaque dirigée vers une position récemment localisée ; avertissement clair, portée locale et interruption possible. | **Écoute profonde** : s'immobilise et partage les vibrations locales pour préparer une interception, au prix de son offensive. | Signale les pas proches et certains dangers vibrants ; se calme en reconnaissant les déplacements de son propriétaire. |
+| 88 · Wither (`wither`) | **Pression répartie.** Ses têtes surveillent plusieurs angles ; ses petits tirs se partagent un seul budget offensif total. | **Trois crânes** : prépare une salve répartie entre plusieurs cibles ou concentrée sur une seule, avec dégâts et Wither bornés. | **Moisson** : canalise un tir unique ; une part des dégâts réellement infligés restaure sa propre vie dans une limite stricte. | Aide à une démolition explicitement préparée, si cette fonction est retenue avec ses droits et coûts ; les trois têtes se tournent vers le joueur à son retour. |
+
+## 4. Distinguer les voisins
+
+| Famille proche | Différence de jeu recherchée |
+| --- | --- |
+| Chat / loup / renard / ocelot | Le chat défend la proximité ; le loup intervient sur une menace ; le renard récupère et attire ; l'ocelot exploite un angle et se retire. |
+| Âne / mule / Allay | L'âne ravitaille un point ; la mule assure des échanges au sol et se défend ; l'Allay livre un petit lot par les airs mais reste fragile. |
+| Cheval / dromadaire / cheval-zombie / cheval-squelette | Le cheval perce une ligne ; le dromadaire franchit un obstacle ; le cheval-zombie résiste aux poussées pendant une escorte ; le cheval-squelette relie les rives. |
+| Dromadaire / dromadaire momifié | Le premier franchit et couvre à l'arrêt ; le second ouvre une traversée par la poussière et l'extraction. |
+| Squelette / embourbé / desséché / vagabond / pillard | Précision sur une cible / usure par poison / réduction temporaire de sa force / gêne de poursuite / tirs successifs sur un passage. Leurs flèches et recharges sont des coûts réels. |
+| Slime / cube de magma / cube de soufre | Repositionner et rebondir / occuper un appui par la chaleur / interdire temporairement un petit espace par l'acide. |
+| Mouton / tatou / tortue / golem de fer | Amortir un impact / intercepter ponctuellement un projectile / couvrir un angle en avançant / encaisser personnellement pour un allié. |
+| Hoglin / zoglin / ravageur | Soulever une cible / traverser un petit groupe / provoquer un choc frontal large au prix d'une longue préparation. |
+| Gardien / grand gardien / Warden | Rayon visuel sur une cible / protection de position partagée / lecture des vibrations et onde annoncée. |
+| Morue / têtard / nautile / nautile-zombie | Secours d'air individuel / recherche d'issue et petite garde / pause respiratoire de groupe / extraction et ancrage. |
+| Saumon / dauphin | Le saumon impose une direction par un jet ; le dauphin escorte un bénéficiaire et revient le chercher. |
+| Calmar / poulpe luisant / poisson tropical | Masquer une retraite / conserver une marque sur une cible / coordonner plusieurs cibles visibles ou fournir un reflet-leurre. |
+| Vache / champimeuh / villageois / zombie-villageois / sorcière | Retirer des états par du lait / distribuer les soupes / organiser un ravitaillement abrité / soigner lentement un compagnon / employer l'alchimie préparée. |
+| Araignée / araignée venimeuse / poisson d'argent / Vex | Utiliser la hauteur / maintenir un contact venimeux / exploiter les interstices au sol / traverser ponctuellement une paroi autorisée. |
+| Piglin / piglin zombifié / piglin barbare / vindicateur | Adapter son arme et feinter / répondre à une agression / parer en duel / ouvrir une garde ou un groupe. |
+| Lama / lama de marchand | Le premier interrompt une cible par ses crachats ; le second organise la défense mobile d'un convoi. |
+| Creeper / Ghast / Wither | Risquer une approche pour une détonation / annoncer un bombardement distant renvoyable / répartir une pression entre plusieurs angles. |
+
+Les légendaires ajoutent une échelle ou une préparation particulière. Ils doivent
+perdre des occasions que les petits compagnons saisissent facilement : tourner
+dans un passage, rejoindre vite un allié, intervenir sans longue charge. Le même
+budget de performance doit garder un commun désirable à progression comparable.
+
+## 5. Exemples de combats à deux ou plusieurs
+
+Les combinaisons viennent des propriétés des actions et des objets. Elles
+fonctionnent aussi avec les outils du joueur ; aucun bonus secret n'exige une
+paire d'espèces nommée dans le code.
+
+- **Loup et bouclier du joueur** : le joueur tient l'ennemi de face, le loup
+ intercepte son attaque puis le joueur profite de l'ouverture.
+- **Renard et Creeper de deux joueurs** : le renard attire un petit groupe vers
+ un point choisi ; le second joueur ordonne la détonation au bon moment.
+- **Tortue et squelette** : la garde protège une approche frontale ; l'archer
+ change d'angle pour ne pas tirer dans la garde et utilise ses vraies flèches.
+- **Breeze et sorcière** : une rafale déplace une brume préparée en conservant
+ sa dose, ses bénéficiaires déjà servis et sa durée restante.
+- **Grenouille et magma** : la langue déplace un petit ennemi vers un appui
+ chaud déjà préparé ; sa résistance empêche de répéter indéfiniment l'entrave.
+- **Allay et joueur alchimiste** : l'Allay apporte la potion préparée pendant
+ que le joueur garde son arme en main ; le joueur choisit le moment de la boire.
+- **Nautile et dauphin** : le nautile prépare une pause respiratoire ; le dauphin
+ accompagne l'allié consentant jusqu'à ce point en restant attaquable.
+- **Araignée et arc du joueur** : l'araignée ferme une issue par son fil ; le
+ joueur vise le trajet encore libre. L'ennemi peut détruire ou contourner le fil.
+- **Golem de cuivre et installation** : le joueur attire une cible dans un
+ couloir ; le golem rejoint le bouton autorisé et déclenche le piège préparé.
+- **Mouton et construction** : le mouton prépare une réception avec une laine ;
+ le joueur choisit alors un saut de travail plus engagé.
+
+## 6. Premier prototype et critères de réussite
+
+### FAM-COMBAT-01 — Loup, Slime, tortue
+
+Résultat attendu : une petite rencontre PvE jouable avec engagement, protection,
+G, rappel, dégâts sur le compagnon et K.-O. Une technique par espèce suffit
+d'abord ; les variantes attendent que le trio soit agréable à diriger.
+
+Scénarios : une route dégagée, un passage étroit, un archer en hauteur et un
+duo de monstres. Essais solo puis deux joueurs, à capacités et équipement égaux.
+Les personnages Sanctuary débutants à trois cœurs font partie des essais.
+
+À vérifier en partie :
+
+1. Le joueur comprend où est son compagnon, qui il vise et quand il faut le
+ rappeler. Un ordre impossible explique son refus sans bloquer le compagnon.
+2. Le loup crée une interception, le Slime déplace une cible, la tortue couvre
+ effectivement une retraite. Le changement de compagnon change les décisions.
+3. Le joueur doit encore se déplacer, viser et se défendre. Laisser tourner le
+ compagnon seul ne devient pas la manière optimale de combattre ou de produire.
+4. Il existe une occasion identifiable de sauver son compagnon et une occasion
+ où le compagnon aide le joueur. Le K.-O. laisse une suite jouable à la sortie.
+5. Un rappel, un changement d'œuf ou une reconnexion ne restaure pas la santé,
+ ne réarme pas un pouvoir et ne duplique aucun compagnon ni objet.
+6. Les protections et déplacements respectent leurs angles, leur capacité et
+ les collisions. L'ennemi peut agir entre deux entraves successives.
+7. Un combat à deux reste lisible et évite les collisions frustrantes entre alliés.
+
+### FAM-COMBAT-02 — Vérifier les familles difficiles
+
+Ajouter ensuite **squelette, Allay, morue, Creeper et sorcière** pour éprouver
+les munitions réelles, livraisons, aquatiques à terre, explosions et consommables.
+Tester aussi les 22 communs du Hello World : chaque choix doit offrir une aide
+perceptible dans les premières rencontres, sans dépendre d'une ressource avancée.
+
+### FAM-BOND-01 — Apprentissage et mémoire
+
+Après le premier trio : variante de technique, tempérament et quelques souvenirs
+attestés. Les noms et tailles acquis continuent d'appartenir au même individu.
+Tester les parcours de soin, repos, reconnexion et prestige avant généralisation.
+
+### FAM-CATALOG-07 — Généraliser aux 88
+
+Implémenter par familles de comportements, puis vérifier chaque espèce dans au
+moins une situation favorable et une situation défavorable. Inclure les arènes
+aquatiques, le relief, les lieux étroits, le jour et la nuit. Mesurer ensuite les
+rencontres à plusieurs joueurs et les cas de foule avant de conclure à l'équilibre.
+
+## 7. Décisions restant à chiffrer ou à contractualiser
+
+- Vie, cadence des initiatives, dégâts, garde, portées, durée des entraves,
+ récupération des signatures et prix des soins. Commencer par des profils de
+ rôle comparables, puis ajuster les espèces à partir des essais.
+- Conditions exactes d'entrée et sortie de combat, récupération après K.-O.,
+ rappel sous pression et traitement d'un compagnon tombant dans le vide.
+ Un retour automatique de confort ne doit pas donner de soin ni d'invulnérabilité.
+- Obtention des œufs après le familier de départ, apprentissage des variantes,
+ lien avec les aptitudes achetées et droit de transférer un compagnon.
+- Place des anciens passifs utilitaires : une partie devient des gestes du
+ compagnon ; chaque suppression ou conservation devra être explicitement recensée.
+- Différences entre PvE, duel volontaire et autres règles PvP ; attribution des
+ éliminations et du butin, prévention du farm autonome, charge serveur mesurée.
+- Sort exact des explosions et des allumages en mode travail, avec coûts, droits,
+ aperçu et limites. Le comportement beta.044 reste inchangé en attendant ce contrat.
+- **Contrat de migration indispensable avant du code de persistance** : identité
+ de l'individu, santé, K.-O., techniques, souvenirs, échange de l'œuf, mort,
+ déconnexion, prestige et données inconnues. Préserver noms, tailles, achats et
+ identifiants existants ; aucune réécriture d'un monde n'est demandée ici.
+
+## 8. Vérification du document
+
+La livraison de ce ticket est documentaire : catalogue proposé, règles communes,
+différences entre espèces et parcours de prototype.
+
+Contrôle local effectué le 15 septembre 2026 par lecture du registre Java et du
+Markdown : **88 identifiants uniques, 88/88 conformes, numéros 01–88 continus** ;
+les groupes contiennent respectivement **22, 22, 25, 16 et 3** espèces, toutes
+dans leur rareté actuelle. Chaque fiche a son initiative, sa signature, sa
+variante et sa vie commune. Les liens locaux du document sont valides.
+
+Les versions binaires, le manifeste packwiz et les installations de jeu ne sont
+pas modifiés par ce ticket. Les commandes Gradle et les essais de gameplay
+concernent les futurs tickets de code ; ils ne valident pas une proposition écrite.
diff --git a/docs/familiar-hit-feedback-beta029.md b/docs/familiar-hit-feedback-beta029.md
new file mode 100644
index 0000000..b5365a8
--- /dev/null
+++ b/docs/familiar-hit-feedback-beta029.md
@@ -0,0 +1,46 @@
+# beta.029 — réaction des familiers aux coups
+
+## Contrat
+
+Un clic gauche sur un familier provoque son animation de blessure, son cri natif
+et un recul à l’opposé de l’attaquant. Le recul utilise la physique Minecraft
+(force 0,4), avec une petite impulsion vers le haut lorsqu’il est au sol.
+Le suivi et les déplacements autonomes cèdent la place pendant 8 ticks (0,4 s),
+puis reprennent. Le mouvement respecte les blocs ; le familier garde son retour
+automatique près du propriétaire s’il s’éloigne ou se retrouve bloqué.
+
+La voix vient du mob associé à l’œuf, avec son volume, sa catégorie sonore et
+la hauteur de sa voix bébé lorsqu’il dispose d’un bébé. Le gardien utilise son
+cri terrestre ou aquatique selon l’état du familier. Les espèces silencieuses
+et le drapeau natif Silent restent silencieux. Le son est envoyé par le serveur
+aux joueurs proches. Le modèle utilisé pour lire la voix n’est jamais ajouté
+au monde et ne reçoit aucun tick ni comportement autonome.
+
+Le délai de l’animation existante (10 ticks) évite de cumuler les impulsions et
+les cris à chaque clic. Aucun dégât, usure, enchantement offensif, butin, XP ou
+cible hostile n’est créé par ce geste. Les protections entre membres d’une même
+pile restent applicables, y compris au familier porté. La caresse reste au clic
+droit, avec ses cœurs. Aucune modification de sauvegarde ni migration.
+
+## Vérifications
+
+**38 tests serveur natifs réussis**, dont 6 nouveaux scénarios : paquets sonores
+des 88 types d’œufs, voix de bébé, variantes du
+gardien, Silent, recul dans les quatre directions, délai anti-cumul, mouvement
+réel au sol/en vol/du poisson, collisions avec les murs, reprise du suivi,
+invulnérabilité, arme intacte et protections du portage.
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=carry,familiar,movement,familiarhit`
+réussit en 3 min 42 s. Les paquets sonores réels sont capturés sur une connexion
+Minecraft de test ; le mouvement passe par les ticks communs et physiques natifs.
+Les suites familiers, mouvements et piles de joueurs passent ensemble.
+L’écoute et l’observation graphique restent à confirmer : session Mac verrouillée.
+
+Export local : [Sanctuary-beta.029.mrpack](../build/Sanctuary-beta.029.mrpack).
+Client et serveur beta.029 ensemble. Versions, ZIP, 1 256 classes compilées et
+JAR imbriqués vérifiés. Seuls FamiliarEntity et les accès aux propriétés vocales
+natives changent ; la carte, le portage, le plongeon et JEI conservent leur code.
+L’export beta.028 reste intact. Aucun déploiement effectué.
+
+SHA-256 MRpack : `b545dac3970b971f96cd56e6539451edfc265ec93c206029a97571b304c2d885`.
+Reçu : `build/familiar029-artifact.json` ; vérificateur : `build/verify-familiar029.py`.
diff --git a/docs/familiar-interactions-beta020.md b/docs/familiar-interactions-beta020.md
new file mode 100644
index 0000000..d8453fe
--- /dev/null
+++ b/docs/familiar-interactions-beta020.md
@@ -0,0 +1,128 @@
+# beta.020 — interactions, lecture des œufs et plongeon
+
+Ticket du 13 septembre 2026, branche `codex/familiar-interactions-beta020`.
+**Livraison locale beta.020 vérifiée — 13 septembre 2026.**
+
+## Demande retenue
+
+- Maj + clic droit sur un joueur : monter sur sa tête. Sur un animal : le porter,
+ puis refaire le geste pour le déposer. Le porteur est ralenti et les coups entre
+ porteur et passager sont interdits. Une seule place, sans empilement dans beta.020 ; les piles sont ajoutées par
+ [beta.027](player-stacks-beta027.md).
+- Caresser le familier ne produit que des cœurs. Le clic gauche provoque une
+ réaction visuelle de coup sans retirer de vie. Préférer son modèle bébé natif.
+ [beta.029](familiar-hit-feedback-beta029.md) ajoute le cri de l’espèce et le recul.
+- Nommer l’œuf à l’enclume ou utiliser une étiquette sur son propre familier.
+ Le nom appartient à l’œuf ; déséquiper l’œuf fait disparaître le compagnon.
+- Nom de l’œuf coloré par rareté : blanc, jaune, vert, bleu, violet.
+ L’infobulle présente les noms cryptiques du passif et de l’actif. L’aptitude
+ **Étude des familiers**, à quatre niveaux, révèle les explications.
+- Retirer l’écran Familier des aptitudes et le bouton d’activation du pouvoir.
+ Les réglages et le carnet rejoignent Habitant. Les aides alliées sont acceptées
+ par défaut ; leur réglage reste personnel.
+- Saut en sprint, puis touche S’allonger en vol : plongeon vers l’avant, une
+ impulsion par saut. L’arrivée au sol ou dans l’eau termine cette posture.
+ [beta.028](dive-until-collision-beta028.md) retire ensuite la limite de durée
+ et ajoute explicitement les collisions au plafond.
+- Taille aléatoire propre à chaque œuf, avec des familiers minuscules ou géants.
+
+## Contrat de données
+
+Les pièces jointes existantes, leurs schémas et les identifiants des œufs restent
+stables. Un nouveau reçu indépendant `sanctuary:companion_study`, schéma 1,
+conserve l’achat de l’aptitude après mort et prestige. Absent : aptitude verrouillée.
+Invalide ou version inconnue : reçu conservé, achat et révélation refusés.
+Les choix d’aide déjà enregistrés sont conservés ; l’absence d’état utilise
+le nouveau défaut accepté. Les noms utilisent le composant natif `CUSTOM_NAME`.
+
+Le portage et le plongeon sont temporaires. Aucun animal n’est converti en objet,
+dupliqué ou remplacé : son UUID, ses équipements et ses données restent ceux de
+l’entité native. Les animaux portés sont sauvegardés comme entités indépendantes,
+sans enregistrer le portage dans les données d’un joueur. Une déconnexion, mort
+ou transition de dimension termine le portage. Aucun monde personnel n’est
+modifié par les outils de développement.
+
+Client et serveur beta.020 doivent être installés ensemble. Cette livraison
+conserve les prix et recharges des 88 pouvoirs et les règles d’obtention des œufs.
+
+Le premier équipement d’un œuf sans taille ajoute au composant natif
+`minecraft:custom_data` une entrée `sanctuary:familiar_size`, composée de
+`schema: 1` et `per_mille: 120..6000`. Les autres données de l’objet restent
+intactes. Les anciens œufs reçoivent ce trait lorsqu’ils sont équipés, sans
+parcours des mondes ni conversion des inventaires existants. Un œuf équipé
+occupe une case unique ; les œufs d’une pile sont individualisés lors de leur
+équipement. Une copie de l’œuf déjà individualisé garde sa taille.
+
+Le trait voyage avec l’objet lors d’un don, d’un renommage, d’une mort ou d’un
+prestige, et survit à la sauvegarde/reconnexion. Une entrée inconnue ou invalide
+est conservée telle quelle ; le rendu utilise alors la taille ordinaire.
+
+## Pouvoirs de perception et téléportations
+
+Les détections de blocs connus, le sonar, les circuits et les marques restent
+visibles derrière les parois. Les téléportations Endermite / Enderman peuvent
+traverser les murs ; le point d’arrivée reste libre et soutenu, dans la portée
+et les zones autorisées. Le freinage d’une chute efface sa distance accumulée :
+un atterrissage après le ralentissement ne réapplique pas les dégâts antérieurs.
+Une nouvelle chute après la fin du pouvoir accumule à nouveau sa propre distance.
+
+## Gestes dans le jeu
+
+Maj + clic droit prend un animal ou monte sur un joueur. Un nouveau Maj + clic
+droit dépose le passager même avec la main vide, sans devoir viser au-dessus de
+sa propre tête. Le joueur porté relâche Maj puis appuie de nouveau pour descendre.
+Le porteur se déplace 25 % moins vite ; la réduction disparaît dès qu’il est libre.
+Les chats, loups et perroquets utilisent une pose assise ; les autres animaux
+reposent au-dessus de la tête. L’espace occupé est vérifié avant de les porter.
+
+Les noms utilisent l’œuf et l’étiquette natifs. Les modèles bébés sont choisis
+lorsqu’un modèle bébé existe dans Minecraft ; aucune intelligence hostile n’est
+appliquée au familier. La caresse garde uniquement les cœurs. Le coup léger est
+une animation, sans dégâts, enchantement offensif ni usure de l’arme.
+
+L’infobulle native affiche la rareté et deux noms d’effets. **Progression → Étude
+des familiers** coûte 4 niveaux et ajoute les explications aux infobulles. Aucun
+mod Item Description supplémentaire n’est nécessaire pour cet affichage.
+**Habitant** contient le choix d’aide alliée et le carnet. **G** reste la touche
+reconfigurable du pouvoir ; aucun bouton d’activation dans ces menus.
+
+## Tailles des familiers
+
+La taille est tirée par le serveur une seule fois, indépendamment de l’espèce et
+de la rareté. Elle multiplie le modèle réduit du familier, bébé lorsque possible :
+
+| Fréquence | Multiplicateur visuel |
+| --- | --- |
+| 90 % | ×0,65 à ×1,50 |
+| 5 % | ×0,12 à ×0,35 |
+| 5 % | ×2,50 à ×6,00 |
+
+L’infobulle indique Minuscule, Petit, Ordinaire, Grand ou Colossal après le
+premier équipement. Le nom affiché suit la hauteur du modèle et les volants
+géants s’estompent en fonction de leur encombrement visuel. Les pouvoirs,
+la vitesse et le corps utilisé pour la navigation restent identiques : la
+taille visuelle ne bloque pas le compagnon dans une porte et ne modifie pas
+l’équilibrage de son œuf. La zone d’interaction reste celle du familier mobile.
+
+## Vérifications de livraison
+
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=familiar,companions,accessories,movement,progression,cycle,mining,building,inventory,inventoryflow,sorting,tutorial` : contrôles purs et **104 tests serveur réussis**.
+- Portage joueur/animal, position sur la tête, coups interdits, ralentissement, sauvegarde native sans duplication ni véhicule-joueur enregistré, dépose et conflit avec double-Maj.
+- Achat de l’Étude facturé une fois, sauvegarde/rechargement, mort et prestige réel ; données invalides conservées et refus historique des aides maintenu.
+- Taille tirée une fois, distribution bornée, données étrangères conservées, sauvegarde et transfert de l’œuf à un autre habitant sans changement de taille.
+- Plongeon autorisé après départ en sprint, sans deuxième impulsion en vol ; sortie dans l’eau sans déverrouiller Nage. Poule freinant une chute déjà accumulée, nouvelle chute native ; sonar derrière un mur et téléportation au travers vers un espace libre.
+- Deux parcours client natifs dans de nouveaux mondes de développement, graine 42 : achat et infobulles, Habitant aux échelles 2/3/4, Maj + clic droit réel pour porter/déposer, chat bébé assis, plongeon avec une touche reconfigurée et rendu horizontal, synchronisation de taille et captures des extrêmes.
+- Les 88 modèles sont extraits à deux instants, puis aux multiplicateurs ×0,12 et ×6. Éclairage, fondu du corps et des yeux des volants, activation réelle avec G, recharge commune et échange de villageois restent validés.
+- Le MRpack et les JAR sont contrôlés : CRC, versions exactes, identité du mod embarqué et de Demeure imbriqué, FR/EN, 88 passifs/actifs et leurs noms cryptiques, absence de code de test, sources identiques à celles du build. Les anciens PNG et l’export beta.019 restent inchangés.
+
+Ces essais ne remplacent pas un équilibrage collectif des 88 paires de pouvoirs.
+Le rendu et les gestes de portage ont été observés sur un client intégré ; les
+interactions entre deux joueurs sont couvertes côté serveur, sans essai visuel
+à deux clients. L’obtention des œufs, les rangs I–III et Anomaly restent à définir.
+Aucune installation personnelle ni aucun canal de mise à jour n’a été modifié.
+
+Export : [Sanctuary-beta.020.mrpack](../build/Sanctuary-beta.020.mrpack).
+Reçu : `build/familiar020-artifact.json` ; captures : `build/familiar020-evidence/`.
+
+SHA-256 MRpack : `98d1cdb8aa5b7b5249a4e23eabea963aff394cb416d8d78f7bc1bf49450c9e78`.
+SHA-256 JAR : `3ec020eb381653f4cc53dad719338db9ec534c83f1c1a8552f0c77d42d3e739b`.
diff --git a/docs/food-panorama-beta039.md b/docs/food-panorama-beta039.md
new file mode 100644
index 0000000..fff94e1
--- /dev/null
+++ b/docs/food-panorama-beta039.md
@@ -0,0 +1,66 @@
+# beta.039 — connaissance alimentaire, panoramas et têtes numérotées
+
+Contrat établi avant modification. Branche `codex/food-knowledge-beta039`.
+Minecraft 26.3-pre-2 ; les autres travaux présents dans le dépôt sont conservés.
+
+## Connaissance alimentaire
+
+L’aptitude « Connaissance alimentaire » coûte quatre niveaux d’XP. Elle révèle
+les apports avant dégustation. Sans elle, finir de consommer un aliment apprend
+ses valeurs ; le tenir, le fabriquer, le planter, le donner à un animal ou
+interrompre la consommation ne suffit pas. Les infobulles et les aperçus du HUD
+suivent la même règle. Les réserves actuelles de faim et saturation restent
+visibles. Les recettes et le droit de manger restent libres.
+
+Un attachement serveur additif `sanctuary:food_knowledge`, schéma 1, mémorise
+les identifiants goûtés et le reçu d’achat. Il se sauvegarde avec l’XP, se copie
+à la mort et reste au New Game+. Aucun changement du schéma de progression ni
+des anciens achats. À la première lecture, les critères réussis de l’advancement
+Minecraft « Une alimentation équilibrée » peuvent attester une consommation,
+uniquement si leur prédicat correspond à un seul type d’aliment. Une donnée
+invalide est conservée et bloque l’écriture. Aucune sauvegarde personnelle
+n’est modifiée pendant les travaux.
+
+## Captures de panorama
+
+Une option de capture permet d’utiliser la touche de screenshot pour produire
+les six faces `panorama_0.png` à `panorama_5.png`, à 90° selon l’ordre Minecraft.
+Chaque capture utilise un nouveau dossier, sans écraser d’images. Le mode normal
+reste celui par défaut. Export complémentaire demandé : profondeur et masque de
+brume reconstitué, avec paramètres de projection/fog pour rendre le calcul
+explicite. Le moteur ne possède pas de texture de fog indépendante.
+
+## Têtes numérotées et skin
+
+Une nouvelle tête porte le pseudo suivi du numéro de mort, depuis le compteur
+Minecraft `deaths` : première mort « Poupoutain 1 ». L’indice est figé au décès,
+conservé au transport et au vidage. Les morts antérieures comptent ; le prestige
+ne remet pas ce compteur à zéro. Les anciennes têtes sans indice affichent leur
+nom sans inventer de numéro rétroactif. Le champ `death_number` est additif au
+marqueur de tombe schéma 1. Les informations de mort et le retrait seul restent.
+Le rendu cosmétique utilise le renderer natif des têtes, son cache de skins et
+le profil stocké sur la tête.
+
+## Validation
+
+`./gradlew check build assemblePack assembleTestPack` passe, avec **68 tests
+serveur** ciblant l’accueil, la connaissance alimentaire, les tombes, la
+progression, les inventaires, les accessoires, le cycle, les recettes et le temps
+réel. Les 14 784 comparaisons entre aperçu alimentaire et consommation native
+restent valides.
+
+Le parcours client natif vérifie les infobulles cachées/apprises, un véritable
+achat à quatre niveaux, le renderer de tête lié au cache du propriétaire,
+l’ouverture des options et deux captures successives de six faces à 256×256.
+Chaque face fournit couleur PNG, profondeur PNG 16 bits, profondeur brute
+float32 little-endian (`depth_N.f32`) et brume PNG 16 bits. `capture.json` décrit
+l’ordre des faces, les matrices, la convention de profondeur inversée et les
+paramètres du brouillard. Le test vérifie la profondeur des surfaces, la
+projection, la restauration de la caméra/fenêtre/FOV et le screenshot normal.
+
+Résolutions proposées : 256, 512, 1 024 (défaut), 2 048 pixels par face.
+Le masque reconstruit la brume du terrain à partir de sa profondeur : transparence,
+nuages, particules et shaders externes peuvent différer du rendu final. Il ne
+constitue pas un buffer de fog natif séparé. Les six vues conservent la brume
+visible. Captures et exports vérifiés dans `build/food039-evidence/release/`.
+Pas de déploiement dans une installation personnelle.
diff --git a/docs/functional-head-blocks-beta043.md b/docs/functional-head-blocks-beta043.md
new file mode 100644
index 0000000..2567182
--- /dev/null
+++ b/docs/functional-head-blocks-beta043.md
@@ -0,0 +1,80 @@
+# beta.043 — blocs portés fonctionnels
+
+Branche `codex/functional-head-blocks-beta043`.
+
+## Contrat demandé
+
+Un bloc utilisable porté dans le slot cosmétique reprend ses interactions
+Minecraft. Clic droit par un autre habitant ; clic droit sur le slot pour le
+porteur. Les inventaires sont publics. Les recettes, combustibles, distances,
+permissions et conditions environnementales restent celles du serveur.
+
+Retirer un bloc ordinaire ferme ses menus et rend son contenu, comme sa casse,
+sans supprimer les objets. Une Shulker conserve son contenu ; le coffre de
+l'End ouvre le stockage personnel du visiteur. Les contenus ne doivent jamais
+être copiés à la fois dans l'objet retiré et dans les objets rendus.
+
+Les formes cosmétiques restent sans collision. Aucun bloc temporaire ne doit
+être placé dans le terrain ni remplacer un bloc du monde.
+
+## Contrat de données additif
+
+Les trois accessoires gardent `sanctuary:accessories`, schéma 1. Un champ voisin
+`sanctuary:head_block` contient l'état natif du bloc pendant son équipement,
+avec un schéma et l'identifiant du bloc. Il n'appartient pas à l'item ordinaire :
+copier celui-ci ne copie pas un stockage. Le retrait détruit cette session
+après restitution. Les anciennes sauvegardes sans ce champ ouvrent un bloc vide.
+Les données Shulker restent dans les composants natifs de l'item.
+
+Ce ticket ne migre aucun chunk, aucune génération ni aucun identifiant existant.
+Les changements de dimension ferment les visiteurs ; les traitements reprennent
+près du porteur. Déconnexion et sauvegarde gardent le bloc équipé.
+
+## Périmètre livré
+
+Les interactions natives sont dispatchées pour 117 familles de blocs : menus de
+travail et de stockage, cuissons, infusion, livres, disques, pots, chaudrons,
+leviers, boutons, portes, lits et interfaces techniques. Les signes, bougies,
+gâteaux avec bougie et statues gardent leurs gestes Sanctuary existants.
+Le son du jukebox suit le porteur. Les éditeurs de blocs techniques restent
+réservés au mode opérateur et nécessitent de rester dans la même case pendant
+l'édition. Le coffre de l'End demeure personnel à chaque visiteur.
+
+Les lits portés sont utilisables par un autre habitant ; pour dormir dans son
+propre lit, il faut le poser. Le lit conserve les conditions natives et le point
+de réapparition à sa position lors de l'utilisation : déplacer le porteur ne
+transforme pas ce point en destination mobile.
+
+Cette adaptation ne simule pas un chunk autour de la tête : les pistons ne
+poussent pas le terrain depuis le cosmétique, les cultures ne reçoivent pas de
+random ticks et un bloc suspendu ne remplace pas les fondations d'une balise,
+un réseau de redstone ou un portail. Les conditions natives restent requises.
+
+## Icône officielle
+
+Le fichier fourni `sanctuary_icon-export.png` est repris sans modification dans
+`assets/sanctuary/icon.png` et `packwiz/icon.png`. SHA-256 :
+`6214b44ad058b2ec43d46e8d3d8c33ae0862ae616a7373d7a1b0edd5c415f8b5`.
+L'icône est déclarée dans Fabric, incluse dans les overrides du MRpack et dans
+le modèle Prism sous la clé stable `sanctuary-beta`. L'import local de Prism
+recherche l'icône du pack dans ces overrides
+([code officiel](https://github.com/PrismLauncher/PrismLauncher/blob/develop/launcher/InstanceImportTask.cpp)).
+Aucune instance personnelle n'est modifiée par l'assemblage.
+
+## Vérifications
+
+`check build assemblePack assembleTestPack` et **41 tests serveur** passent.
+22 menus natifs, 117 familles avec usage main vide, stockage public partagé,
+shift-click, retrait, mort/tombe, cuissons, sauvegarde, bouton temporisé après
+rechargement, Shulker et coffre de l’End ont été vérifiés.
+
+
+Complément de validation sous beta.044 : le parcours client natif passe après
+désactivation de VSync dans les seuls réglages du client de développement.
+Le coffre porté affiche ses trois rangées et les six rangées du joueur ;
+le shift-click est attesté côté serveur, le retrait ferme le menu, puis le
+four et l'éditeur opérateur natifs s'ouvrent. Log : build/egg044-client1.log.
+La capture du coffre a été relue. L'artefact beta.043 reste immuable.
+
+Aucun EULA accepté, aucun serveur réseau dédié démarré et aucune installation
+personnelle déployée.
diff --git a/docs/git-sync-beta060.md b/docs/git-sync-beta060.md
new file mode 100644
index 0000000..d522238
--- /dev/null
+++ b/docs/git-sync-beta060.md
@@ -0,0 +1,87 @@
+# Synchronisation Git jusqu’à beta.060
+
+Ticket du 15 septembre 2026, branche `codex/git-sync-beta060`.
+
+## Résultat attendu
+
+Enregistrer les sources, ressources et documents accumulés depuis
+`1490fb2` (alpha.30.7) jusqu’à la dernière livraison locale vérifiée,
+**beta.060**, puis avancer `main` sans réécrire l’historique.
+
+La tâche beta.061 travaille simultanément dans le dossier principal. Cette
+synchronisation utilise un worktree isolé et la copie des sources beta.060
+conservée avant cette tâche dans `build/arrival-mount061-intake/`. Les sources
+du mod, ses tests, sa configuration Gradle, les versions et les documents
+modifiés par beta.061 sont repris depuis cette copie. Les autres sources et
+documents proviennent du dossier principal. Les changements beta.061 restent
+dans le dossier principal.
+
+Ce rattrapage Git ne crée pas une nouvelle livraison binaire : le compteur
+reste `beta.060`. Les anciennes bêta ne reçoivent pas de tags rétrospectifs,
+car leurs états source complets n’ont pas été enregistrés dans Git.
+
+## Périmètre
+
+- Code Sanctuary, module Demeure, port JEI reproductible et profil de test.
+- Ressources, traductions FR/EN, manifestes packwiz, scripts et documentation.
+- Versions courantes du README corrigées ; historique des livraisons conservé.
+- Exports ZIP des resource packs ignorés ; sources décompressées versionnées.
+
+Les mondes, caches, builds, dépendances téléchargées et JAR générés restent
+ignorés. Les artefacts bêta restent locaux. Cette opération ne publie pas de
+release binaire, n’avance pas le canal packwiz et ne synchronise pas Prism.
+
+## Vérifications
+
+Les SHA-256 des archives locales beta.060 correspondent au reçu de livraison
+`build/mob-head060-artifact.json` :
+
+- Pack normal : `7489ea0b5012f799baa929eebf37fed7abc588b913a0107c30f49ddcdfff6e01`.
+- Monde plat : `7782ba69542ad7b922a1f4777763fc04dfcf0b92b5a1ea926c69c2235072d7af`.
+
+La compilation isolée retrouve **1 620 classes Sanctuary, 14 classes Demeure
+et 1 101 classes JEI strictement identiques** aux classes des JAR de la
+livraison beta.060. Aucune classe n’est ajoutée ou retirée. Les 680 ressources
+de jeu sont identiques ; les métadonnées Fabric concordent avant l’ajout
+normal de la liste des JAR imbriqués par Gradle.
+
+Reçus locaux : `build/git-sync-beta060-class-comparison.json` et
+`build/git-sync-beta060-resource-comparison.json`.
+
+La commande complète `./gradlew check build assemblePack assembleTestPack`
+échoue après **9 min 33 s** sur `:sanctuary:runGameTest` : **235 tests serveur
+réussissent sur 246**, dans un monde de développement neuf, graine `0`.
+Les échecs constatés sur les sources beta.060 sont :
+
+| Test | Assertion en échec |
+| --- | --- |
+| `GravesFood038GameTests.familiarSaturationPreviewUsesActualServerPassive` | Passif réel du familier disponible |
+| `UnifiedWorldGameTests.retainedHydrologySurvivesNativeDecoration` | Support naturel du sédiment en `x=-141, z=-86, bedY=238` |
+| `UnifiedWorldGameTests.populationTerrainAndNaturalSpawnRemainPresent` | Hauteur totale du monde |
+| `Progression003GameTests.serverCustomNameConfiguration` | Anciennes commandes réservées aux opérateurs |
+| `Accessory018GameTests.familiarIsHarmlessAndEscapesWalls` | Familier insensible aux dégâts |
+| `Companion019GameTests.switchingEggRemovesPassiveAndActive` | Passif de chute du chat |
+| `Companion019GameTests.poisonDurationAndMilkUseNativeHooks` | Durée du poison |
+| `Companion019GameTests.furnaceBonusConsumesFuelAndKeepsOneOutput` | Bonus de cuisson et combustible |
+| `Companion019GameTests.aquaticPetFlopsAndThenSwims` | Poisson sur terrain sec |
+| `Companion019GameTests.cropCyclesStopAfterOwnerLeaves` | Cycle de culture du propriétaire d’abeille |
+| `Companion032GameTests.axolotlFoodDurationAndGolemKnockbackUseNativeHooks` | Durée de consommation sur terre |
+
+Les deux assertions de génération sont déjà consignées dans
+[le ticket de transition beta.001](versioning.md#validation-et-distribution).
+Les autres échecs restent à trier ; cette synchronisation ne les corrige pas
+et ne modifie aucune assertion. Elle ne constitue pas une validation complète
+de tous les comportements des 60 incréments.
+
+Journal et reçu locaux : `build/git-sync-beta060-check.log` et
+`build/git-sync-beta060-tests.json`.
+
+La construction des artefacts est ensuite exécutée avec
+`./gradlew build assemblePack assembleTestPack -x :sanctuary:runGameTest`.
+L’exclusion évite de répéter la suite serveur déjà exécutée ; elle ne transforme
+pas le contrôle complet en réussite. Cette construction réussit en **2 min 25 s**,
+avec **122 tâches** ; les deux packs sont assemblés et leurs manifestes
+vérifiés. Journal : `build/git-sync-beta060-assemble.log`.
+
+Les vérifications natives de la livraison initiale et leurs limites sont
+décrites dans [le ticket beta.060](mob-head-blocks-beta060.md).
diff --git a/docs/grave-inventory-beta039.md b/docs/grave-inventory-beta039.md
new file mode 100644
index 0000000..cc360c5
--- /dev/null
+++ b/docs/grave-inventory-beta039.md
@@ -0,0 +1,43 @@
+# beta.039 — inventaires côte à côte à la mort
+
+Contrat demandé le 14 septembre 2026, avant modification du stockage des nouvelles
+têtes. À gauche : les cases du défunt, équipement et accessoires compris, avec
+les rangées visibles, le nombre de rangées débloquées et la hotbar au décès.
+Toutes ces cases restent grisées et interdisent tout dépôt. À droite : les cases
+du joueur qui récupère le contenu, avec **son écran d’inventaire habituel**, son
+personnage, ses cases, sa fabrication et ses équipements. Les textures existantes
+`inventory_1.png` à `inventory_6.png` et la hotbar native sont réutilisées des deux
+côtés ; aucune nouvelle texture. Les deux panneaux s’adaptent ensemble aux petites
+échelles de GUI. La fabrication au décès garde ses quatre emplacements ; le
+curseur est récupérable à la place du résultat dans le panneau de gauche.
+
+Migration additive : seuls les nouveaux décès écrivent `layout: 1`,
+`inventory_rows`, `visible_rows`, `hotbar_row` dans `sanctuary:grave` schéma 1.
+Le composant natif `minecraft:container` garde ses 108 adresses, mais les nouvelles
+têtes réservent 0–53 au stockage physique, 54–57 à l’armure, 58–61 au cosmétique,
+à la cape, au familier et à la main gauche, 62–65 à la fabrication et 66 au curseur.
+Les éventuels objets supplémentaires restent récupérables à partir de 67.
+Les cases vides restent vides : aucun compactage lors du retrait.
+
+Les anciennes têtes, sans disposition sauvegardée, gardent le coffre de récupération
+à deux pages. Leurs données ne sont pas réinterprétées ni réécrites. Le pillage,
+l’XP, la malédiction de disparition et `keepInventory` ne changent pas.
+Aucun monde personnel n’est utilisé pour les essais.
+
+## Validation
+
+Quatre tests serveur couvrent le décès natif avec six rangées, une hotbar déplacée,
+l’armure, les trois accessoires, la fabrication et le curseur ; le retrait partiel
+sans compactage ; deux joueurs pillant le même contenu ; les dépôts refusés et
+le shift-clic qui équipe armure/cape/familier sans équiper le cosmétique.
+La fabrication personnelle utilise les recettes natives, le shift-clic, le clic
+ordinaire, les touches de hotbar et leurs compteurs de fabrication, puis restitue
+les ingrédients à la fermeture. Les icônes d’armure vides et le statut spécial
+de la case de résultat restent ceux des emplacements natifs. Anciennes têtes et rangées occupées
+après prestige gardent leur comportement.
+
+Le parcours client natif ouvre un décès à six rangées à côté d’un joueur à une
+rangée, récupère réellement les objets et équipements par paquets de clic, puis
+vérifie les échelles de GUI 1 et 2 et les libellés français. Les **173 PNG du mod**
+sont identiques à ceux du début du ticket. Captures dans
+`build/food039-evidence/release/`.
diff --git a/docs/graves-food-beta038.md b/docs/graves-food-beta038.md
new file mode 100644
index 0000000..4e99ccb
--- /dev/null
+++ b/docs/graves-food-beta038.md
@@ -0,0 +1,140 @@
+# beta.038 — têtes-tombes, informations alimentaires et fondation des factions
+
+Livraison beta.038 vérifiée. Cible exacte : Minecraft 26.3-pre-2.
+
+## Contrat de mort et migration
+
+La mort dans une partie Sanctuary place les objets normalement perdus dans une
+`minecraft:player_head` portant le profil du défunt. Tout le monde peut la piller.
+L’XP conserve sa règle actuelle (70 % en orbes). `keepInventory`, le mode
+spectateur et la malédiction de disparition restent respectés.
+
+Les anciennes sauvegardes d’inventaire ne changent pas. Seules les nouvelles
+morts produisent des tombes. Les têtes ordinaires restent ordinaires. La tombe
+utilise les composants natifs `minecraft:profile`, `minecraft:container` et
+`minecraft:custom_data` (marqueur `sanctuary:grave`, schéma 1). Son contenu suit
+la tête lorsqu’elle est cassée ou transportée. La tête vide conserve l’identité.
+Aucun registre mondial supplémentaire, aucune réécriture de chunks ni migration
+26.2. Retirer Sanctuary conserve les composants, mais supprime l’interface et
+les protections spécifiques : vider les tombes avant de retirer le mod.
+
+L’interface est un double coffre paginé pour accueillir les six rangées,
+l’équipement, les accessoires et les objets en cours de manipulation. La tombe
+est un coffre de récupération, sans dépôt de nouveaux objets. Toutes ses cases
+sont grisées, y compris les cases vides ; les objets restent lumineux et retirables. Les fenêtres
+partagent le même contenu côté serveur et deviennent invalides si leur support
+disparaît. Le placement cherche un sol sûr proche dans les chunks chargés ; il
+n’écrase aucun bloc. Les cas de lave et de vide ont un repli protégé.
+
+## Informations alimentaires
+
+AppleSkin officiel propose 3.0.10 pour 26.2, aucune version déclarée pour
+26.3-pre-2 (API Modrinth et branches amont vérifiées le 14 septembre 2026).
+Sanctuary reprend les indications dans son propre HUD : icônes de nourriture
+et de saturation en infobulle, saturation actuelle, épuisement et aperçu de la
+nourriture tenue. Le serveur fournit la saturation et l’épuisement réels ; les
+aperçus respectent la capacité Hunger actuelle, dès trois icônes.
+
+Cela ne change ni la valeur des aliments ni la vitesse de consommation ou de
+régénération. Pas de nouvelle aptitude requise. Les préférences d’affichage
+restent locales. Les sprites de faim vanilla restent la référence remplaçable
+par le resource pack ; les contours AppleSkin sont un asset distinct crédité.
+Aucune texture de respiration ne change.
+
+## Utilisation et limites
+
+Clic droit sur la tête posée : ouvrir. Le bouton `1 / 2` ou `2 / 2` change de
+page quand le curseur est vide. Shift-clic extrait vers les rangées débloquées.
+Le coffre permet la récupération, pas le dépôt de nouveaux objets. Casser la
+tête, y compris en créatif, conserve les objets dans une tête transportable.
+Clic droit dans le vide avec cette tête en main : ouvrir ; Maj + clic droit sur
+un bloc : reposer. Aucun contenu n’est publié dans les mises à jour de chunks ;
+seul le marqueur d’identité accompagne le rendu de la tête.
+
+Le placement cherche à 12 blocs horizontalement et verticalement, sans charger
+de chunks. Si cette zone est inutilisable, il essaie le dernier sol sûr observé
+dans la même dimension pendant cette connexion. Sans sol utilisable, la tête
+flotte près de la mort, au-dessus de la limite basse du monde ; le message donne
+ses coordonnées. Cette tête ne disparaît pas au bout de cinq minutes et résiste
+aux dégâts. Aucun terrain ni île n’est généré pour la récupérer.
+
+AppleSkin est une intégration Sanctuary, pas un JAR 26.2 supposé compatible.
+Les valeurs sont envoyées seulement au joueur concerné, au maximum quatre fois
+par seconde lorsqu’elles changent. Un aperçu peut donc suivre la consommation
+avec un retard maximal d’environ un quart de seconde à 20 TPS. Les infobulles
+montrent deux rangées d’icônes, sans points chiffrés : nourriture native et
+contours dorés de saturation. La part non absorbée maintenant reste assombrie
+avec une légende. Les demi-icônes et fractions de saturation restent visibles.
+Les bonus de saturation du cochon et de la mooshroom viennent des passifs
+réellement actifs côté serveur. Les effets de potion et la régénération ne sont
+pas prédits. Les textures et les crédits sont documentés dans [FOOD.md](../ressources-pack/sanctuary/FOOD.md).
+
+## Vérifications
+
+`./gradlew check build assemblePack assembleTestPack` passe en **4 min 30 s**,
+avec **55 tests serveur** et les contrôles purs du dépôt. Le lot couvre les
+inventaires complets, deux pages et deux pilleurs, les refus de dépôt (clic,
+shift-clic, touche numérique et glisser), les protections lave/vide, le placement,
+la casse créative et la sérialisation du contenu et de la mémoire du décès.
+
+Les aperçus alimentaires sont comparés à la consommation Minecraft sur
+**14 784 cas**, puis 18 consommations avec les passifs réels cochon/mooshroom.
+La fondation est testée à zéro prestige : refus sans XP, coût exact de dix niveaux,
+propriété et place unique, invitations refusées avant extension, charges de
+prestige, reprise du paiement avant/après sauvegarde, mort et restauration
+partielle refusée. Le contrôle pur relit un vrai journal schéma 1 sans changer
+ses anciennes places/dépenses, puis vérifie sa mutation en schéma 2 et la
+conversion unique de la configuration.
+
+Le parcours graphique final passe en **45 s**, sur un monde plat de développement :
+HUD à trois puis dix icônes, bonus serveur du familier, infobulles illustrées,
+préférences FR/EN, ouverture réelle d’une tête par paquet, extraction sur les deux
+pages, transport, informations de mort, GUI 1/2, puis création d’une faction par
+les vrais boutons à prestige zéro. Treize captures sont conservées dans
+`build/graves038-evidence/`, les logs dans `build/graves038-check-release.log` et
+`build/graves038-client-ready.log`.
+
+Les deux MRpacks ont été exportés et vérifiés : 1 381 classes Sanctuary,
+dépendances et ressources embarquées identiques aux builds, trois classes du
+module Test. Le profil plat ajoute uniquement ce module ; les MRpacks beta.037
+restent inchangés. Reçu : `build/graves038-artifact.json`.
+
+- [Pack normal](../build/Sanctuary-beta.038.mrpack).
+- [Pack de test plat rapide](../build/Sanctuary-Test-beta.038.mrpack).
+
+Ces tests ne constituent pas un essai prolongé sur un serveur public. Les seules
+sauvegardes utilisées sont celles des dossiers de développement ignorés ; aucun
+canal public, serveur personnel ni instance Prism n’a été mis à jour.
+
+## Fondation des factions — contrat beta.038
+
+La fondation devient accessible dès l’arrivée, sans prestige : **10 niveaux
+entiers d’XP**, une seule place occupée par le fondateur, qui en est responsable.
+Chaque place supplémentaire garde le prix en charges personnelles issu du
+prestige (une charge par défaut). Les invités n’ont pas besoin de prestige.
+Le départ et la dissolution ne remboursent pas la fondation.
+
+Ce contrat autorise une lecture additive des journaux de cycles schéma 1 vers
+schéma 2 : les anciens événements restent datés et valorisés en charges, sans
+remboursement ni réduction des factions existantes. Les événements nouveaux
+précisent leur monnaie (`charges` ou `levels`) ; l’identifiant
+`sanctuary:faction_create` reste stable. La configuration schéma 1 est convertie
+au chargement vers schéma 2 : `creationCost` est remplacé par
+`creationLevels: 10`, `initialCapacity` devient 1. Les autres paramètres sont
+conservés. Une configuration schéma 2 conserve son prix configuré.
+
+Le serveur vérifie les niveaux avant d’écrire la fondation. Un reçu persistant
+`sanctuary:faction_xp_paid`, sauvegardé avec l’XP du joueur et conservé à la mort,
+empêche de payer deux fois après reconnexion. Si le journal a été écrit juste
+avant un arrêt mais pas le joueur, le débit restant est repris avant toute autre
+opération, puis avant un éventuel reset New Game+. Un reçu en avance sur le
+journal est refusé comme restauration partielle. Aucune sauvegarde personnelle
+n’est modifiée pendant le développement ; les anciens binaires ne doivent pas
+relire un monde passé au schéma 2. Restaurer ensemble joueur et journal.
+
+Les têtes enregistrent aussi l’identité, la date et l’heure civiles dans le
+fuseau du serveur (celui de la machine en solo), le message natif de mort,
+la dimension et les coordonnées du décès, distinctes du lieu sûr de dépôt.
+Ces champs additifs du marqueur schéma 1 et le lore natif restent après vidage.
+Le client et le serveur doivent tous deux être en beta.038 : le marqueur réseau
+additif `sanctuary:faction_founding_levels_v1` vérifie la nouvelle politique.
diff --git a/docs/head-blocks-beta050.md b/docs/head-blocks-beta050.md
new file mode 100644
index 0000000..b7b06fd
--- /dev/null
+++ b/docs/head-blocks-beta050.md
@@ -0,0 +1,85 @@
+# beta.050 — bateaux compacts et blocs portés
+
+Branche `codex/head-blocks-beta050`.
+
+## Contrat
+
+Deux formats supplémentaires, largeur × longueur : **1 × 2 et 2 × 1**,
+dans les onze bois natifs. Deux bateaux ordinaires identiques, disposés selon
+la forme souhaitée dans l'établi, donnent le bateau collectif correspondant.
+Les noms restent « bois + dimensions ». Les modèles répètent les fonds natifs,
+avec bordures extérieures et deux rames. Chaque format accueille quatre
+passagers : les deux places principales, puis les deux secondaires.
+Les moteurs animaux, commandes partagées, piles et règles de poids sont conservés.
+Les 22 variantes rejoignent la collection Bateaux et radeaux, soit 88 variantes.
+
+## Blocs portés
+
+- Les bibliothèques sculptées acceptent les livres enchantés avec leurs données
+ intactes. Le clic est converti vers la face et la case du modèle porté.
+ Depuis l'inventaire, un clic avec un livre remplit la première case libre ;
+ à vide, il récupère le premier livre présent. La même règle s'applique aux
+ trois cases des étagères. Depuis le slot cosmétique, les objets récupérés
+ reviennent au curseur.
+- Les étagères transmettent leurs trois objets visibles au rendu natif. Les
+ coffres et fours continuent de conserver leur contenu hors de l'apparence.
+- Le pupitre affiche le livre inséré et son interface s'ouvre à main vide.
+- Le lit porté comporte ses deux moitiés. Il est purement décoratif : aucun
+ joueur ne peut y dormir ou y fixer son point de réapparition. Les lits posés
+ conservent leur fonctionnement natif.
+- Les pots acceptent les fleurs admises par Minecraft, avec consommation d'un
+ exemplaire et récupération à main vide, y compris depuis le slot cosmétique.
+- Boutons et leviers reposent à plat sur le dessus de la tête, légèrement
+ relevés pour rester visibles avec la couche extérieure du skin. Le clic droit
+ d'un autre joueur, ou le clic dans le slot cosmétique, les actionne.
+
+## Redstone mobile
+
+Une torche de redstone portée est une source continue ; le levier allumé est
+une source maintenue ; le bouton suit sa temporisation native. Ces trois sources
+émettent une puissance 15 autour de la cellule occupée par la tête et peuvent
+alimenter les circuits réels adjacents. La torche portée n'a pas de bloc support
+et ne s'inverse donc pas selon un support fictif. Les blocs posés restent natifs.
+
+Le serveur tient un index des cellules alimentées. Il notifie les voisins
+seulement lors d'un changement d'état ou de cellule, puis laisse la redstone
+native propager le signal. Déplacement, retrait, mort et déconnexion retirent
+l'ancienne source ; plusieurs porteurs dans une cellule se cumulent sans que
+le retrait de l'un annule les autres. Aucun bloc source n'est placé dans le
+terrain et aucun chunk n'est régénéré. Les circuits peuvent naturellement
+activer leurs récepteurs, y compris pistons et distributeurs placés.
+
+Le signal local déclenché par un coup de beta.049 reste distinct de ces sources
+mobiles. Les cosmétiques ne forment pas un réseau de plusieurs blocs sur une
+seule tête. Aucun changement de format de sauvegarde ni de génération.
+
+## Vérification
+
+- `./gradlew check build :sanctuary:runGameTest assemblePack assembleTestPack`
+ avec `-PsanctuaryFocusedTests=head050,poultry,headblocks,boatshead,cosmetics`
+ et `-PsanctuaryQuickTests=true` : **40 tests natifs réussis**.
+- Le parcours client FR/EN valide les 88 modèles par tuiles et leurs libellés,
+ les objets réellement synchronisés dans les étagères, le livre du pupitre,
+ les deux moitiés du lit, les boutons et leviers sur la couronne, le pot fleuri,
+ le livre enchanté et le vol des deux formats depuis les commandes natives.
+- Après le léger relèvement visuel des commandes, nouveau `check build`,
+ `runClientGameTest` et assemblage, avec les propriétés
+ `sanctuaryClientTests`, `sanctuaryHead050ClientTests`, `sanctuaryClientNoVsync`
+ et `sanctuaryQuickTests` à `true`. `-x :sanctuary:runGameTest` évite de répéter
+ les 40 tests serveur pour cet ajustement limité au rendu. Parcours client réussi.
+- `python3 scripts/compile_collections.py` : Markdown, données et traductions
+ concordants ; 22 nouveaux plans de bateaux ajoutés à la collection.
+- Les deux exports packwiz sont vérifiés contre les classes et ressources du
+ build : **1 533 classes**. Icône officielle, PNG existants, JEI et données de
+ génération préservés ; archives beta.049 inchangées.
+
+Journaux : `build/head050-delivery.log`, `build/head050-client-final.log`.
+Reçu : `build/head050-artifact.json` ; captures : `build/head050-evidence/`.
+
+[Pack normal](../build/Sanctuary-beta.050.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.050.mrpack).
+
+Validation effectuée sur les serveurs natifs de test et un client intégré local.
+Une session avec plusieurs clients distants reste à confirmer en partie.
+Aucun déploiement dans l'instance personnelle, changement du canal public ou
+acceptation d'EULA de serveur dédié.
diff --git a/docs/head-cosmetics-beta021.md b/docs/head-cosmetics-beta021.md
new file mode 100644
index 0000000..b487899
--- /dev/null
+++ b/docs/head-cosmetics-beta021.md
@@ -0,0 +1,109 @@
+# beta.023 — cosmétiques de tête interactifs
+
+Ticket commencé en beta.021 le 13 septembre 2026, branche
+`codex/head-cosmetics-beta021`. Finalisation en beta.023 après le correctif
+de chargement beta.022 préparé dans un autre espace de travail.
+
+## Contrat avant implémentation
+
+Le slot tête reste un objet réel, sans protection, collision, redstone ou effet
+de bloc. Les œufs sur la tête gardent leur rendu d’objet. Les autres accessoires,
+familiers et pouvoirs ne changent pas.
+
+Les bannières utilisent leurs couleurs et motifs natifs, y compris la bannière
+menaçante. Lits et portes ont leurs deux moitiés. Les minecarts utilisent leur
+modèle d’entité sans créer d’entité dans le monde. Feuilles, verre et toile
+d’araignée enveloppent la tête. Les panneaux utilisent leur texte natif.
+
+Clic droit sur un autre porteur : écrire sur son panneau, ajouter une bougie
+au gâteau avec une bougie en main, allumer avec briquet ou boule de feu,
+éteindre à main vide, changer la pose d’une statue de golem à main vide.
+Une bougie éteinte sur un gâteau peut être retirée à main vide et est rendue.
+L’ajout consomme une bougie hors créatif ; allumer use le briquet ou consomme
+la boule de feu. Aucun feu ni lumière de bloc n’est créé dans le monde.
+Maj garde le geste de portage. Les trois interactions bougie/gâteau/statue sont
+aussi accessibles par clic droit sur la case cosmétique dans l’inventaire,
+avec l’objet au curseur. Un curseur vide éteint, retire la bougie ou change la
+pose ; la bougie retirée revient au curseur. Clic gauche retire normalement
+le cosmétique et Maj-clic conserve le transfert natif. Aucun bouton supplémentaire.
+Le porteur ne peut jamais écrire sur son propre panneau, même opérateur.
+
+Les huit emplacements d’équipement ont une infobulle de rôle : tête, buste,
+jambes, pieds, main secondaire, familier, cape et cosmétique. Elle reste présente
+avec un objet équipé, à côté des informations natives de cet objet.
+
+## TNT : exception de gameplay demandée
+
+Un coup infligeant des dégâts amorce la TNT équipée. L’objet est consommé,
+remplacé par une vraie TNT native avec une mèche de 80 ticks (4 secondes à
+20 TPS), qui suit la tête. Retirer ou remplacer le cosmétique n’annule rien.
+La mort, la déconnexion ou le changement de dimension la détachent au dernier
+endroit, où elle poursuit sa mèche. Son explosion native endommage les entités
+et les blocs selon les règles du serveur, avec l’attaquant comme responsable
+lorsque possible. Une seule TNT peut être amorcée sur un même porteur à la fois.
+Aucune immunité nouvelle n’est accordée au porteur. La TNT utilise sa sauvegarde
+native (position et mèche) ; après redémarrage elle reste au dernier lieu sauvé.
+
+## Stockage et réseau
+
+Le schéma 1 des accessoires reste inchangé. Les composants natifs de la pile
+portent `sign_text_front`, `sign_text_back`, `banner_patterns` et `block_state`.
+Le gâteau reçoit uniquement une clé additive `sanctuary:head_candle` dans
+`custom_data`, contenant l’identifiant de sa bougie. L’absence de cette clé
+signifie un gâteau ordinaire. Elle ne transforme pas la pose native du gâteau
+en bloc : retirer sa bougie avant de le poser permet de la récupérer.
+Toutes les autres données de l’objet sont préservées. Les piles existantes
+sont lues sans migration ni réécriture de monde.
+
+Le serveur valide proximité, visibilité, objets, coût et état courant. L’éditeur
+natif de panneau est relié à un paquet dédié, jamais au paquet d’édition d’un
+bloc du monde. Une session temporaire et un instantané de l’objet empêchent
+d’écrire sur un panneau remplacé ou à distance. Le texte accepté est constitué
+de quatre lignes sans commandes, avec le filtrage de texte natif du serveur.
+Le client et le serveur de cette livraison utilisent `head_cosmetics_v2`.
+La version 2 distingue l’interaction avec l’objet au curseur de l’ancien bouton
+utilisant la main principale ; un ancien client est refusé à la connexion
+pour éviter de donner deux sens différents au même geste. Client et serveur
+doivent être mis à jour ensemble.
+
+## Validation
+
+Livraison locale **beta.023**, client et serveur de même version.
+
+- `./gradlew check build assemblePack :sanctuary:runClientGameTest` réussi,
+ avec les suites cosmétiques, accessoires, familiers, progression, mouvement,
+ inventaire, transferts, rangement, cycle, minage, construction, premiers pas
+ et optimisation : **117 tests serveur**, puis parcours client natif.
+- Actions au curseur : bougie consommée une seule fois, briquet usé ou cassé,
+ extinction puis restitution au curseur, main principale conservée. Un menu
+ de coffre ouvert refuse le paquet réservé à l’inventaire personnel.
+- Clic gauche retire le cosmétique ; le clic droit d’un panneau personnel
+ garde le transfert natif et n’ouvre pas l’éditeur. Le mixin ignore Maj.
+- Autre joueur : vrais clics sur panneaux classiques/suspendus, éditeur natif,
+ texte réseau puis rendu. Motifs menaçants et seize couleurs de bannières,
+ deux moitiés de portes/lits, six minecarts, verre/feuilles/toile et œuf inchangé.
+- TNT : dégâts réels, coup fatal avant les drops, quatre secondes de mèche,
+ suivi et explosion native malgré le retrait du cosmétique.
+- Optimisation beta.022 intégrée : **588 288 comparaisons bit à bit** réussies
+ et classes de densité identiques à celles du précédent pack vérifié.
+- ZIP, versions, dépendances exactes, JAR embarqué et Demeure imbriqué vérifiés.
+ Exports beta.020/beta.022 immuables ; PNG existants conservés ; aucun test
+ ni monde distribué. Sources figées pendant la construction finale.
+
+Commande de vérification :
+
+```sh
+./gradlew check build assemblePack :sanctuary:runClientGameTest \
+ -PsanctuaryClientTests=true -PsanctuaryHeadCosmeticsClientTests=true \
+ -PsanctuaryFocusedTests=cosmetics,accessories,familiar,companions,progression,movement,inventory,inventoryflow,sorting,cycle,mining,building,tutorial,startup
+```
+
+Export : [Sanctuary-beta.023.mrpack](../build/Sanctuary-beta.023.mrpack).
+SHA-256 : `2140d477f2e105884ffdfe8d3d7a1199a1fdf3d298ad97dc7880c89918eb22c4`.
+Preuves ignorées : `build/cosmetics023-artifact.json`,
+`build/cosmetics023-check-build.log`, captures du client et diagnostics serveur.
+
+Les essais graphiques ont lieu sur Mac M1, dans un monde de développement
+neuf, avec un second avatar serveur de test ; aucune session distante réelle,
+ni validation Windows, ni installation Prism n’est revendiquée. Le portage
+JEI est le [ticket suivant](recipes-jei-beta022.md), pas une fonction de ce pack.
diff --git a/docs/head-dispenser-beta051.md b/docs/head-dispenser-beta051.md
new file mode 100644
index 0000000..d957cb3
--- /dev/null
+++ b/docs/head-dispenser-beta051.md
@@ -0,0 +1,46 @@
+# beta.051 — visée du distributeur porté
+
+Branche `codex/head-dispenser-aim-beta051`.
+
+Le distributeur équipé dans le slot cosmétique lance ses projectiles selon le
+regard du porteur au moment du tir, horizontalement et verticalement, y compris
+pendant une rotation entre le déclenchement et le tir différé. L'origine du tir
+suit la tête réelle du joueur, pas le centre arrondi d'une cellule du monde.
+
+Les treize munitions natives utilisent le même correctif : flèches normales,
+spectrales et à effet, trois couleurs d'œufs, boule de neige, fiole d'expérience,
+potions jetables et persistantes, fusée, boule de feu et charge de vent.
+Le lancement natif conserve vitesse, dispersion, trajectoire ultérieure,
+effets et consommation d'une munition. Le porteur devient le propriétaire du
+projectile, ce qui évite de le toucher immédiatement lors d'un tir vers le bas
+et attribue les impacts au joueur. Les comportements habituels des projectiles
+appartenant à un joueur restent applicables.
+
+Les distributeurs posés, droppers et objets non projectiles conservent leur
+fonctionnement. Aucun changement des sauvegardes, de la génération ou des textures.
+
+## Vérification
+
+`./gradlew check build assemblePack assembleTestPack
+-PsanctuaryFocusedTests=dispenser051,head050,headblocks,boatshead,cosmetics
+-PsanctuaryQuickTests=true` : succès, **34 tests serveur** sur Minecraft
+26.3-pre-2. Les quatre nouveaux tests couvrent :
+
+- 104 lancements natifs : treize munitions dans huit directions, dont les
+ verticales ; origine précise, vitesse, propriétaire et consommation vérifiés.
+- Changement de regard et de position entre l'impulsion et le tir différé.
+- Distributeur posé conservant son orientation, même pendant l'exécution
+ d'un autre bloc porté.
+- Dropper conservant son éjection d'objet et flèche vers le bas sans impact
+ immédiat sur le porteur.
+
+Journal : `build/dispenser051-delivery.log`. Reçu :
+`build/dispenser051-artifact.json`. Les 1 534 classes et ressources emballées
+correspondent au build ; seule la classe du nouveau mixin s'ajoute depuis
+beta.050, avec son enregistrement. Archives beta.050, textures et données de
+génération préservées.
+
+[Pack normal](../build/Sanctuary-beta.051.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.051.mrpack).
+Validation en partie avec plusieurs clients distants restant à confirmer.
+Aucun déploiement sur le canal public ou l'instance personnelle.
diff --git a/docs/hello-starter-beta039.md b/docs/hello-starter-beta039.md
new file mode 100644
index 0000000..536aa34
--- /dev/null
+++ b/docs/hello-starter-beta039.md
@@ -0,0 +1,44 @@
+# beta.039 — Hello World et familier de départ
+
+Contrat demandé le 14 septembre 2026, avant modification. Le serveur enregistre
+une proposition de trois espèces différentes dès le début du Hello World. Le
+tirage prend trois communs sans remise ; avec 10 % de chance, une place aléatoire
+est remplacée par un peu commun. Les raretés et espèces activées proviennent de
+la configuration serveur des familiers au moment du tirage. Aucun rare ni au-delà.
+
+La création reste sur **un seul écran Hello World** : identité, palette de
+couleurs, puis **trois cases d’œufs centrées** sur la ligne suivante. Le libellé
+« Familier de départ » reste aligné à gauche avec les autres champs. Le survol montre le
+nom, la rareté et une courte description ; un cadre natif marque le choix actif.
+Les 44 espèces communes/peu communes du catalogue possèdent une description FR/EN.
+Entrer exige une couleur et un familier. Les cases réutilisent les sprites natifs,
+sans créer de textures. L’écran séparé de choix n’est pas conservé.
+
+Quitter ne relance aucun tirage. La validation enregistre l’identité et le choix
+avant l’introduction. Une coupure après validation reprend l’intro. Si l’écriture
+est interrompue entre identité et sélection, le même formulaire reprend avec
+l’identité enregistrée verrouillée et les trois œufs d’origine. Le choix confirmé
+est définitif. L’œuf est équipé à la première arrivée.
+
+Migration additive : nouveau fichier du monde `data/sanctuary-starters.json`,
+schéma 1, contenant UUID, trois choix et sélection. Les registres d’habitants
+existants ne changent pas. Les habitants déjà enregistrés avant cette version
+continuent leur parcours sans recevoir rétroactivement un œuf. Nouveau reçu
+joueur `sanctuary:starter_companion` sauvegardé avec l’équipement, copié à la mort
+et conservé au prestige ; aucune remise d’œuf après perte, transfert ou mort.
+Les anciennes données invalides sont préservées et bloquent l’accueil.
+
+La synchronisation utilise des messages de configuration distincts, sans modifier
+les anciens messages Hello World. Les clients doivent connaître ce nouvel écran.
+Les essais restent dans les mondes de développement.
+
+## Validation
+
+Quatre tests serveur passent : 10 000 tirages et catalogue configuré, reprise du
+fichier avant/après sélection, refus des choix étrangers et du changement final,
+œuf/reçu sauvegardés ensemble, mort et véritable prestige, absence de cadeau
+rétroactif et préservation des fichiers invalides. Le parcours client natif
+vérifie le choix en phase de configuration sans joueur/monde client, un paquet
+falsifié refusé, l’œuf choisi équipé, puis sauvegarde et reconnexion sans remise.
+Capture : `build/food039-evidence/hello-starter.png`.
+Le profil plat de test garde cette sélection ; seule la cinématique est passée.
diff --git a/docs/initial-loading.md b/docs/initial-loading.md
new file mode 100644
index 0000000..905b9e9
--- /dev/null
+++ b/docs/initial-loading.md
@@ -0,0 +1,98 @@
+# STARTUP-01 — raccourcir la création de monde
+
+Branche : `codex/initial-loading`. Minecraft **26.3-pre-2**, Fabric Loader
+**0.19.5**, Fabric API **0.160.0+26.3**. Correctif local **beta.022**, préparé à partir du
+chantier beta.021 ; publication et instance Prism distinctes.
+
+## Problème observé
+
+Les plans d’eau, de traversées et de structures sont calculés avant l’arrivée.
+Le profil JFR du serveur de développement situe le coût surtout dans les bruits
+Perlin du champ `PopulationIslandDensity`. Le message Minecraft `Time elapsed`
+arrive après la planification : il sous-estime donc la création totale.
+
+## Correctif
+
+Les bruits restent identiques, mais leur évaluation devient conditionnelle :
+
+- À l’intérieur de `radius - 128`, le facteur d’érosion du bord est exactement
+ nul. Le bruit du bord n’est pas nécessaire.
+- À partir de Y140, le facteur d’érosion inférieur est exactement nul, même
+ avec les bruits à leurs bornes maximales. Le bruit du dessous est inutile.
+- Lorsque ces deux conditions sont réunies, les bruits de sculpture et de
+ détail ne contribuent plus au champ, sauf dans l’ancienne variante à terrasses.
+- À partir de Y320, avec le repère vertical normal, l’érosion haute vaut au
+ moins 2 pour un terrain borné à 1 : la densité de base vaut exactement -1.
+ Les corps flottants sont toujours ajoutés ensuite par `FloatingTerrain306`.
+ La variante historique 30.4, qui déplace le repère vertical, garde son calcul.
+
+Aucun plan n’est supprimé ou différé, aucun cache disque n’est ajouté. Les codecs,
+identifiants, graines et formats de sauvegarde restent inchangés. La suppression
+de calculs doit conserver exactement les mêmes valeurs, pas une approximation
+visuelle. Les essais utilisent uniquement des mondes de développement neufs.
+
+## Mesures
+
+Mac Apple M1, 8 Gio, Java Temurin 25.0.3, tas JVM 2 Gio. Profil de départ Moyen
+724, graine 0, structures actives, nouveau dossier de monde à chaque essai.
+
+| Mesure | Résultat |
+| --- | --- |
+| CPU de 441 colonnes, référence beta.021 | médiane 706,874 ms |
+| CPU des mêmes colonnes, correctif | médiane 567,929 ms |
+| Réduction de CPU sur ce travail | **19,66 %** |
+| Création serveur finale, du lancement du serveur de test à sa disponibilité | **89 s** |
+| Préparation finale du spawn seule, incluse dans ces 89 s | 4,578 s |
+
+Le benchmark CPU utilise la graine 42, 183 456 densités par passage, un
+échauffement puis trois paires alternées dans la même JVM. Les six empreintes
+sont identiques : `d47c98ab8f10169c`. Il mesure du calcul de terrain, pas un gain
+universel de 20 % sur la durée totale du lancement.
+
+Le premier serveur profilé demandait 171 s, puis la première optimisation 132 s.
+D’autres constructions et tests tournaient pendant ces essais. Le dernier essai
+sans JFR atteint 89 s : ces trois temps ne prouvent pas à eux seuls une division
+par deux du chargement. Ils excluent le lancement du client, l’interface de
+création, le réseau et le rendu graphique. La planification restante prend
+encore du temps ; ce correctif ne rend pas la création instantanée.
+
+## Vérifications réussies
+
+- Référence beta.021 figée dans `StartupDensityReference` : **588 288 comparaisons
+ bit à bit réussies**, champs actuels et legacy27, capacités 5/10/20/100 et
+ graines 0, 42, -7228211907433324401. Colonnes traversant les seuils et
+ coordonnées positives/négatives, modes scalaire et volume.
+- Mesure CPU alternée dans une même JVM, sur les mêmes 441 colonnes complètes,
+ pour distinguer le gain de calcul des variations de charge de la machine.
+- `./gradlew check build assemblePack` réussit en **7 min 17 s**, avec les
+ sélecteurs ci-dessous : tous les contrôles purs et **10 tests serveur** réussis.
+ Couverture native : équivalence, mesure CPU, anciennes expéditions, plans
+ aériens, traversées, village et ruines asséchées.
+- Le correctif testé dans `build/startup-lab` est intégré aux sources principales
+ après contrôle des empreintes avant modification. Les travaux préexistants
+ restent conservés. `build assemblePack -x :sanctuary:check` y réussit en 18 s :
+ cette seconde commande assemble l’intégration et ne remplace pas le `check`
+ réussi dans l’espace isolé. Les classes optimisées du JAR intégré sont
+ identiques octet par octet à celles du serveur testé.
+- Pas d’essai graphique ni de validation Windows pour ce correctif.
+
+Commande de validation complète utilisée :
+
+```sh
+./gradlew check build assemblePack \
+ -PsanctuaryFocusedTests=startup,expeditions,aerial,transit,village,drained \
+ -PsanctuaryAerial24Tests=true -PsanctuaryTransit29Tests=true
+```
+
+Les preuves restent ignorées dans `build/startup-evidence/`,
+`build/startup-check-build.log` et `build/startup-integrated-build.log`.
+Le profil initial reste dans `build/startup-baseline-preserved.jfr`.
+Le mod et le pack prennent ensemble la version beta.022. Aucun tag,
+canal public ou installation Prism n’est modifié.
+
+Export local : [Sanctuary-beta.022.mrpack](../build/Sanctuary-beta.022.mrpack).
+Intégrité ZIP, dépendances exactes, absence de tests et identité du JAR embarqué
+vérifiées. Reçu : `build/startup-evidence/artifact.json`.
+
+SHA-256 JAR : `53203075916e1f494e5cb73b6a28959170c7f9df26b6edd4746b3531d404f3d1`.
+SHA-256 MRpack : `97545339b9f7c2465a8248e7fad018e694190ba5acf46aa38c3b40774aa55b56`.
diff --git a/docs/introduction-beta031.md b/docs/introduction-beta031.md
new file mode 100644
index 0000000..96cc76c
--- /dev/null
+++ b/docs/introduction-beta031.md
@@ -0,0 +1,116 @@
+# INTRO-01 — apparition galactique, beta.031
+
+Branche `codex/introduction-beta031`. Première proposition jouable demandée le
+14 septembre 2026 : entre **Entrer dans Sanctuary** et le spawn / message
+`helloworld`, l'identité du joueur apparaît dans le vide.
+
+## Séquence proposée
+
+Durée **29,5 secondes**, indépendante du nombre d'images par seconde.
+La direction demandée s'inspire de la tension et du mystère de l'introduction
+de **Super Metroid**, avec une composition propre à Sanctuary.
+
+- 0–2 s : noir profond, grondement lointain et premières traces de profondeur.
+- 2–22,2 s : **un glyphe après l'autre**, au centre. Chaque signe s'approche,
+ persiste brièvement, puis traverse le regard avec des rémanences. Les premiers
+ durent près de 1,4 s ; les derniers s'enchaînent en environ un quart de seconde.
+ Leur lumière augmente, du bleu gris presque noir au blanc froid. Des
+ rémanences spectrales cyan et violettes accompagnent les premiers signes.
+- 22,2–28,6 s : les glyphes partent des bords de l'écran et descendent vers le
+ centre en tournant. Leurs traces forment les marches d'un escalier en colimaçon
+ **vu du dessus**, avec une spirale circulaire continue. Un portail carré grandit au centre
+ jusqu’à dépasser les bords : la caméra semble entrer à l’intérieur.
+ Sa surface utilise la texture animée native du portail Minecraft.
+- 28,6–29,5 s : la spirale entre dans un **flash blanc**. Le blanc couvre la
+ transition réseau et le chargement des chunks, puis s'efface en 1,2 s sur le
+ monde prêt : l'ouverture révèle le spawn, sans retour au panorama du menu.
+
+Les grands glyphes de la première partie et leurs rémanences adaptent leur
+cadrage à la taille de la fenêtre.
+
+L'UUID exact fourni par le serveur conserve ses 128 bits : chaque chiffre
+hexadécimal `0..f` devient une lettre `a..p`, rendue avec la police Minecraft
+`minecraft:alt` (SGA). Tous les signes passent dans leur ordre UUID. Ils ne
+s'accumulent jamais sur une ligne : seul le signe courant et la courte
+rémanence du précédent coexistent avant la spirale. Chacun atteint le centre
+avant le blanc final. Quelques points froids suggèrent la profondeur du vide.
+
+Le fond sonore combine l'ambiance de la vallée des âmes, le portail assourdi,
+une écoute lointaine et huit battements de Warden de plus en plus rapprochés,
+puis l’ouverture du portail de l’End. Les sons proviennent de Minecraft **26.3-pre-2**, déjà requis ; aucune
+nouvelle dépendance ou copie de fichier audio vanilla n'est ajoutée. Le mixage
+respecte les volumes **Général / Ambiance**. La musique est interrompue pendant
+la séquence, sans modifier les réglages. Les sons sont arrêtés à la sortie.
+Le voile blanc est abandonné à une déconnexion ou à un écran d’erreur ;
+les menus ne sont pas masqués.
+
+**Échap** passe la séquence après la première seconde. Une indication FR/EN
+apparaît en bas après trois secondes. Le flash blanc est unique et progressif,
+sans clignotement répété.
+**`/sanctuary intro`** permet de la revoir à volonté, avec son UUID courant.
+La commande d'aperçu laisse le monde tourner, pour conserver le même mixage
+sonore que l'introduction. Elle se teste depuis un endroit tranquille.
+
+## Admission et sauvegardes
+
+La bio et la couleur sont validées avant le démarrage. Le serveur garde sa tâche
+de configuration ouverte jusqu'à l'accusé de fin : aucune entité joueur, chute,
+attaque ou annonce `helloworld` pendant l'introduction. Un paquet de fin avant
+la création est ignoré, comme une deuxième soumission pendant la cinématique.
+
+L'identité utilise le schéma existant, dont `arrived=false` désigne déjà une
+création qui n'a pas encore rejoint le monde. Après une déconnexion pendant
+l'introduction, la fiche et sa couleur restent acquises, puis l'apparition
+reprend à la connexion suivante. `arrived=true` est toujours écrit à l'arrivée
+effective. Les habitants déjà arrivés ne rejouent pas l'introduction.
+
+Deux nouveaux payloads de configuration, `sanctuary:intro_v1` et
+`sanctuary:intro_finished_v1`, accompagnent le contrat existant. Un client ancien
+qui reste compatible avec les autres fonctionnalités peut conserver l'ancien
+parcours ; il ne reçoit pas une cinématique qu'il ne sait pas rendre. Pour tester
+cette proposition, utiliser le client et le serveur beta.031 ensemble.
+
+Aucun schéma, paramètre de génération ou chunk n'est modifié. La commande
+d'aperçu n'écrit aucun état de progression.
+
+## Vérifications et livraison
+
+Les contrôles purs valident les 128 bits de l'UUID, l'ordre et l'accélération,
+la présence d'au plus deux signes dans la première partie, les trajectoires
+circulaires toujours convergentes, l'arrivée des 32 signes au centre avant
+le flash, et la conservation d'une identité interrompue avant son arrivée.
+
+Le parcours client natif complet réussit sur un monde Sanctuary neuf, graine
+`42` : validation de fiche, UUID serveur, absence de joueur pendant l'animation,
+paquets prématurés et doublons ignorés, arrivée sous le blanc, effacement sur
+le monde prêt, reconnexion sans répétition, puis relecture par la commande
+réellement saisie dans le chat. La fin est unique et une fermeture inattendue
+n'envoie pas d'accusé de fin. Le serveur conserve son annonce unique.
+
+Les dernières retouches de rendu (rémanences, courbe circulaire et portail qui
+remplit l'écran) passent ensuite le parcours graphique court, en français et
+en anglais, avec redimensionnement. Elles ne modifient pas l'admission ni le
+raccord du flash déjà testé. Les captures sont inspectées dans
+`build/intro031-evidence/`. Logs : `build/intro031-spiral-client.log` et
+`build/intro031-final-visual.log`.
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,lights`
+réussit sur le code final : **9 tests serveur**, contrôles purs du projet,
+construction et vérification des manifestes passent.
+
+Export local : [Sanctuary-beta.031.mrpack](../build/Sanctuary-beta.031.mrpack),
+**4 810 444 octets**. Les **1 278 classes** du JAR embarqué sont
+identiques au build ; aucune classe de test n'est distribuée. La comparaison
+avec beta.030 borne les changements à l'introduction et à son admission.
+Les instructions des anciens payloads Action/Names/Snapshot restent identiques
+(seules leurs lignes de debug se déplacent). Les classes Demeure/JEI et
+l'archive beta.030 sont préservées.
+
+SHA-256 MRpack : `c7161e024d84ff9709ccaea8f940b79d5f8adba743ecf5f7759c273424297717`.
+Reçu : `build/intro031-artifact.json` ; build : `build/intro031-check.log`.
+Le canal publié et l'instance Prism personnelle ne sont pas modifiés par cet
+export d'essai.
+
+L'introduction reste une proposition à affiner à l'écoute et en partie réelle.
+Aucun essai Windows ni connexion à un serveur distant n'est revendiqué.
+Aucune sauvegarde personnelle n'a servi aux essais.
diff --git a/docs/inventory-beta010.md b/docs/inventory-beta010.md
new file mode 100644
index 0000000..4fa369c
--- /dev/null
+++ b/docs/inventory-beta010.md
@@ -0,0 +1,127 @@
+# beta.010 — Inventaire, contrat préalable de migration
+
+Ticket PROG-02C, branche `codex/progression-inventory`. Portage demandé le
+13 septembre 2026 depuis `../26.2/sanctuary`, consulté en lecture seule.
+
+## Capacité et adoption
+
+Une rangée contient neuf cases. La barre rapide compte dans les six rangées :
+le départ est de neuf cases et le plafond de 54, hors armure et seconde main.
+Inventaire possède cinq achats, aux cinq premiers coûts de compétences
+(1, 2, 4, 8 et 16 niveaux par défaut). Les cinq autres compétences gardent
+leurs sept achats. Les opérateurs en survie suivent les mêmes capacités.
+
+La progression passe du schéma 4 au schéma 5 par ajout de `inventory: 0`.
+Les schémas 1–3 passent d'abord par leurs migrations existantes. Dates,
+coûts, rangs acquis, aptitudes et révision restent inchangés. Le nouveau
+plafond de l'historique est de 42 achats. Aucune migration du terrain.
+
+Les 36 cases vanilla gardent leurs indices et leurs objets. Une case déjà
+occupée dans une rangée non acquise reste visible et permet de retirer son
+contenu ; elle refuse tout ajout. La rangée disparaît de la présentation
+après fermeture du menu si elle est vide et toujours verrouillée. Aucune
+capacité n'est offerte et aucun objet n'est effacé pour adopter la progression.
+Cette règle prépare une réduction future ; aucun New Game+ n'est activé ici.
+
+Les 18 cases supplémentaires sont enregistrées avec les données du joueur,
+dans un champ Sanctuary distinct et versionné, lors du même enregistrement
+que l'inventaire vanilla. Les composants des objets sont conservés. Le format
+26.2 de stockage global n'est pas importé automatiquement : ce ticket porte
+le fonctionnement et les ressources, pas les anciennes sauvegardes 26.2.
+Sans `keepInventory`, les objets supplémentaires suivent la mort native et
+sont retirés de l'ancien inventaire une seule fois. Avec `keepInventory`,
+ils sont repris par le nouveau joueur. Les achats persistent dans les deux cas.
+
+## Interfaces et protocole
+
+Le serveur exige la capacité réseau `sanctuary:inventory_layout_v1` avant
+la création du joueur. Un client ancien est refusé avant le spawn ; un client
+beta.010 refuse également un serveur envoyant une progression antérieure au
+schéma 5. Les migrations sur disque restent possibles côté serveur. Client
+et serveur doivent être mis à jour ensemble pour ce nouveau protocole.
+
+Les emplacements supplémentaires sont ajoutés après tous les emplacements
+natifs des menus, afin de garder les indices des machines, de la fabrication,
+des armures et de la seconde main. Les règles d'insertion sont contrôlées sur
+le serveur, y compris pour les transferts rapides et les clics envoyés par un
+client. Les objets récupérables des anciennes rangées ne deviennent pas une
+réserve dans laquelle on peut déposer à nouveau.
+
+Les six fonds d'inventaire et les variantes des conteneurs proviennent de
+26.2. Les emplacements de cape et d'œuf de compagnon ne font pas partie de
+ce ticket. La fabrication manuelle et les restrictions natives des machines
+restent applicables. Les interfaces et les transferts sont vérifiés sur la
+cible exacte 26.3-pre-2 avant livraison.
+
+## Portage des interfaces
+
+Les ressources copiées sans retouche comprennent six fonds personnels et une
+barre rapide, 132 panneaux (22 familles × six hauteurs), 21 fonds supérieurs
+natifs et le cadre de sélection. Les fonds de machines sont redirigés vers le
+namespace Sanctuary uniquement pendant une partie avec progression active.
+Le livre de recettes est conservé et déplacé dans la zone de fabrication.
+
+La hauteur visible s'adapte aux conteneurs. Si toutes les rangées ne tiennent
+pas dans un petit GUI, la molette au-dessus du panneau fait défiler les rangées
+de stockage ; la barre rapide reste fixe. Le titre indique les rangées acquises,
+un astérisque signale la récupération d'anciennes cases, et `↕` indique la plage
+visible. Le catalogue créatif garde son protocole de 46 emplacements et propose
+un bouton Inventaire pour ouvrir la vue personnelle, avec les capacités acquises.
+
+Les indices internes 36–42 restent réservés à l'équipement Minecraft. L'API
+Inventory adresse les 18 cases supplémentaires par 43–60 ; les indices de menus
+sont distincts et restent ajoutés après les cases natives de chaque conteneur.
+La collecte, le clic de sélection, les ticks d'objets, les recettes et `/clear`
+prennent ces cases en compte. Une recette vérifie la place nécessaire avant de
+restituer sa grille ; les cases verrouillées ne sont pas de la place libre.
+
+## Vérifications
+
+Contrat écrit avant la modification des données. La commande de livraison est :
+
+```sh
+JAVA_HOME=/Users/koka/Library/Java/JavaVirtualMachines/temurin-25.jdk/Contents/Home \
+ ./gradlew :sanctuary:compileGametestJava check build assemblePack \
+ -PsanctuaryFocusedTests=progression,map,demeure,mining,building,inventory
+```
+
+**Livraison finale : `check build assemblePack` réussit en 3 min 55 s,
+avec 38 tests natifs réussis**, dont dix scénarios Inventaire. Le journal est
+`build/inventory010-delivery-complete.log`. Les scénarios couvrent les cinq
+achats, la récupération d’anciennes cases, les 54 cases réelles, les outils
+endommagés, les indices de tous les menus enregistrés, les montures, les
+transferts de machines/coffres, les clics et glisser-déposer verrouillés, la
+fabrication manuelle et par recettes, le clic de sélection, `/clear`, la mort,
+`keepInventory` et la relecture des composants depuis la sauvegarde. Les tests purs couvrent aussi les migrations 1–4 → 5, les
+achats et le plafond. La suite native générale de génération n'est pas relancée
+par cette sélection ; ses limites historiques restent documentées séparément.
+
+Les sources des tests client compilent avec `-PsanctuaryClientTests=true`.
+Le lancement opt-in `:sanctuary:runClientGameTest -PsanctuaryClientTests=true
+-PsanctuaryInventoryClientTests=true` transforme correctement les classes et
+mixins d'inventaire sur le client natif. Le démarrage graphique reste bloqué dans
+`SDL_GL_SwapWindow` au moment de cet essai : le Mac est verrouillé. Le processus
+de développement a été arrêté après capture des traces. **Aucune capture de
+l'interface ni parcours graphique n'est déclaré validé.** Le scénario client
+préparé ouvre les six capacités et les menus natifs sur plusieurs échelles GUI.
+
+Le contrôle de l'archive vérifie les versions internes, les JAR embarqués,
+les textures originales, les libellés FR/EN, les hashes packwiz/Fabric API et
+l'immutabilité des MRpack beta.003–009. Export local uniquement ; aucune
+publication du canal et aucune modification d'instance personnelle.
+
+## Revue suivante
+
+Vérifier ensemble le rendu, la lecture des rangées et la molette en petit GUI.
+La validation de la progression (PROG-03), le contrat des capacités de faction
+et FACTION-01 restent les étapes suivantes. Ce lot ne crée ni faction, ni
+claim, ni recommencement ; les règles de prestige/charges doivent encore être
+précisées dans CYCLE-01.
+
+
+## Artefact local vérifié
+
+- `build/Sanctuary-beta.010.mrpack` — 2,429,141 octets.
+- SHA-256 : `0d9977227a7204381cebc89b28fa544c64659651bfa0bd400e62dedea1266431`.
+- Reçu de vérification : `build/inventory010-artifact.json`.
+- Les archives beta.003–009 restent strictement identiques.
diff --git a/docs/inventory-flow-beta012.md b/docs/inventory-flow-beta012.md
new file mode 100644
index 0000000..71655d7
--- /dev/null
+++ b/docs/inventory-flow-beta012.md
@@ -0,0 +1,121 @@
+# beta.012 — transferts, barres rapides et progression
+
+Ticket sur `codex/inventory-flow-progression`, demandé le 13 septembre 2026.
+Contrat préalable à l'implémentation et à la migration des données.
+
+## Comportement attendu
+
+Les rangées de stockage suivent leur ordre naturel de haut en bas ; la barre
+rapide active reste en bas. Tab fait tourner les contenus des rangées acquises,
+Maj+Tab inverse le sens. La touche est configurable ; E prend la priorité sur
+la fermeture de l'inventaire si elle est choisie, Échap ferme toujours le menu.
+Une rotation complète restitue l'ordre initial. Les anciennes cases verrouillées,
+l'équipement et les objets du conteneur ouvert ne participent pas à la rotation.
+Le serveur valide le menu, sa révision et la capacité ; aucune rotation avec un
+objet porté par le curseur. Il envoie ensuite les objets actualisés au client.
+
+Maj-clic regroupe les piles compatibles dans toutes les rangées autorisées avant
+d'occuper une case vide. Les priorités des machines restent celles de Minecraft.
+Un coffre plein refuse le transfert sans déplacer l'objet dans l'inventaire du
+joueur. Les rangées verrouillées restent utilisables uniquement en récupération.
+Les fonds des coffres et machines partagent la même origine que leurs cases.
+
+Les six compétences affichent une jauge textuelle et leur valeur/plafond. Le
+bouton + apparaît lorsque l'achat est possible. Le bouton − exige la permission
+opérateur réelle du serveur ; il retire un rang de compétence et rembourse le
+prix du dernier achat encore acquis pour cette compétence, enregistré au moment
+de l'achat. Les aptitudes ne sont pas retirables. Chaque retrait est historisé.
+
+## Migration et protocole
+
+La progression passe du schéma 6 au schéma 7. Les migrations 1–5 restent suivies.
+Tous les achats, dates, coûts, compétences, aptitudes et cycles restent conservés.
+Un compteur `revision` explicite est initialisé avec l'ancienne valeur
+`cycle * 64 + history.size()`, puis augmente à chaque achat, retrait et New Game+.
+Le passage de cycle conserve aussi le plancher historique `nouveauCycle * 64`
+pour reprendre les anciens journaux. Une révision déjà utilisée ne redevient jamais valide après un remboursement.
+
+L'historique enregistre aussi les événements `remove_`, avec le coût
+exact remboursé. La relecture vérifie les achats encore acquis, leurs plafonds et
+chaque remboursement. Le journal est limité à 1 024 événements par cycle ; arrivé
+à cette limite, les ajustements sont refusés explicitement. Les retraits
+réservent toujours assez de place pour terminer les six compétences et acheter
+les aptitudes restantes. Le New Game+ archive
+l'ancien historique par le journal de cycle existant et conserve les aptitudes.
+Les anciens événements de cycle restent lisibles par les migrations de progression.
+
+Le format des objets `sanctuary:inventory` reste au schéma 1 : les rotations
+modifient les emplacements existants, sans inventaire parallèle ni permutation
+persistante. Les objets occupés restent récupérables après retrait d'un rang.
+Mort ordinaire : progression et historique conservés, règles d'objets inchangées.
+Aucun terrain, identité, carte, Demeure, faction ou charge n'est modifié.
+
+Client et serveur beta.012 exigent la capacité `sanctuary:inventory_controls_v1`.
+Le snapshot de progression ajoute le droit opérateur et agrandit la borne de son
+historique. Les identifiants existants restent stables. Un ancien client est
+refusé avant le spawn ; mise à jour des deux côtés requise. Export local MRpack
+pour essai, sans publication ni installation personnelle.
+
+## Vérification
+
+La passe graphique `:sanctuary:runClientGameTest -PsanctuaryClientTests=true
+-PsanctuaryInventoryClientTests=true` réussit en 1 min 29 s. Elle crée uniquement
+un monde de développement neuf, de graine 42. Le journal est
+`build/inventory012-client-final.log`.
+
+Elle ouvre les six capacités à deux échelles GUI, puis 24 types de menus natifs.
+Une sonde de test contrôle les coordonnées des vrais appels de dessin du fond,
+comparées à celles des cases. Les coffres sont remplis de blé et de charbon pour
+vérifier leur alignement. Les rangées visibles restent dans l'écran ; les grands
+coffres permettent de faire défiler le stockage en conservant la barre rapide.
+
+Les essais client couvrent aussi le déplacement Tab, son inverse, le remplacement
+par E, la fermeture par Échap, les + masqués sans XP, l'affichage opérateur, l'achat
+d'un cœur puis son remboursement par −. L'XP passe de 30 à 29 puis revient à 30,
+et le serveur enregistre `remove_vitality`. Les contrôles du protocole opèrent
+uniquement sur un serveur Sanctuary ; les serveurs sans Sanctuary restent accessibles.
+
+Captures conservées dans `build/inventory012-evidence/`, notamment le coffre
+`0014_sanctuary-beta012-generic_9x3-minimum.png` et la progression opérateur
+`0039_sanctuary-beta012-progression-operator.png`.
+
+La validation finale serveur et la vérification de l'artefact sont consignées
+ci-dessous. Les tests ne simulent pas 30 clients simultanés.
+Les autres mods communautaires ne sont pas inclus dans cet essai des menus natifs.
+
+
+### Livraison vérifiée
+
+`check build assemblePack` réussit en **4 min 19 s**, avec **49 tests natifs
+réussis** et les tests purs. Commande :
+
+```sh
+JAVA_HOME=/Users/koka/Library/Java/JavaVirtualMachines/temurin-25.jdk/Contents/Home \
+ ./gradlew check build assemblePack \
+ -PsanctuaryFocusedTests=progression,map,demeure,mining,building,inventory,cycle,inventoryflow
+```
+
+Journal : `build/inventory012-delivery-final.log`. Les quatre nouveaux scénarios
+vérifient les regroupements à chacune des six capacités, les coffres pleins,
+les priorités et restes natifs de 24 types de menus sur huit types d'objets,
+les rotations inverses et complètes avec composants et anciennes cases occupées,
+les refus de requêtes périmées, l'autorité opérateur et le remboursement exact.
+Le coût remboursé et l'historique sont relus depuis la sauvegarde native du joueur.
+Les tests de cycle vérifient également l'archivage des retraits au passage de
+prestige ; les tests de progression atteignent le budget maximal d'historique
+sans empêcher de terminer les six compétences. Les assertions historiques qui
+exigeaient de déplacer tout le charbon dans toutes les machines sont remplacées
+par la comparaison avec leurs règles natives ; les montures sont conservées
+avec un conteneur de test aux dimensions exactes de 26.3-pre-2.
+
+La suite serveur de génération générale n'est pas relancée par la sélection
+ci-dessus ; les tests purs de génération inclus dans `check` passent. Aucun
+serveur de 30 habitants n'est simulé.
+
+- Export : `build/Sanctuary-beta.012.mrpack`, **2,496,778 octets**.
+- SHA-256 : `5d7fa635adb7ea5bf32ef361be95f242090912d52d6209398abeac32ea06f87d`.
+- Reçu : `build/inventory012-artifact.json`.
+- Archives beta.003–011 inchangées ; versions internes, JAR imbriqué Demeure,
+ ressources d'origine, traductions et index packwiz vérifiés.
+- Distribution locale pour essai, sans publication du canal ni installation
+ dans une instance personnelle.
diff --git a/docs/inventory-sorting-beta014.md b/docs/inventory-sorting-beta014.md
new file mode 100644
index 0000000..cee183a
--- /dev/null
+++ b/docs/inventory-sorting-beta014.md
@@ -0,0 +1,170 @@
+# beta.014 — rangement, hotbar mobile et préfixe de faction
+
+Ticket demandé le 13 septembre 2026, branche `codex/inventory-sorting-aptitude`.
+Contrat écrit avant l'implémentation et les migrations.
+
+## Comportement
+
+Une aptitude permanente **Rangement** s'achète dans le panneau Aptitudes du
+Blocodex, pour 4 niveaux par défaut (prix configurable côté serveur).
+Elle ne conditionne pas le prestige.
+
+Dans un menu d'inventaire ou de conteneur natif, le clic molette range la zone
+personnelle visée. La touche souris ou clavier se règle dans les contrôles.
+Viser une case de stockage regroupe les piles compatibles puis trie toutes les
+rangées débloquées, y compris celles qui sont masquées par le défilement.
+La hotbar reste en place ; viser une de ses cases trie uniquement ses neuf cases.
+L'ordre est déterministe par identifiant de type d'objet, puis par groupe de
+composants dans l'ordre rencontré ; les piles pleines précèdent les restes.
+Deux objets dont les composants diffèrent ne sont jamais fusionnés.
+
+Les cases de récupération verrouillées, l'équipement, la main secondaire, la
+fabrication et le contenu du conteneur ouvert ne participent pas au rangement.
+Aucun tri avec un objet porté par le curseur ou pendant un glisser-déposer.
+Le catalogue créatif garde ses commandes natives ; la vue d'inventaire personnel
+Sanctuary permet le rangement avec l'aptitude. Le clic de sélection en jeu et le
+défilement de la molette conservent leurs fonctions.
+
+Le serveur vérifie l'aptitude, le menu, son état, la révision de progression,
+la capacité et le numéro de requête. Le client envoie une zone, jamais des objets.
+Les contenus sont préparés sur des copies avant d'appliquer le résultat dans les
+emplacements existants, puis synchronisés. Pas de nouvelle réserve d'objets.
+
+## Migration
+
+La progression passe du schéma 7 au schéma 8 : ajout de `sorting: false` pour
+les habitants existants. Toutes les migrations 1–6 restent suivies. Les rangs,
+achats, coûts, dates, révisions et prestiges sont conservés. Le nouvel achat
+`sorting` rejoint l'historique des aptitudes et survit au changement de cycle.
+Les journaux de cycle existants restent immuables et sont relus par cette
+migration ; leur propre schéma ne change pas.
+
+La configuration passe du schéma 3 au schéma 4, avec `sortingCost: 4` ajouté.
+Les valeurs existantes restent identiques, y compris un prix final explicitement
+fixé à 64 dans le schéma 3. La migration antérieure des schémas 1/2 conserve son
+contrat. Le fichier est validé puis réécrit atomiquement une fois ; un fichier
+invalide est refusé sans remplacement. Le schéma 4 n'est pas réécrit au chargement.
+
+Nouvelle capacité réseau `sanctuary:inventory_sorting_v1`, requise avant le spawn
+sur un serveur Sanctuary. Les identifiants existants restent stables. Client et
+serveur doivent utiliser beta.014 ensemble. Le schéma des 18 cases supplémentaires
+reste identique ; la métadonnée additive de hotbar est précisée ci-dessous.
+Aucune modification de génération, de terrain ou d’expansion.
+
+## Préfixe de faction — ajout demandé pendant le ticket
+
+Le nom de faction précède le nom affiché : `Les Arpenteurs · Poupoutain I`.
+Le préfixe prend la couleur de faction ; le pseudo et le prestige gardent la
+couleur personnelle. Le rendu est commun à la fiche Habitant, au nom affiché
+côté serveur (chat et messages), à la liste Tab et aux noms des joueurs en jeu.
+Quitter une faction ou la dissoudre retire le préfixe ; le nom enregistré du
+compte, son UUID et l'identité enregistrée restent identiques.
+
+Le serveur projette les données du journal de faction dans les noms affichés et
+les diffuse par le paquet natif de liste des joueurs. Pas de modification des
+équipes de scoreboard, ni nouveau format de sauvegarde. La fiche suit aussi
+les mises à jour de faction reçues pendant qu'elle est ouverte.
+
+Les descriptions d'aptitudes ne répètent plus la conservation au New Game+ :
+cette propriété est une règle globale, décrite dans le bilan du cycle.
+
+## Hotbar mobile — correction du contrat beta.012/013
+
+Tab déplace la sélection vers la rangée au-dessus (retour en bas au sommet),
+Maj+Tab vers celle en dessous. La molette sur le stockage suit le même geste.
+Les objets restent dans leurs cases visibles ; le cadre et la case sélectionnée
+se déplacent. Les neuf objets de cette rangée deviennent utilisables en jeu.
+La molette sur la rangée active choisit toujours une de ses neuf cases.
+Le rangement préserve cette rangée, sauf si elle est directement visée.
+
+Le serveur conserve les indices natifs des menus et de l'objet tenu. Une
+permutation entre la rangée active et les neuf cases natives est compensée
+par leur placement à l'écran ; les autres rangées gardent leur contenu et leur
+position. Aucun réservoir d'objets supplémentaire n'est créé. Le tri du stockage
+suit les positions visibles, de haut en bas, en excluant la rangée active.
+
+Migration additive explicite : `sanctuary:hotbar_row`, entier de 0 à 5, est
+enregistré dans le même instantané joueur que les objets. Absent dans beta.013
+et avant, il vaut zéro (rangée de base en bas) et ne réordonne rien au chargement.
+Il est conservé à la reconnexion et avec l'inventaire après la mort. Si la
+capacité diminue sous la rangée active, celle-ci revient en bas et les objets
+gardent leurs positions visibles, les autres rangées devenant de récupération.
+Le schéma du stockage supplémentaire reste inchangé. Un nouvel état réseau
+`sanctuary:hotbar_row` synchronise cette sélection après les cases natives.
+La capacité beta.014 impose des clients et serveurs cohérents. Un stockage
+supplémentaire illisible interdit le tri et les permutations de hotbar sans
+remplacer les données originales.
+
+## Vérifications
+
+Les tests de contrat passent : reprise des progressions 1–7, conservation des
+achats et prix, refus des aptitudes forgées, achat unique de Rangement, budget
+d'historique, conservation après deux cycles et relecture de leur journal.
+Un ancien journal contenant des progressions au schéma 7 se relit sans changer
+ses octets. La configuration migre vers le schéma 4 en conservant les réglages,
+puis un `sortingCost` personnalisé est relu sans réécrire le fichier.
+Journal : `build/inventory014-contracts.log`.
+
+Le test client natif passe avec Minecraft **26.3-pre-2**, Fabric Loader **0.19.5**,
+Fabric API **0.160.0+26.3** et Java 25 :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
+ -PsanctuarySortingClientTests=true -PsanctuaryInventoryClientTests=true
+```
+
+Le parcours crée un monde Sanctuary de laboratoire, graine 42. Trois niveaux
+ne permettent pas l'achat ; quatre activent le bouton de l'aptitude, en FR/EN.
+L'achat depuis le Blocodex dépense exactement quatre niveaux. Le clic molette
+réel fusionne et range le stockage, puis la hotbar séparément. Le raccourci
+remplacé par E range le stockage dans un coffre ouvert, sans fermer le menu et
+sans toucher au contenu du coffre ; Échap ferme le menu. L'état final est
+contrôlé côté serveur. Les préférences du test sont restaurées à la fin.
+
+Le parcours final fait ensuite un cycle complet des six rangées : les **54
+couples coordonnées/contenus restent strictement identiques** à chaque appui
+sur Tab. Le premier appui monte le cadre de 18 pixels, et l'objet tenu natif
+correspond à la rangée choisie. La molette distingue rangée et case sélectionnée.
+Le coffre conserve ses objets, les deux tailles de GUI sont vérifiées, puis
+fermeture et réouverture conservent le cadre et toutes les positions d'objets.
+
+Le New Game+ réel ramène le cadre en bas. La charge gagnée fonde une faction :
+la fiche affiche son préfixe et le prestige I, puis perd le préfixe à la dissolution
+sans être refermée. Le nom client suit le paquet natif. Les descriptions
+individuelles FR/EN ne contiennent aucune mention du New Game+.
+
+Journal final : `build/inventory014-client-final.log` (`BUILD SUCCESSFUL`, 1 min 15 s).
+Dix captures : `build/inventory014-evidence/`. Les tests utilisent les menus
+Minecraft natifs, sans mods communautaires supplémentaires ni serveur à trente
+clients. Les mondes et classes de test restent dans les dossiers ignorés.
+
+
+### Validation serveur et livraison locale
+
+```sh
+./gradlew check build assemblePack \
+ -PsanctuaryFocusedTests=progression,map,demeure,mining,building,inventory,cycle,inventoryflow,sorting
+```
+
+`build/inventory014-release-final.log` : **BUILD SUCCESSFUL**, 4 min 17 s ;
+**56 tests natifs serveur réussis**, avec les tests de contrat du dépôt.
+Ils vérifient les six capacités, les composants et limites de piles, les requêtes
+périmées/refusées, les objets au curseur, le statut spectateur, le prix réel,
+la mort et les sauvegardes. Le cadre mobile conserve les 54 cases à chaque étape,
+le tri exclut la rangée active, la réduction de capacité garde la récupération,
+et les données illisibles refusent les écritures. Le Maj-clic transfère la rangée
+active vers le coffre puis conserve la destination native sur cette rangée.
+Les appartenances de faction, les couleurs, les messages de liste des joueurs
+et les identités immuables sont vérifiés côté serveur.
+
+Le MRpack contient le JAR vérifié de Sanctuary et Demeure embarqué, ainsi que
+la dépendance Fabric API épinglée. Les classes de test et mondes sont absents.
+Les textures importées de 26.2 et les icônes sont identiques aux originaux.
+Les exports beta.003–013 conservent leurs empreintes. Il s'agit d'un export
+local de test ; aucun canal publié ni aucune instance personnelle n'est modifié.
+
+- [Sanctuary-beta.014.mrpack](../build/Sanctuary-beta.014.mrpack)
+- Vérification : `build/verify-beta014-pack.py` ; reçu : `build/inventory014-artifact.json`.
+- Taille : 2,527,741 octets.
+- SHA-256 MRpack : `9bc77d9fcdcc812d64f2c95d1270e00adc94b90bc08e055f3314e2950761ac49`.
+- SHA-256 JAR : `3eb4ac92de0ad671592ae79ed65f00547184d771ad72603952edf7bd86e0352c`.
diff --git a/docs/lan-carry-beta042.md b/docs/lan-carry-beta042.md
new file mode 100644
index 0000000..0d7140d
--- /dev/null
+++ b/docs/lan-carry-beta042.md
@@ -0,0 +1,67 @@
+# beta.042 — portage sur la tête et commandes LAN
+
+Branche `codex/lan-carry-beta042`. Deux signalements : commandes inutilisables
+pour l'hôte après activation tardive en LAN ; joueurs assis au-dessus de la tête.
+
+## Portage
+
+L'origine des pieds était placée sur la tête du porteur, puis le modèle gardait
+son bassin environ 0,7 bloc plus haut. L'ancrage utilise désormais le bassin
+de la pose assise native et le sommet de la tête du porteur. Il tient compte
+de l'échelle des deux joueurs, de l'accroupissement du porteur et des piles.
+Le passager conserve sa pose assise lorsqu'il maintient Maj au moment de monter.
+
+La même géométrie est utilisée côté client et serveur, pour la position réelle
+et les vérifications de place sous un plafond. Les animaux gardent leur ancrage
+sur la tête ; les bébés restent exempts de ralentissement selon beta.041.
+Pas de nouveau format de sauvegarde, pas de texture, pas de migration.
+
+## Commandes LAN — diagnostic
+
+Mise à jour beta.044 : la commande précise fournie ensuite est
+/sanctuary time vanilla. L'erreur à la position 10 est reproduite dans le chat
+natif : la commande locale d'introduction intercepte la racine Sanctuary.
+Le correctif et les vérifications figurent dans
+[le ticket beta.044](spawn-eggs-audit-beta044.md).
+
+
+Le scénario natif sur le code beta.041 ouvre un monde plat de développement
+avec les commandes désactivées, ouvre le LAN depuis World Options, puis active
+Allow Commands et applique les changements. L'hôte est reconnu avec les droits
+OWNERS, reçoit l'arbre des commandes et peut exécuter depuis son client
+`experience set @s 17 levels`. Résultat : 17 niveaux attestés côté serveur.
+Ce premier scénario ne reproduit donc pas le signalement.
+
+Le message d'erreur et la commande précise ont été demandés à l'utilisateur.
+Aucune règle d'autorisation n'est modifiée sans reproduction. Les invités ne
+reçoivent pas de droits supplémentaires lors de cette vérification.
+Il s'agit du serveur intégré d'un client de développement, sans serveur dédié
+ni nouvelle acceptation d'EULA. Aucun monde personnel n'est ouvert.
+
+## Vérifications
+
+`./gradlew check build assemblePack assembleTestPack
+-PsanctuaryFocusedTests=carry,familiar,movement,operator` passe avec **39 tests
+serveur natifs** : ancrage du bassin, pile à plusieurs joueurs, échelles,
+accroupissement, plafond, démontage, dégâts entre porteurs, poids des bébés,
+familiers et règles opérateur existantes.
+
+Le test de la beta.020 qui exigeait les pieds au-dessus de la tête a été mis à
+jour : il vérifie maintenant le bassin. Le parcours graphique contrôle les
+coordonnées des modèles Minecraft natifs, les jambes repliées et le contact
+bassin/tête à chaque étage. Deux captures sont conservées dans
+`build/lan-carry042-evidence/`.
+
+Diagnostic initial : `build/lan-carry042-baseline3.log` ; validation serveur :
+`build/lan-carry042-check.log`. Le parcours client et ses preuves figurent dans
+`build/lan-carry042-client-first.log`.
+
+Un essai supplémentaire de saisie au clavier dans le chat est resté bloqué
+avant la création du monde, dans `SDL_GL_SwapWindow`, puis le processus a reçu
+SIGTERM. Il ne constitue pas une validation supplémentaire ; sa trace reste
+dans `build/lan-carry042-client.log` et `build/lan-carry042-client-threads.txt`.
+Le parcours réussi valide déjà le paquet de commande émis par le client réel.
+
+Exports vérifiés : [pack normal](../build/Sanctuary-beta.042.mrpack) et
+[profil plat](../build/Sanctuary-Test-beta.042.mrpack).
+Reçu : `build/lan-carry042-artifact.json`.
diff --git a/docs/menus-discovery-beta033.md b/docs/menus-discovery-beta033.md
new file mode 100644
index 0000000..fd57859
--- /dev/null
+++ b/docs/menus-discovery-beta033.md
@@ -0,0 +1,75 @@
+# UI-033 — menu pause, Discovery et catalogue
+
+Branche `codex/menus-discovery-beta033`, à partir du binaire beta.032.
+Le ticket regroupe les corrections demandées sur la flamme du gâteau porté,
+les libellés et l'accès à la configuration JEI, puis la navigation et les droits
+de Discovery. Les parcours client normal et monde plat sont validés.
+
+## Contrat
+
+Le menu pause conserve ses entrées natives de retour, advancements, statistiques,
+options, options du monde et déconnexion. Discovery, Progression, World et
+Inhabitant remplacent Blocodex ; Factions et History disposent d'entrées directes,
+côte à côte, avec History à droite.
+World ouvre la carte. Les pages consacrent leur en-tête à leur titre et gardent
+Done, sans répéter les quatre onglets. Prestige remplace le libellé technique
+Cycle et son compteur dans Progression. L'historique reste accessible.
+
+Discovery s'ouvre sur Owned. Les entrées inconnues et les outils de maintenance
+ne doivent pas être exposés à un habitant. L'élargissement du filtre utilise
+l'aptitude Catalogue existante, achetée pour 4 niveaux. Le créateur a confirmé
+que « All Blocks » ne montre que les blocs déjà découverts, jamais les inconnus.
+Les snapshots serveur limitent aussi ce qui est transmis, indépendamment du GUI.
+Les autorisations serveur restent la référence pour les opérations administratives.
+
+Aucun changement de sauvegarde, de génération ou de stock d'objets.
+Les données de découvertes déjà conservées et les achats existants restent valides.
+Le fork JEI garde son attribution et sa licence MIT ; seuls les accès et libellés
+visibles dans le jeu changent. Distribution locale de test, aucun déploiement.
+
+## Parcours client vérifiés
+
+Les essais utilisent les vrais menus, les échanges réseau et le rendu de
+Minecraft 26.3-pre-2. Le menu pause tient aux échelles GUI 1, 2, 3 et 4 ;
+World ouvre directement la carte et Factions son panneau. Les pages conservent
+Done et l’historique reste accessible depuis Habitant et la carte.
+
+Un joueur en survie sans commandes ne dispose ni de l’inspection opérateur ni
+du filtre All Blocks avant achat. Une requête opérateur directe est refusée
+par le serveur. L’achat réel débite quatre niveaux puis rend disponibles les
+blocs vus mais jamais possédés ; aucune entrée inconnue n’est transmise.
+Les libellés favoris/historique passent le contrôle en français et en anglais,
+et le bouton de configuration du catalogue n’est ni dessiné ni interactif.
+La capture du gâteau porté montre la flamme au bout de sa bougie bleue.
+
+Journaux : `build/menus033-client2.log` et
+`build/menus033-quick-client-final.log`. Captures : `build/menus033-evidence/`.
+Ces essais ne remplacent pas les tests d’équilibrage des familiers en partie.
+
+## Build et contrôles serveur
+
+`./gradlew check build assemblePack assembleTestPack` avec la sélection
+`-PsanctuaryFocusedTests=menus,recipes,progression,cycle,cosmetics` réussit :
+**34/34 tests serveur** et les contrôles purs du dépôt passent. Les trois nouveaux
+scénarios vérifient le filtre serveur avant/après achat, l’indépendance entre
+recettes et découvertes, ainsi que la reprise d’anciens achats et le refus d’un
+reçu malformé sans réécriture des données.
+
+L’ajout final du bouton **History / Historique**, à droite de Factions, est
+vérifié par le parcours natif sur monde plat aux quatre échelles GUI. Le clic
+ouvre directement le journal et Done ramène au menu pause. Ce changement
+concerne le client et les traductions ; les contrôles serveur déjà réussis n’ont
+pas été répétés. Les binaires sont reconstruits avec
+`./gradlew build assemblePack assembleTestPack -x check` après ce parcours.
+Journaux : `build/menus033-check-final.log`,
+`build/menus033-quick-client-history.log`, `build/menus033-assemble-history.log`.
+
+## Livraison locale
+
+[Sanctuary-beta.033.mrpack](../build/Sanctuary-beta.033.mrpack) contient les
+corrections finales, dont History à droite de Factions. Les archives, les
+1 304 classes compilées Sanctuary et les JAR imbriqués Demeure/JEI ont été
+comparés aux builds ; les classes de familiers, génération et éclairage dynamique
+restent identiques à beta.032. Reçu : `build/menus033-artifact.json`.
+Le [pack de test plat](../build/Sanctuary-Test-beta.033.mrpack) contient exactement
+le même mod avec le module de test supplémentaire. Aucun déploiement effectué.
diff --git a/docs/mining-beta008.md b/docs/mining-beta008.md
new file mode 100644
index 0000000..cc6ffae
--- /dev/null
+++ b/docs/mining-beta008.md
@@ -0,0 +1,129 @@
+# PROG-02A — Minage et vein mining — beta.008
+
+Branche : `codex/progression-vein-mining`. Ticket implémenté et export local
+vérifié le 13 septembre 2026. Cible inchangée : Minecraft 26.3-pre-2 / Fabric.
+
+## Contrat préalable de données
+
+La progression personnelle passe au schéma 3 en ajoutant `mining`, entier
+de 0 à 7. Les schémas 1 (beta.003/004) et 2 (beta.005–007) sont lus selon
+leurs champs stricts, puis reçoivent `mining: 0`. Le schéma 1 reçoit aussi
+`atlas: false` selon son contrat antérieur. Aucun rang existant, achat, date
+ou coût historique n'est modifié. La migration ne crée pas d'achat fictif.
+
+L'historique accepte sept achats `mining` supplémentaires, soit 30 événements
+au maximum. Chaque rang doit toujours correspondre à son nombre exact d'achats.
+À la restauration du joueur, la représentation canonique est réattachée au
+même champ persistant `sanctuary:progression` et enregistrée avec sa prochaine
+sauvegarde Minecraft. La migration est idempotente. Une donnée invalide refuse
+la restauration au lieu de réinitialiser la progression.
+
+Les profils d'habitants, couleurs, Atlas, Demeure, XP non dépensée et fichiers
+de configuration existants sont conservés. `statCosts` finance aussi Minage :
+par défaut 1, 2, 4, 8, 16, 32 puis 64 niveaux. La configuration reste au schéma 2.
+Le paquet de progression conserve son identifiant et transporte le nouveau
+JSON ; le client et le serveur doivent utiliser beta.008 ensemble. Les anciennes
+versions ne lisent pas le schéma 3 : un retour arrière exige une sauvegarde
+antérieure des données de joueur. Aucun monde personnel n'est ouvert ici.
+
+La mort ordinaire et la reconnexion conservent Minage comme les autres rangs.
+Aucun New Game+ ou reset n'est activé dans ce ticket. La génération, les chunks,
+les expansions et les identifiants existants restent inchangés.
+
+## Fonctionnement implémenté
+
+- Rangs 0–7, plafonds de 1 / 4 / 8 / 16 / 32 / 64 / 128 / 256 blocs.
+ Le rang 0 garde le geste de minage individuel.
+- Courbe d'essai : vitesse Minecraft au départ, puis 115, 130, 145, 160, 175,
+ 187,5 et 200 %. Le modificateur natif `BLOCK_BREAK_SPEED` compose avec les
+ outils, enchantements, effets et autres mods ; leurs modificateurs restent
+ présents. La courbe historique 25–100 % n'est pas appliquée par défaut.
+- Blocs identiques reliés par les faces, dans une limite de sept blocs sur
+ chaque axe autour de la cible. Coffres et autres blocs à entité, blocs
+ indestructibles et chunks absents sont exclus. Aucun chunk n'est chargé
+ pour trouver une veine.
+- Touche configurable maintenue et clic gauche ; aperçu avant de commencer,
+ taille ajustable dans les rangs achetés. B reste le Blocodex.
+- Par défaut **R maintenu** affiche la veine ; **R + clic gauche maintenus**
+ la minent. **R + molette** choisit un palier inférieur parmi les rangs acquis
+ sans changer de case de barre rapide. Le prochain rang se voit dans l'infobulle
+ de la compétence ; la touche affichée suit son réglage dans les contrôles.
+- Le serveur recalcule la sélection, vérifie l'origine visée, les permissions,
+ l'outil, les états de blocs et la durée. Relâcher, changer d'outil, s'éloigner,
+ mourir ou se déconnecter interrompt l'action.
+- La destruction passe par Minecraft pour les objets, l'XP, la durabilité,
+ la faim, les enchantements, les statistiques et les événements de mods.
+ L'aperçu et les actions refusées ne créditent aucune activité.
+
+La durée de chaque bloc est cumulée avant la casse du groupe, puis les blocs
+sont détruits par lots d'au plus 16 par tick. Les états et permissions sont
+revérifiés ; une modification concurrente annule les travaux devenus obsolètes.
+Un outil remplacé, même par un exemplaire identique, ne reprend pas une charge.
+La rupture de l'outil arrête les blocs restants. Chaque geste a un identifiant
+éphémère et des maintiens espacés de cinq ticks ; sans maintien pendant plus de
+12 ticks, il expire. Un geste terminé ou refusé ne redémarre pas sur son maintien.
+Le passage au geste groupé annule aussi le minage individuel différé de Minecraft.
+
+Les sons et particules de casse du groupe remplacent uniquement l'événement
+audiovisuel natif de ces blocs, pour ne pas jouer 256 sons simultanés. Les
+événements de jeu, callbacks de protection, statistiques, objets et XP suivent
+toujours la destruction Minecraft. Le créatif garde ses règles natives sans loot
+ni usure ; être opérateur en survie n'accorde aucune de ces exemptions.
+
+Références GPL-3.0-or-later en lecture seule : `../26.2/sanctuary`, sélection
+`SanctuaryVeinSelection`, session `SanctuaryMiningSession`, limites communes
+et aperçu `SanctuarySelectionPreviewRenderer`. Le port est compilé et testé sur
+les API exactes de 26.3-pre-2 : attribut natif de vitesse synchronisé, nouvelle
+signature d'animation d'attaque, paquet de geste et rendu des contours par Gizmos.
+
+## Vérifications
+
+- Compilation et contrôles de contrat `progression003Smoke` / `atlas005Smoke`
+ réussis. Les anciennes données, leurs achats et configurations sont relus,
+ et la migration Minage est contrôlée avec ses rangs, son plafond et ses refus.
+- Parcours client natif :
+ `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryProgressionClientTests=true`,
+ réussi en **2 min 15 s**. Deux achats par clic réel, aperçu de huit puis quatre
+ blocs, molette native sans changement d'outil, annulation, second geste avec
+ huit casses et huit usages d'outil, reconfiguration de R vers G, interruption
+ par B, mort et reprise des cinq achats sont vérifiés. La matrice de GUI et
+ le parcours Atlas précédents restent inclus. Captures inspectées et conservées
+ dans `build/beta008-preview/` ; journal `build/beta008-client-retry.log`.
+- Un premier client a été arrêté avant le lancement des tests, bloqué dans
+ `SDL_GL_SwapWindow` pendant le chargement initial. Le nouvel essai a désactivé
+ la VSync dans les options de développement uniquement ; cette option a été
+ remise à sa valeur précédente après le succès. Aucun réglage de ce diagnostic
+ n'est distribué. Pile native : `build/beta008-client-threads.txt`.
+- Le premier essai serveur a détecté deux erreurs de montage des tests : la
+ contribution Demeure devait être additionnée sur les chunks traversés, et
+ l'avance simulée d'un joueur faisait avancer les autres tests asynchrones.
+ La simulation est maintenant isolée par joueur ; le scénario à deux mineurs
+ les fait avancer alternativement. Le scénario créatif a aussi été corrigé :
+ le joueur simulé doit être créé dans ce mode, car le mock Minecraft conserve
+ le mode fourni à sa création malgré un appel ultérieur à `setGameMode`.
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,map,demeure,mining`
+ réussi en **4 min 4 s**, dont **18 tests serveur natifs obligatoires réussis**.
+ Les dix scénarios Minage couvrent les achats, limites, protections Fabric,
+ annulations, interruptions, deux mineurs concurrents, expiration du maintien,
+ blocs instantanés, créatif, usure, mauvais outil, Toucher de soie et Fortune.
+ Ils vérifient aussi les crédits Blocodex/Demeure, statistiques, faim et XP
+ issus des destructions natives. Les contrôles purs du dépôt sont inclus.
+ Journal : `build/beta008-delivery-final.log`.
+
+Les deux assertions worldgen générales connues en beta.003/005 ne sont pas
+revendiquées corrigées par ce ticket ; la sélection de tests serveur ci-dessus
+reste ciblée sur progression, carte, Demeure et Minage.
+
+## Artefact local
+
+- [Sanctuary-beta.008.mrpack](../build/Sanctuary-beta.008.mrpack), **2 331 912 octets**.
+- SHA-256 : `760176daa164fd28ece77008dc0442cfc5539c997f9a9696ea2cea6edf21bcf3`.
+- Reçu : `build/mining008-artifact.json` ; somme séparée :
+ `build/Sanctuary-beta.008.mrpack.sha256`.
+- Export packwiz vérifié : CRC, manifeste et versions internes, identité du JAR
+ construit et du JAR Demeure embarqué, classes et mixins Minage, traductions
+ FR/EN, six icônes originales, index et dépendance Fabric API exacts, absence
+ de classes de test. Les exports beta.003 à beta.007 sont restés identiques.
+
+Cet export n'a pas été publié sur le canal packwiz ni installé dans une instance
+personnelle. Les mondes utilisés pour les vérifications sont ceux du développement.
diff --git a/docs/mob-head-blocks-beta060.md b/docs/mob-head-blocks-beta060.md
new file mode 100644
index 0000000..48c5202
--- /dev/null
+++ b/docs/mob-head-blocks-beta060.md
@@ -0,0 +1,100 @@
+# beta.060 — objets et blocs sur les mobs
+
+Ticket `codex/mob-head-blocks-beta060`.
+
+## Contrat avant implémentation
+
+Les mobs peuvent porter un exemplaire d'un objet dans un emplacement distinct
+de leur équipement natif. L'IA continue de se déplacer et d'agir normalement.
+Le geste confirmé est Maj + clic droit avec l'objet pour équiper, clic droit
+pour utiliser, Maj + clic droit à main vide pour récupérer. La tête vide
+retrouve le geste de portage existant. Aucun achat d'aptitude supplémentaire.
+
+Les blocs réutilisent les interactions de tête existantes : stockage public,
+établi, four, levier, bouton, torche de redstone, etc. Un seul emplacement,
+aucun remplacement silencieux. Le retrait et la mort rendent l'objet et le
+contenu ordinaire ; une Shulker garde son contenu une seule fois. Les mobs
+hostiles gardent leur comportement et leur armure. Les familiers conservent
+leur IA et leurs règles d'appartenance ; leur disparition restitue l'objet.
+
+La source redstone est indexée dans la cellule de la tête, sans poser de bloc.
+Une poule équipée d'une torche alimente les circuits voisins au fil de ses
+déplacements. Un levier ou bouton peut être actionné par un joueur. Les coups
+déclenchent les réactions déjà prévues des blocs portés. Les distances,
+menus opérateur et contrôles du serveur restent applicables.
+
+## Données et compatibilité
+
+Ajout du champ `sanctuary:mob_head`, schéma 1, aux seules entités équipées.
+Il contient l'exemplaire et l'état du bloc natif (même contrat de session que
+les joueurs). Les anciennes entités sans champ restent sans objet. Les données
+illisibles sont conservées et leur équipement est bloqué jusqu'à réparation.
+Les formats des joueurs, inventaires, générations et chunks ne changent pas.
+Déchargement : sauvegarde et fermeture des menus, suppression du signal local ;
+rechargement : restauration sans duplication. Aucune migration de monde existant.
+Un retour à une version antérieure ne doit pas être fait en sauvegardant les
+mobs ainsi équipés : récupérer leurs objets avant ce retour.
+
+## Vérifications
+
+Le parcours `MobHead060ClientChecks` passe sur Minecraft 26.3-pre-2, dans un
+monde plat de développement, graine 42. Les commandes sont désactivées après
+la création pour vérifier l'accès d'un joueur normal.
+
+- Maintien réel de la touche d'accroupissement et paquet natif d'interaction :
+ une seule torche transférée, aucune mise en portage, récupération à main vide,
+ puis portage de la poule redevenue disponible.
+- Deux lampes déjà posées : allumage à proximité, extinction après déplacement
+ et signal présent dans la nouvelle cellule. Suppression immédiate de la
+ source lors d'un déchargement natif d'entité, puis restauration après lecture
+ de son NBT et rechargement.
+- Vrai menu de coffre à trois rangées, sept diamants conservés par sauvegarde,
+ fermeture du visiteur et restitution exacte au retrait. Shulker rendue une
+ seule fois avec cinq émeraudes ; mort avec un baril et quatre lingots, sans
+ duplication lors d'un second nettoyage.
+- Levier, bouton, pot fleuri, distributeur tirant une flèche après un vrai coup,
+ four fondant du fer avec du charbon et TNT consommée dans une seule entité
+ amorcée. Le coffre porté par le joueur reste fonctionnel.
+- Extraction des modèles natifs adultes/bébés pour poule, vache, zombie,
+ abeille, slime, morue, Wither et Ender Dragon ; objet ordinaire affichable.
+ Libellés FR/EN et conservation d'un enregistrement invalide vérifiés.
+- Captures de la poule avec sa torche, des lampes et du coffre ouvert inspectées
+ dans `build/mob-head060-evidence/`.
+
+Commande native réussie en 1 min 10 s :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -x :sanctuary:runGameTest \
+ -PsanctuaryClientTests=true -PsanctuaryMobHead060ClientTests=true \
+ -PsanctuaryQuickTests=true
+```
+
+Journal : `build/mob-head060-client.log`, marqueur `MOB_HEAD060_PASS`.
+`./gradlew check build assemblePack assembleTestPack`, avec les mêmes propriétés
+et l'exclusion du serveur dédié, réussit en 2 min 15 s (122 tâches).
+Journal : `build/mob-head060-check.log`.
+
+Les deux exports packwiz sont vérifiés par `build/verify-mob-head060.py` :
+1 620 classes conformes au build, références compilées aux nouvelles signatures
+contrôlées, ressources de jeu et JEI préservés, archives beta.059 intactes.
+Reçu : `build/mob-head060-artifact.json`.
+[Pack normal](../build/Sanctuary-beta.060.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.060.mrpack).
+
+## Limites de cette livraison
+
+Les blocs gardent les mêmes limites que sur les joueurs : lit décoratif,
+piston local sans poussée de terrain, absence de random ticks pour les cultures
+et conditions natives requises pour les structures multiblocs. Un objet porté
+ne remplace pas l'IA : les déplacements restent ceux du mob. Les familiers
+éphémères rendent leur objet au sol lorsqu'ils disparaissent ; l'objet ne se
+stocke pas dans leur œuf.
+
+Les modèles ordinaires suivent leur tête animée. Les rendus spécialisés sans
+modèle de créature standard, notamment le dragon, ont un placement de repli
+près de leur hauteur de regard, qui pourra demander un ajustement visuel.
+Le test des variantes est une extraction native ; seules la poule et son
+interface de coffre ont fait l'objet d'une inspection visuelle de capture.
+Aucune session avec plusieurs clients humains ou mesure de FPS n'est revendiquée.
+Aucune installation personnelle, publication de canal ou acceptation d'EULA
+de serveur dédié.
diff --git a/docs/multiplayer-hello-beta040.md b/docs/multiplayer-hello-beta040.md
new file mode 100644
index 0000000..d3dc384
--- /dev/null
+++ b/docs/multiplayer-hello-beta040.md
@@ -0,0 +1,35 @@
+# beta.040 — crash à l'arrivée en multijoueur
+
+Le rapport transmis le 14 septembre 2026 concerne beta.039, Minecraft
+26.3-pre-2 : `NullPointerException: Components not bound yet` dans
+`StarterCompanions.egg`, appelé par le dessin d'une case du Hello World.
+
+Un client qui rejoint directement un serveur peut afficher cette phase de
+configuration avant que les composants des items soient liés. Créer un
+`ItemStack` à ce moment échoue. Un essai après création d'un monde solo masque
+le problème, puisque le serveur intégré a déjà initialisé les composants.
+
+Les trois cases dessinent désormais directement les textures d'œufs natives,
+déjà disponibles via le chargement des ressources client. Les 88 textures ont
+été vérifiées dans les ressources de cette version exacte. Les noms, descriptions,
+raretés, choix définitif et couleurs gardent leur comportement. Il ne s'agit
+pas de capturer l'exception et cacher un œuf : aucun `ItemStack` n'est construit
+pendant cet aperçu de configuration.
+
+Les œufs réellement remis en jeu sont toujours créés par le serveur après
+l'admission. Aucun registre, paquet, tirage de familier ni fichier de sauvegarde
+n'est modifié pour ce correctif. Le Hello World reste avant l'apparition en jeu.
+
+## Vérification
+
+Le parcours démarre sur un client à froid, confirme que la construction d'un
+œuf reproduit l'ancienne exception, puis affiche et utilise les cases du Hello
+World pendant plusieurs images sans composants d'items. La suite vérifie
+l'admission et la reconnexion en serveur intégré. La connexion réseau complète
+à un serveur dédié n'a pas été vérifiée : l'EULA reste non acceptée, conformément
+au choix de l'utilisateur. Aucun serveur personnel n'est contacté.
+
+Parcours client réussi : `build/operator040-client-final.log`, témoin
+`MULTIPLAYER040_CLIENT_PASS`. Capture :
+`build/operator040-evidence/final/0000_sanctuary-beta040-cold-multiplayer-welcome.png`.
+Reçu : `build/operator040-artifact.json`.
diff --git a/docs/notifications-beta034.md b/docs/notifications-beta034.md
new file mode 100644
index 0000000..1a87ad6
--- /dev/null
+++ b/docs/notifications-beta034.md
@@ -0,0 +1,103 @@
+# HUD-034 — découvertes et suivi natif
+
+Branche `codex/notifications-beta034`, base beta.033.
+
+## Contrat retenu
+
+Le créateur confirme un **style F3 visible pendant le jeu normal**, en bas à droite.
+Les premières observations de blocs, possessions de blocs et découvertes d’objets
+ou recettes produisent des messages courts qui s’empilent puis s’effacent.
+Les lots de recettes se regroupent ; la file et le nombre de lignes sont bornés.
+Les acquisitions répétées et la reconnexion ne rejouent pas l’historique.
+
+Les petites barres utilisent les sprites natifs d’expérience. Elles décrivent les
+collections réelles, sans attribuer d’XP supplémentaire. Le suivi des advancements
+Minecraft affiche les objectifs visibles déjà commencés, avec leurs critères natifs.
+Il utilise les données reçues du serveur, sans modifier les récompenses ni les toasts.
+
+Options client persistantes : fil de découvertes, barres de collections, suivi des
+advancements et durée de lecture. Les menus et F1 masquent le HUD ; les messages
+attendent la fermeture du menu. Les inconnus ne sont pas révélés par ces messages.
+
+Quêtes, bounties, rubis/saphirs et panneaux ne sont pas ajoutés dans ce ticket.
+Le centre sépare messages temporaires et suivis persistants pour accueillir leurs
+futurs événements confirmés par le serveur. Aucun faux objectif n’est affiché.
+Les règles de progression, sauvegardes et générations existantes restent inchangées.
+
+## Retours intégrés pendant le ticket
+
+Les boutons Factions et History sont retirés du menu pause, à la demande du
+créateur. Ils restent accessibles par Progression → Prestige.
+
+La hotbar de l’inventaire personnel utilisait une texture de 182×22 comprimée
+sur 164×22. Elle reprend le sprite natif du HUD en **182×22**, neuf cases espacées
+de **20 pixels**, et des objets rendus en **18×18** au lieu de 16×16. La rangée
+sélectionnée réserve sa hauteur lorsqu’elle se déplace ; les autres rangées,
+l’équipement et les index de sauvegarde restent inchangés. Clics, Maj-clics,
+balayage et tri utilisent les mêmes surfaces agrandies. Les interfaces de
+conteneurs conservent leur grille compacte et leurs emplacements natifs.
+
+Les objets au sol visibles à huit blocs maximum alimentent aussi la connaissance
+des objets existante, avec occultation par les blocs et sans acquisition simulée.
+
+## Utilisation
+
+**Options → Notifications** permet de désactiver séparément les messages de
+découverte, leurs trois barres de collection et le suivi des progrès Minecraft.
+La durée se règle entre 3 et 15 secondes, avec 5 secondes par défaut. Les choix
+restent dans `config/sanctuary-notifications.json`, propres à chaque installation.
+Les barres de collection accompagnent le fil temporaire ; jusqu’à deux objectifs
+Minecraft incomplets restent suivis après sa disparition. Les sous-titres activés
+réservent une marge supplémentaire en bas de l’écran.
+
+Les messages affichent au plus cinq entrées. Le serveur regroupe les nouveautés
+toutes les dix ticks, à raison de 32 entrées maximum par envoi. Le compteur de
+recettes disponibles vient du serveur : le registre des recettes complet n’est
+pas exposé par le registre client de Minecraft 26.3-pre-2. Le titre d’une recette
+déjà découverte utilise le catalogue chargé de JEI, avec un libellé de secours.
+Les objectifs lisent les données natives une fois par seconde ; le dessin du HUD
+ne relance aucun parcours du monde ou du catalogue de recettes.
+
+Pour essayer : observer un nouveau bloc, ramasser un nouvel objet puis découvrir
+une famille de recettes. Reprendre le même objet ne doit pas rejouer sa première
+acquisition. Ouvrir l’inventaire met le fil en attente ; sa hotbar agrandie reste
+cliquable jusqu’au bord de la neuvième case, et Tab la déplace toujours de rangée.
+
+## Vérification graphique
+
+Le parcours client natif sur monde plat valide les notifications, les lots de
+recettes, leur expiration, les masquages F1/menu, le suivi d’un advancement
+Minecraft partiellement accompli, les options persistantes en FR/EN et les
+échelles GUI 1 à 4 de l’écran d’options. Il vérifie aussi l’absence de Factions et
+History dans Échap, et leur accès par Progression → Prestige.
+
+La hotbar est vérifiée à une et six rangées, puis après son déplacement. Les
+clics et Maj-clics au bord agrandi de la neuvième case sont exercés en client
+natif, ainsi que le défilement entre rangées, la sélection de case et les offres
+du villageois. Les captures sont conservées dans `build/notifications034-evidence`.
+
+## Livraison vérifiée
+
+Minecraft **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**.
+`check build assemblePack assembleTestPack` réussit, avec la sélection native
+`notifications,menus,recipes,progression,inventoryflow` : **22 tests serveur**,
+dont première observation/possession, absence de répétition, isolation entre
+joueurs, lots bornés et occultation des objets par les murs. Le test de file
+exerce aussi une rafale de 10 000 événements, les priorités et l’expiration.
+Le parcours client décrit ci-dessus réussit séparément.
+
+- [Pack normal beta.034](../build/Sanctuary-beta.034.mrpack).
+- [Pack test rapide beta.034](../build/Sanctuary-Test-beta.034.mrpack) : monde plat,
+ fiche d’habitant conservée et cinématique passée automatiquement.
+
+Les ZIP, métadonnées, 1 320 classes Sanctuary, trois classes du module test,
+ressources et dépendances imbriquées sont vérifiés. Le second pack ne diffère
+du premier que par son nom et l’ajout du module facultatif de test. JEI reste
+en `30.32.0-sanctuary.2`. Les anciennes archives beta.032 et beta.033 sont
+inchangées ; aucun monde, canal packwiz ou instance Prism personnelle n’a été
+modifié. Client et serveur doivent utiliser ensemble beta.034.
+
+SHA-256 normal : `df0a0c2ab937ccec4361bd37a07a551a592b3d5819ade1f289fa9fb428c355e5`.
+SHA-256 test : `cf41d20a118128afd044556e6dee44804852ea7232f2b13623020ec471c3b4fc`.
+Reçu : `build/notifications034-artifact.json`. Journaux :
+`build/notifications034-check-final.log` et `build/notifications034-client-final.log`.
diff --git a/docs/operator-inhabitant-stars-beta040.md b/docs/operator-inhabitant-stars-beta040.md
new file mode 100644
index 0000000..ed5123e
--- /dev/null
+++ b/docs/operator-inhabitant-stars-beta040.md
@@ -0,0 +1,118 @@
+# beta.040 — opérateur, habitant et ciel partagé
+
+Branche `codex/operator-inhabitant-beta040`. Ticket issu des demandes du 14 septembre 2026.
+
+## Contrat du mode opérateur
+
+Dans une partie intégrée, **World Options → Allow Commands → Apply Changes**
+active ou désactive le mode opérateur Sanctuary. Le réglage natif enregistré
+fait autorité ; le mode créatif seul ne constitue pas une autorisation.
+Il n'existe plus de second interrupteur opérateur propre à la carte ou aux
+recettes. Sur serveur dédié, les droits sont ceux des opérateurs Minecraft :
+un joueur distant ne peut pas s'attribuer ces droits depuis son interface.
+
+Sans commandes, les contrôles réservés aux opérateurs sont invisibles et leurs
+actions sont refusées côté serveur. Avec commandes, l'inspection de la carte,
+le catalogue complet, les recettes complètes, les remboursements de compétences
+et la remise à disposition du pouvoir du familier deviennent accessibles.
+Le catalogue indique explicitement son mode opérateur. Couper les commandes
+rétablit les filtres de découvertes et recettes, efface le terrain inspecté et
+retire les contrôles, y compris lorsque le serveur intégré est en pause.
+Les inspections n'accordent ni aptitude, ni découverte, ni recette persistante.
+
+Les commandes d'administration Sanctuary utilisent la même règle. Les
+outils de configuration native du monde réservés aux opérateurs sont masqués
+hors de ce mode ; le réglage Allow Commands reste accessible à l'hôte selon
+les conditions natives du jeu (notamment démo et hardcore).
+
+## Fiche Habitant
+
+La page reprend la sobriété de Hello World : personnage à gauche, familier à
+droite et, entre les deux, les sections Nom, Biographie et Familier suivies de
+leur contenu. Le nom conserve faction et prestige. Le nom personnalisé du
+familier vient de son œuf équipé. Les textes d'aide sur le renommage, les
+raccourcis et le consentement sont retirés de cette page ; restent le réglage
+compact d'aide alliée et l'accès à l'historique.
+
+Les aperçus ne font apparaître aucune entité de jeu et ne déclenchent pas d'IA.
+Les modèles de bébé sont privilégiés, comme pour les familiers du monde.
+
+Le cadrage du personnage tient compte de la largeur disponible : la hauteur
+se réduit avant que les bras soient coupés. Le familier dispose de son propre
+cadrage, adapté aussi aux grands modèles. Les textures existantes sont reprises.
+
+Le HUD d’armure affiche uniquement les icônes pleines et les demi-icônes : les
+emplacements vides sont supprimés, sans modifier les points de protection.
+
+## Étoiles gagnées
+
+Le ciel Sanctuary remplace les étoiles décoratives natives par un ciel partagé.
+Avant tout événement, il est vide. Les événements enregistrés sont :
+
+- première arrivée d'un habitant : une étoile, une seule fois pour son UUID ;
+- advancement Minecraft accompli et normalement annonçable dans le chat : une
+ étoile pour une tâche, trois pour un objectif, cinq pour un défi. Les étoiles
+ supplémentaires restent proches de l'étoile principale ;
+- prestige : une étoile pour chaque rang effectivement atteint ;
+- faction atteignant deux membres, fondateur compris : une étoile.
+
+Les critères partiels, recettes techniques, reconnexions, messages libres du
+chat et créations de factions solitaires ne créent pas d'étoile. Couper les
+annonces d'advancements dans les règles du monde ne coupe pas leur mémoire.
+Un même advancement accompli par deux habitants donne deux groupes distincts ;
+le retirer puis l'obtenir à nouveau avec le même habitant ne crée aucun doublon.
+
+Une faction ne reçoit qu'une étoile à son premier duo. Le duo initial est
+mémorisé indépendamment du nom et de l'identifiant de faction : dissoudre puis
+recréer une faction avec ces deux mêmes personnes ne crée pas une autre étoile.
+Un nouveau duo peut marquer une autre faction. Départs, exclusions et dissolution
+ne retirent aucune étoile existante. Ce seuil remplace la proposition initiale
+d'une étoile dès la fondation solitaire.
+
+Les directions sont stables et communes aux clients du serveur. Le ciel
+accomplit une rotation en sept jours réels, sur une référence UTC commune
+synchronisée par le serveur. Le soleil et la lune conservent leur fonctionnement.
+La visibilité nocturne et les effets de l'environnement restent natifs.
+Le maillage graphique est conservé entre les images : il est reconstruit
+seulement à l'arrivée de nouvelles étoiles ou lors du rechargement graphique.
+Les deltas sont envoyés par lots ; les mouvements de caméra ne demandent aucun
+calcul serveur et ne réattribuent pas les positions.
+
+Le dessin passe par la méthode native de rendu des étoiles, pour conserver les
+points d'intégration des moteurs de shaders. Un premier traitement Vanilla Light
+est disponible dans les options ; voir [le contrat graphique](shaders-beta040.md)
+pour son fonctionnement et l'état encore non validé des moteurs externes.
+
+## Sauvegarde et reprise
+
+Nouveau fichier additif `data/sanctuary-stars.json`, schéma 1, limité à
+100 000 événements et 32 Mio. Chaque événement garde sa clé stable, son type,
+l'habitant, son nom au moment de l'événement, sa source, sa date et son nombre
+d'étoiles. L'écriture atomique vérifie le contenu précédent ; un fichier invalide
+est préservé et suspend les ajouts, sans être remplacé par un ciel vide sauvegardé.
+
+À la première ouverture avec ce système, les arrivées déjà attestées et le
+journal des prestiges/factions sont repris. Les advancements déjà accomplis
+sont repris à la connexion de leur habitant. Leur date d'étoile correspond à
+cette reprise lorsque la date exacte n'est pas disponible. Les historiques
+source, les sauvegardes personnelles et la génération des chunks ne sont pas
+réécrits. Le journal stellaire ne dépend pas de l'inventaire ni du personnage
+courant : mort et New Game+ conservent ses événements.
+
+Le dessin de constellations, la sélection à la longue-vue et la consultation
+Web ne sont pas inclus. Les événements structurés préparent ces usages sans
+leur inventer d'interface dans cette livraison.
+
+## Vérification
+
+Les contrôles portent sur les autorisations et leur révocation, les tirages
+stellaires déterministes, la sauvegarde/relecture, les vrais advancements,
+le prestige, le seuil de deux membres et la conservation après dissolution.
+Le parcours client utilise les boutons natifs de World Options, les aperçus
+de plusieurs familiers et le rendu réel du ciel. `check build assemblePack
+assembleTestPack` et 50 tests serveur passent, ainsi que le parcours client
+natif avec les dernières modifications. Le test observe les appels de dessin
+du HUD et le nombre d’indices de la passe stellaire native. Captures et reçu :
+`build/operator040-evidence/final/`, `build/operator040-artifact.json`.
+La connexion réseau dédiée n’a pas été testée ; le client à froid et le serveur
+intégré ont été vérifiés.
diff --git a/docs/packwiz.md b/docs/packwiz.md
index afec214..e4aab6c 100644
--- a/docs/packwiz.md
+++ b/docs/packwiz.md
@@ -13,6 +13,546 @@ JAR Sanctuary sont des pièces jointes de releases Gitea. Aucun JAR de mod ou
dépendance téléchargée n'entre dans l'historique Git. Le canal Beta est distinct
de l'ancien pack 26.2.
+## beta.060 — objets et blocs sur les mobs
+
+[Contrat et vérifications](mob-head-blocks-beta060.md). Équipement par
+Maj + clic droit, usage natif des blocs, source redstone mobile, sauvegarde
+et restitution des contenus. Build et parcours natif réussis.
+
+- [Pack normal](../build/Sanctuary-beta.060.mrpack), 5668807 octets.
+ SHA-256 : `7489ea0b5012f799baa929eebf37fed7abc588b913a0107c30f49ddcdfff6e01`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.060.mrpack), 5687740 octets.
+ SHA-256 : `7782ba69542ad7b922a1f4777763fc04dfcf0b92b5a1ea926c69c2235072d7af`.
+
+Reçu `build/mob-head060-artifact.json` : 1 620 classes conformes au build,
+JEI et archives beta.059 préservés. Aucun canal ou monde personnel modifié.
+
+## beta.059 — bannières et cartes au trésor
+
+[Contrat et vérifications](atlas-markers-beta059.md). Bannières nommées,
+import par utilisation d'une carte physique, butin des îlots et inspection
+opérateur. Inclut les altitudes et l'affichage simplifié de beta.058.
+Build et parcours natif réussis.
+
+- [Pack normal](../build/Sanctuary-beta.059.mrpack), 5648457 octets.
+ SHA-256 : `a530884f3d03111f71da6b39128e5fa14756c323ce320296ee8a2a1a5cdb1332`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.059.mrpack), 5667392 octets.
+ SHA-256 : `3362bf88c923550ed402ba9dc60e60963aeec217725fc26fb13f2ff04f673c91`.
+
+Reçu `build/atlas059-artifact.json` : 1 607 classes conformes au build,
+JEI et archives beta.058 préservés. Aucun canal ou monde personnel modifié.
+
+## beta.058 — altitude et affichage de la carte
+
+[Contrat et vérifications](atlas-altitude-beta058.md). Six plafonds, cadrage
+stable, relevés persistants par couche. Chunks/Demeure désactivés par défaut,
+cadre assombri supprimé, nord conservé. Build et parcours natif réussis.
+
+- [Pack normal](../build/Sanctuary-beta.058.mrpack), 5627223 octets.
+ SHA-256 : `bc9af60be020fd32f99a173638e9006cab128cf942df666e34e03824bac6f892`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.058.mrpack), 5646157 octets.
+ SHA-256 : `010eb08df83117669d2f13d3c2e8641c965fac3ddec11fbab419f01a9cb94942`.
+
+Reçu `build/atlas058-artifact.json`. Aucun canal ou monde personnel modifié.
+
+## beta.057 — météo quotidienne et nouvelles ambiances
+
+[Contrat et vérifications](daily-weather-beta057.md). Six profils quotidiens,
+averses alternées, ciel gris et brouillard natifs. Modes Real Time et Vanilla
+indépendants du soleil. Build, essais natifs, calendrier et archives vérifiés.
+Inclut les étoiles dispersées et la saturation par défaut de beta.056.
+
+- [Pack normal](../build/Sanctuary-beta.057.mrpack), 5623789 octets.
+ SHA-256 : `6f6cecc2dd5b6815a2c06545e32479b2f93d973abeffc86058fbac75e4cd6f62`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.057.mrpack), 5642723 octets.
+ SHA-256 : `3025bf5f1b0db68a2643ded8a7b25b6ae3ea7fc0e4ad463f064e3a2eca7ca713`.
+
+Reçu `build/weather057-artifact.json` : 1 600 classes conformes au build.
+Anciennes archives beta.056 et JEI préservés. Aucun canal public, monde
+personnel ou installation de jeu modifié.
+
+## beta.056 — étoiles dispersées et saturation
+
+[Contrat et vérifications](sky-spacing-beta056.md). Répartition sur toute la
+sphère et saturation par défaut à 142 %. Build, test natif et exports vérifiés.
+
+- [Pack normal](../build/Sanctuary-beta.056.mrpack), 5599547 octets.
+ SHA-256 : `12847a09a6fa32a292fd3418197d504743692ffe7a5c9ea1606b5784e57eb62a`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.056.mrpack), 5618480 octets.
+ SHA-256 : `3d4202ef3780886acfc76675e2b217204dce0e2ed9fcd80eafcc7916fa708dd1`.
+
+Reçu `build/sky056-artifact.json` ; archives beta.055 préservées.
+Aucune publication ni installation personnelle.
+
+## beta.055 — Force et portage de joueurs
+
+[Contrat et vérifications](strength-carry-beta055.md). Force réduit la charge
+ressentie des joueurs portés, avec recalcul à chaque changement d'effet.
+Build, montures natives, expiration et synchronisation client vérifiés.
+
+- [Pack normal](../build/Sanctuary-beta.055.mrpack), 5599276 octets.
+ SHA-256 : `75157abd37810d4bf50366c69d883c553682836d2c076a1d83c1c32eb9d3cc1c`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.055.mrpack), 5618210 octets.
+ SHA-256 : `80d70c68291391d50eefc50022c98c40ace3a2cc99b1eda670bf0b7741bbfdb4`.
+
+Reçu `build/strength055-artifact.json` : 1588 classes conformes au build ;
+seule `CarryService.class` change dans le code principal. Ressources et
+archives beta.054 préservées. Aucune publication ni installation personnelle.
+
+## beta.054 — duels, mises et niveaux des familiers
+
+[Contrat, migration et vérifications](familiar-combat-beta054.md).
+Duels consentis avec mises en objets ; stockage et retrait exacts d'XP dans
+les œufs, puissance sans plafond de niveau et rareté croissante. Menus natifs
+FR/EN, transactions interrompues, échange entre joueurs et reconnexion testés.
+
+- [Pack normal](../build/Sanctuary-beta.054.mrpack), 5599061 octets.
+ SHA-256 : `4e8c0655c6a6fc9a277df8f984dd7fe1d682f46a6664828d81c64d6cbd3a347a`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.054.mrpack), 5617995 octets.
+ SHA-256 : `edeaeacded1ed596b5ef687addb9c4b3d3efff1ef91f2cfec25c6c0bd396a433`.
+
+Reçu `build/combat054-artifact.json` : 1588 classes et ressources conformes
+au build. Référence beta.053 extraite de son archive immuable ; textures,
+shaders, JEI et données de génération préservés. Les variantes des 88 profils
+ne sont pas toutes finalisées au niveau du catalogue de conception.
+Aucun changement du canal public ni de l'instance personnelle.
+
+## beta.053 — brume progressive et saturation
+
+[Contrat et vérifications](progressive-fog-beta053.md). Brume progressive,
+saturation réglable et œufs de Hello World alignés à gauche. Build et parcours
+client natif FR/EN réussis.
+
+- [Pack normal](../build/Sanctuary-beta.053.mrpack), 5412739 octets.
+ SHA-256 : `f66292ac5eda04e0bbf51bdd99393dc2b999c48d36fdf341b0573dec266db000`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.053.mrpack), 5431672 octets.
+ SHA-256 : `db2281a7ce6727c2689a1395ba0ff86f4977912b1b1e4cec0afa4ebef86f65cf`.
+
+Reçu `build/fog053-artifact.json` : 1 537 classes et ressources conformes au
+build. Archives beta.052, textures, JEI et données de génération préservés.
+Aucun changement du canal public ni de l'instance personnelle.
+
+## beta.052 — nom du familier de départ
+
+[Contrat et migration du registre](starter-name-beta052.md). Champ de nom dans
+Hello World, nom conservé sur l'œuf et le familier. Build, 8 tests serveur,
+parcours client FR/EN et reconnexion réussis.
+
+- [Pack normal](../build/Sanctuary-beta.052.mrpack), 5408629 octets.
+ SHA-256 : `9696473ca63314c17895f83d3869ada5795e72cf071958e5ef0587437309601a`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.052.mrpack), 5427562 octets.
+ SHA-256 : `e65dae7e0a1af4008cf279b21f309327ff7b5868639dc8715356e05eea8af921`.
+
+Reçu `build/starter052-artifact.json` : 1 535 classes et ressources conformes au
+build. Archives beta.051, textures, JEI et données de génération préservés.
+Aucun changement du canal public ni de l'instance personnelle.
+
+## beta.051 — visée du distributeur porté
+
+[Contrat](head-dispenser-beta051.md). Le tir suit le regard horizontal et vertical
+du porteur au moment du lancement. Build et 34 tests serveur réussis, dont
+104 lancements avec les treize munitions natives.
+
+- [Pack normal](../build/Sanctuary-beta.051.mrpack), 5404487 octets.
+ SHA-256 : `2b62dfce683e0fc1b6617d1b7ab9012ca98775358595d2939e5b29cdde9e6e0c`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.051.mrpack), 5423420 octets.
+ SHA-256 : `e8d165178a59a2771de6ffcc456559b5ce162913c4e5acaeb9acc8f06ee9a67c`.
+
+Reçu `build/dispenser051-artifact.json` : 1 534 classes et ressources conformes
+au build. Archives beta.050, PNG, JEI et données de génération préservés.
+Validation multijoueur distante restant à confirmer ; aucun changement du canal
+public ni de l'instance personnelle.
+
+## beta.050 — bateaux compacts et redstone portée
+
+[Contrat](head-blocks-beta050.md). Huit formes, 88 variantes ; nouveaux formats
+1 × 2 et 2 × 1 à quatre places. Étagères et pupitres visibles, livres enchantés,
+lits complets décoratifs, pots et sources redstone mobiles.
+Build, 40 tests serveur et parcours client natif FR/EN réussis.
+
+- [Pack normal](../build/Sanctuary-beta.050.mrpack), 5402725 octets.
+ SHA-256 : `b10cb1f21136597ec6bb0cccd8693004dbe24f3ad67ae1656086f389f2f32ef4`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.050.mrpack), 5421659 octets.
+ SHA-256 : `a9561477eb16e89863964c0c0652fc95ee91de280bbd762169b723a1db4f91f8`.
+
+Reçu `build/head050-artifact.json` : 1 533 classes et ressources conformes au
+build. Archives beta.049, PNG, JEI et données de génération préservés.
+Aucun changement du canal public ni de l'instance personnelle.
+
+## beta.049 — rectangles, doubles sièges et blocs portés
+
+[Contrat](boats-head-beta049.md). Six formes, 66 variantes ; deux passagers par
+module, places principales puis secondaires. Sons partagés, aliments et
+ouvertures visibles, orientations corrigées et signal redstone à l'impact.
+Build, 35 tests serveur et parcours client natif FR/EN réussis.
+
+- [Pack normal](../build/Sanctuary-beta.049.mrpack), 5391461 octets.
+ SHA-256 : `59cd9ecceb5b81d64825fb9f8d056ed9f551a2918dc7095d2df9e2d91c1d64bb`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.049.mrpack), 5410395 octets.
+ SHA-256 : `0900dad1640200546a41f6a538504f56160a31bc2a14b5e6ed9a8249861a8e58`.
+
+Reçu `build/boats049-artifact.json` : 1 529 classes et ressources conformes au
+build. Archives beta.048, PNG, JEI et données du terrain préservés. Aucun
+changement du canal public ni de l'instance personnelle.
+
+## beta.048 — bateaux collectifs
+
+[Contrat](poultry-aeronautics-beta048.md). Modèles par tuiles natives, sept moteurs,
+commandes partagées et collisions par pile ; Happy Ghast à vitesse doublée.
+Build, 33 tests serveur et parcours client natif FR/EN réussis.
+
+- [Pack normal](../build/Sanctuary-beta.048.mrpack), 5376425 octets.
+ SHA-256 : `c5f25d1e390f5c7b8d6179b6fd3a2d6aa69d413f4766482bd9c6042841f00225`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.048.mrpack), 5395357 octets.
+ SHA-256 : `7f8a38407757850860ba72709d9d4db2aa29638051aae67d0c6c1199265a497b`.
+
+Reçu `build/poultry048-artifact.json` : 1 527 classes et ressources conformes au
+build ; 22 recettes supplémentaires et collection réconciliée. Archives beta.047,
+PNG officiels, icônes d'aptitudes, JEI et données du terrain préservés.
+Pas de modification du canal public ni de l'instance personnelle.
+
+## beta.047 — inspection des étoiles et survol jaune
+
+[Contrat](spyglass-stars-beta047.md). Build, 12 tests serveur, parcours natif
+FR/EN et intégrité des archives vérifiés.
+
+- [Pack normal](../build/Sanctuary-beta.047.mrpack), 5344001 octets.
+ SHA-256 : `f14f2493f6a13982b51632fea97492c601c40b22d9feacd6c18571c4e43bc55c`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.047.mrpack), 5362936 octets.
+ SHA-256 : `0c82e04d94d399ea547d16ec55983e7bf5b618215aa6030101dd42ac9419148c`.
+
+Reçu `build/star047-artifact.json` : 1 514 classes et ressources conformes au
+build. Archives beta.046, textures, données du terrain et JEI préservés.
+Pas de modification du canal public ou de l'instance personnelle.
+
+## beta.046 — constellations, vol, zoom et icônes
+
+Deux places à la fondation, constellations tracées à la souris, bateau à poule,
+zoom déblocable, dix icônes d’aptitudes et identité native Sanctuary.
+[Contrat et résultats](sky-flight-zoom-beta046.md) · [Migration des factions](faction-two-slots-beta046.md).
+Build, **20 tests serveur**, parcours client natif FR/EN et vérification visuelle réussis.
+
+- [Pack normal](../build/Sanctuary-beta.046.mrpack), 5319418 octets.
+ SHA-256 : `16fdcb195451f5ee691dbd4ab33e38f51877724cdc0d8eeadd8105b503632142`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.046.mrpack), 5338352 octets.
+ SHA-256 : `af49f4ab84b430aab2b93c3bff45ce1f036f889ffeaef1c0c8d2dd3725fb36c1`.
+
+Reçu : `build/beta046-artifact.json`. Les 1 503 classes, ressources et JAR
+imbriqués correspondent au build. Les dix nouvelles icônes sont identiques
+aux PNG fournis et aux sources du resource pack. Les anciennes textures,
+données de génération, dépendances distantes et archives beta.045 sont inchangées.
+Le test de présentation sans VSync est absent du JAR livré.
+Le canal public et l’instance personnelle n’ont pas été modifiés.
+
+## beta.045 — menu principal
+
+Version en haut à gauche et Friends / Amis pleine largeur sous Multiplayer.
+[Contrat et résultats](title-menu-beta045.md). Build, quatre tests serveur ciblés,
+parcours client FR/EN et vérification visuelle réussis.
+
+- [Pack normal](../build/Sanctuary-beta.045.mrpack), 5254098 octets.
+ SHA-256 : `e0dc2e66d0238f6e1c29b561bf85ab0da2b84f369ef4493f144719ab2971fa42`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.045.mrpack), 5273031 octets.
+ SHA-256 : `89807b8245944ff8c6d5841941ff3ed7d9969d3f2d270e179f4d64d25cf1eaf9`.
+
+Reçu : `build/title045-artifact.json`. Les 1 471 classes, ressources et
+JAR imbriqués correspondent au build. L'icône et les textures restent identiques.
+Exports beta.044, canal public et instance personnelle inchangés.
+
+## beta.044 — pouvoirs natifs, commandes LAN et icône finale
+
+Build, **69 tests serveur**, **88 contextes positifs d'œufs**, deux parcours
+client et exports vérifiés. [Contrat et résultats](spawn-eggs-audit-beta044.md).
+
+- [Pack normal](../build/Sanctuary-beta.044.mrpack), 5250933 octets.
+ SHA-256 : `a76cabcadb85c3c0439961b8a175ee8be28fcf246970a3e57ca6475da0c169f0`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.044.mrpack), 5269868 octets.
+ SHA-256 : `06f4893e4e4d629a9c8791e59000678c768ad134e32f7346c103edd85e304ed2`.
+
+Reçu : build/egg044-artifact.json. Dernière icône fournie (bleu en haut),
+1 470 classes et ressources comparées au build, dépendances contrôlées.
+Archives beta.043, canal public et instance personnelle inchangés.
+
+## beta.043 — blocs portés et icône officielle
+
+Build, 41 tests serveur et exports vérifiés ; interface graphique non validée.
+[Contrat](functional-head-blocks-beta043.md).
+
+- [Pack normal](../build/Sanctuary-beta.043.mrpack), 5242689 octets.
+ SHA-256 : `1c52b19d503b386452ecd3e8e1141061171ca2ec40946709d5b9dfdc6583a54d`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.043.mrpack), 5261624 octets.
+ SHA-256 : `dae7ed5433095c5fa1d55caa92b7f037a51c2202d29ec0b9e8c8477f77d0dcd2`.
+
+Reçu : `build/head043-artifact.json`. Icône fournie intacte, 1 466 classes
+comparées au build, dépendances imbriquées et ressources vérifiées.
+Exports beta.042, canal public et instance personnelle inchangés.
+
+## beta.042 — ancrage des joueurs portés
+
+Portage corrigé avec 39 tests serveur et un parcours graphique natif réussis.
+Le diagnostic LAN reste ouvert : activation tardive de l'hôte, commande d'XP
+et révocation fonctionnent dans le scénario testé. Aucun correctif des droits
+n'est revendiqué. [Ticket](lan-carry-beta042.md).
+
+- [Pack normal](../build/Sanctuary-beta.042.mrpack), 5,191,398 octets.
+ SHA-256 : `de7d2368de6866d1408cd41f0c60148f5db7a467b00df2152e8821bde4c0ae6b`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.042.mrpack), 5,210,328 octets.
+ SHA-256 : `24f7add0c5f32f8d7cec596d624d6da5dd57a7ef920e3fa711b7f45345828443`.
+
+Reçu : `build/lan-carry042-artifact.json`. Les 1 441 classes correspondent au
+build ; seuls CarryService et CarryPose changent face à beta.041. Les 173 PNG
+et les deux MRpacks beta.041 sont inchangés. Canal public et Prism inchangés.
+
+## beta.041 — bébés portés sans ralentissement
+
+Deux exports locaux vérifiés après `check build assemblePack assembleTestPack`
+et **43 tests serveur** ciblant le portage, les familiers et les mouvements.
+Le poids des bébés est nul, y compris dans les piles ; la croissance rétablit
+la pénalité adulte au prochain tick. [Contrat](carry-babies-beta041.md).
+
+- [Pack normal](../build/Sanctuary-beta.041.mrpack), 5 191 178 octets.
+ SHA-256 : `c2be0db5efc13a6e442f81cb1b2615143c1c5c1d102b0856b42cec37ccd8eaae`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.041.mrpack), 5 210 113 octets.
+ SHA-256 : `03556ad7920a51d9e5d54ca294ef2ee1a1ac84a3d948a0693e926a66eebd7864`.
+- Reçu local : `build/carry041-artifact.json`.
+
+Les 1 441 classes correspondent au build. Les ressources client, les 173 PNG
+et les exports beta.039/beta.040 sont inchangés. Le correctif multijoueur de
+beta.040 est inclus ; aucun nouveau test réseau dédié n'est revendiqué.
+Le canal public, Prism et les serveurs personnels restent inchangés.
+
+## beta.040 — accueil multijoueur, habitant, opérateur et ciel
+
+Deux exports locaux vérifiés, après `check build assemblePack assembleTestPack`,
+**50 tests serveur** et parcours client natif : ouverture du Hello World à froid
+sans composants d'items, contrôles opérateur, portraits, armure, ciel gagné/vide,
+shader natif et relecture d'un catalogue de recettes supérieur à 64 Ko.
+La connexion à un serveur dédié n'a pas été testée ; l'EULA reste non acceptée.
+Aucun moteur Iris compatible avec 26.3-pre-2 n'est embarqué.
+
+- [Pack normal](../build/Sanctuary-beta.040.mrpack), 5,190,946 octets.
+ SHA-256 : `1973be1033d295b1f3a81fe96b6a0b072597922cd089b1fdae26281ea13530f5`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.040.mrpack), 5,209,881 octets.
+ SHA-256 : `320abd13c1e523e7a2d5b1177abb35dcebfe1fabdcc8340e2846b6e3a715ac49`.
+- Reçu local : `build/operator040-artifact.json` ; captures dans
+ `build/operator040-evidence/final/`.
+
+Les 1 441 classes compilées, ressources et dépendances imbriquées correspondent
+aux sources contrôlées. Les 173 PNG existants et les deux exports beta.039 sont
+inchangés. Le profil plat ajoute uniquement le module Sanctuary Test.
+Canal public, instance Prism et serveurs personnels inchangés.
+Contrats : [accueil](multiplayer-hello-beta040.md),
+[opérateur/habitant/ciel](operator-inhabitant-stars-beta040.md),
+[shaders](shaders-beta040.md), [reprise des recettes](recipe-storage-beta040.md).
+
+## beta.039 — familier de départ, savoir alimentaire et inventaire de mort
+
+Les deux distributions locales sont vérifiées : 68 tests serveur, parcours client
+natif, 14 784 comparaisons alimentaires et 10 000 tirages de familiers. Les trois
+cases du Hello World sont centrées sous la palette ; leur titre reste aligné à
+gauche. Le tirage est conservé avant l'arrivée et le familier choisi est équipé
+une seule fois. [Contrat du premier familier](hello-starter-beta039.md).
+
+La livraison comprend le savoir alimentaire, les panoramas avec profondeur et
+masque de brume reconstitué, les têtes numérotées et l'inventaire de mort grisé à
+côté de l'inventaire actuel. Les 173 textures existantes sont conservées.
+[Captures et alimentation](food-panorama-beta039.md) ;
+[inventaire de mort](grave-inventory-beta039.md).
+
+Les 1 419 classes Sanctuary, les ressources et les dépendances imbriquées ont
+été comparées aux builds. Le profil plat ajoute uniquement Sanctuary Test.
+Canal public et instance Prism personnelle inchangés ; beta.038 reste immuable.
+
+- [Pack normal](../build/Sanctuary-beta.039.mrpack), 5 150 501 octets.
+ SHA-256 : `81bbc6770f9c44ba3e259cf8be2881b60d4146ada6a9c4d889afad96f5f0311a`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.039.mrpack), 5 169 436 octets.
+ SHA-256 : `c3649eec2d5a1ec67b08743c9b636157ce4bc692bff1a96c4334a6660363acd6`.
+- Reçu local : `build/food039-artifact.json` ; captures et panoramas dans
+ `build/food039-evidence/release/`.
+
+Pour essayer le choix initial, utiliser un nouvel habitant dans un nouveau
+monde : les habitants déjà arrivés ne sont pas renvoyés au Hello World.
+
+## beta.038 — nourriture, têtes-tombes et fondation des factions
+
+Les deux distributions locales sont vérifiées : 55 tests serveur, parcours
+client natif, 14 784 comparaisons alimentaires, 1 381 classes Sanctuary et les
+ressources embarquées. Le profil plat ajoute uniquement le module Sanctuary Test.
+La fondation coûte désormais dix niveaux et crée une place sans prestige ; les
+anciennes factions sont conservées. [Contrat et reprise](graves-food-beta038.md).
+Canal public et instance Prism personnelle inchangés ; beta.037 reste immuable.
+
+- [Pack normal](../build/Sanctuary-beta.038.mrpack), 5,069,719 octets.
+ SHA-256 : `12b4b56480e3f58da58e312e109417c8834f0682c4bf889ae536e41c4f44de54`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.038.mrpack), 5,088,654 octets.
+ SHA-256 : `ed302d7ad9e921e1f97e9727fefa56b782db8c77820f99f31cf1611a06015076`.
+- Reçu local : `build/graves038-artifact.json` ; treize captures dans
+ `build/graves038-evidence/`.
+
+## beta.037 — temps réel et suivi commun
+
+Les deux distributions locales sont vérifiées : 29 tests serveur, deux parcours
+client, 1 353 classes Sanctuary, dépendances imbriquées et ressources. Le module
+Sanctuary Test est le seul fichier supplémentaire du profil plat. Les artefacts
+beta.036 restent immuables. [Contrat et configuration](realtime-beta037.md).
+Canal public et instance Prism personnelle inchangés.
+
+- [Pack normal](../build/Sanctuary-beta.037.mrpack), 5,014,547 octets.
+ SHA-256 : `e91d29dddf5ee26098a4de6fff85a5cfa5ce920304754c12aecd06cf8b6a9d2d`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.037.mrpack), 5,033,482 octets.
+ SHA-256 : `9b4e3c60e507e2ab9e3bf666615d833db8b0e4375454dc727aff7f5155c4b043`.
+
+## beta.036 — suivi choisi et correctifs de jeu
+
+Distribution locale normale et profil de test plat, versions synchronisées.
+[Contrat](corrections-beta036.md) : notifications/F3, suivi depuis Advancements,
+plané avec animal porté, discrétion au sol et sorties de recettes consultables
+par le catalogue JEI. Les annonces, possessions et recettes restent distinctes.
+Le canal public et l’instance Prism personnelle ne sont pas modifiés par ce ticket.
+Build, 36 tests serveur et deux parcours client natifs réussis. Les 1 335 classes
+Sanctuary, les dépendances imbriquées et la différence unique du profil de test
+sont contrôlées ; beta.034 et beta.035 restent inchangées.
+
+- [Pack normal](../build/Sanctuary-beta.036.mrpack), 4,968,783 octets.
+ SHA-256 : `dfa2602f7cd584f50d7a8d4a1faeb249a3473e9b4259a2c87cf7893a53d6836f`.
+- [Monde plat rapide](../build/Sanctuary-Test-beta.036.mrpack), 4,987,716 octets.
+ SHA-256 : `118ec724517233319c922931d9186f54b9831e628e6661d0a32863d507dc37c9`.
+- Reçu local : `build/corrections036-artifact.json` ; captures et logs cités
+ dans le contrat de livraison.
+
+## beta.035 — collections Minecraft
+
+Exports locaux : [pack normal](../build/Sanctuary-beta.035.mrpack) et
+[profil plat rapide](../build/Sanctuary-Test-beta.035.mrpack).
+[Contrat, reprise des carnets et validation](collections-beta035.md).
+Build, 28 tests serveur, matrice des 126 advancements et parcours client natif
+réussis. Les 67 définitions, 2 042 recettes primaires, 1 322 classes Sanctuary,
+traductions et dépendances embarquées correspondent aux builds. Le profil rapide
+ajoute seulement son module facultatif et son nom de pack.
+
+SHA-256 normal : `dfcf5927eaef183fe582b95454480849a08353561cb66d83a514d8a0274b5c60`.
+SHA-256 test : `f94f216b5b09c7ebeafea45a8d735978a4efc354aa245ffbfd993ee7e6db2b24`.
+Reçu : `build/collections035-artifact.json`. Aucun canal, instance personnelle
+ou monde existant n’a été modifié ; les archives précédentes sont conservées.
+
+## beta.034 — découvertes et hotbar agrandie
+
+Exports locaux : [pack normal](../build/Sanctuary-beta.034.mrpack) et
+[pack test rapide](../build/Sanctuary-Test-beta.034.mrpack).
+[Fonctionnement et vérifications](notifications-beta034.md).
+Build, 22 tests serveur et parcours client natif réussis. Les 1 320 classes du
+mod, ses dépendances imbriquées, les ressources FR/EN et les métadonnées ont
+été comparées aux builds. Le profil rapide ajoute seulement le module test et
+son nom de pack ; JEI reste identique à beta.033.
+
+SHA-256 normal : `df0a0c2ab937ccec4361bd37a07a551a592b3d5819ade1f289fa9fb428c355e5`.
+SHA-256 test : `cf41d20a118128afd044556e6dee44804852ea7232f2b13623020ec471c3b4fc`.
+Reçu : `build/notifications034-artifact.json`. Les anciennes archives, le canal
+packwiz et l’instance Prism personnelle n’ont pas été modifiés.
+
+## beta.033 — menus et profil plat facultatif
+
+Deux exports locaux vérifiés :
+
+- [Sanctuary-beta.033.mrpack](../build/Sanctuary-beta.033.mrpack), pack normal.
+- [Sanctuary-Test-beta.033.mrpack](../build/Sanctuary-Test-beta.033.mrpack), monde plat et intro passée.
+
+Le second conserve le contenu du premier à l’octet près et ajoute seulement
+`sanctuary-test-beta.033.jar` (21 504 octets), avec un nom de pack distinct.
+[Utilisation du profil rapide](test-rapide-beta033.md) · [menus et validation](menus-discovery-beta033.md).
+Les 1 304 classes Sanctuary, les trois classes du module test, les dépendances
+imbriquées et les métadonnées exactes ont été comparées aux builds. Les exports
+beta.031 et beta.032 sont inchangés. Build, 34 tests serveur et parcours client
+réussis ; le bouton History final est vérifié dans le dernier parcours plat.
+
+SHA-256 normal : `a302eee44a282a22a8b845f96c4260583995e0bbd7444e969cda823ee95e3a2f`.
+SHA-256 test : `a703edb798d1981b63f6dd7d8e1980fc6c69ba6a9b0443bb10f5d0bbd6f61234`.
+Reçus : `build/menus033-artifact.json` et `build/quick033-artifact.json`.
+Le canal packwiz et l’instance Prism personnelle n’ont pas été modifiés.
+
+## beta.032 — refonte des 88 familiers
+
+Export d’essai [Sanctuary-beta.032.mrpack](../build/Sanctuary-beta.032.mrpack).
+Client et serveur beta.032 ensemble ; la nouvelle capacité réseau vérifie cette
+compatibilité. [Migration additive, utilisation et validation](spawn-eggs-v2-beta032.md).
+Build, 85 tests serveur et parcours client natif réussis. Reçu d’intégrité :
+`build/refonte032-artifact.json`. Export local ; aucun canal ni instance
+personnelle n’a été mis à jour.
+
+## beta.031 — apparition galactique
+
+Export d'essai [Sanctuary-beta.031.mrpack](../build/Sanctuary-beta.031.mrpack).
+Glyphes spectraux, spirale vers un portail qui remplit l'écran et flash blanc
+sur le spawn. Client et serveur beta.031 ensemble ; `/sanctuary intro` rejoue
+la proposition. L'éclairage de beta.030 est inclus.
+
+Build, 9 tests serveur et parcours graphique natif validés. Archive, versions,
+1 278 classes compilées, dépendances imbriquées et conservation de beta.030
+vérifiées. [Contrat et reçu](introduction-beta031.md).
+Export local ; canal packwiz et instance Prism personnelle non modifiés.
+
+## beta.006 — brouillard, souris et Demeure
+
+Export local [Sanctuary-beta.006.mrpack](../build/Sanctuary-beta.006.mrpack).
+`check build assemblePack` avec la sélection native `progression,map,demeure`
+réussit : 8/8 tests serveur, parcours client natif validé séparément.
+Le JAR Sanctuary contient le mod autonome Demeure, sa licence et sa provenance.
+L'assemblage vérifie que ce JAR imbriqué est identique au build du module.
+
+SHA-256 MRpack : `ef8499de78461e7eab7bafe3d1ccf38a2d06ec3a0e0884e8e10ffe30fc63071f`.
+Reçu : `build/demeure006-artifact.json` ; [migration et validation](demeure-beta006.md).
+Les exports beta.003–005 restent identiques. Aucun canal ni instance personnelle
+n'est modifié par cet export d'essai.
+
+## beta.004 — cadre Blocodex et correction du sprint
+
+Export local `build/Sanctuary-beta.004.mrpack`, JAR `sanctuary-beta.004.jar`,
+version interne `beta.004`. `check build` avec la sélection native progression
+réussit, ainsi que le parcours client et `assemblePack -x check` après les
+contrôles. Les mondes beta.003 restent lisibles sans migration. Voir
+[le ticket, les vérifications et les limites](blocodex-beta004.md).
+
+Archive, version, classes, ressources, dépendance et JAR embarqué vérifiés.
+Reçu : `build/blocodex004-artifact.json`.
+SHA-256 MRpack : `aee4fdebd48d6719f85d2477fbefe90e12ae38313cc107615fa6105c0c57d475`.
+SHA-256 JAR : `3a8c26105121340a41644c817533bff59f519d7f02837efe570511568eba98a6`.
+Le MRpack beta.003 reste identique. Aucune publication ni synchronisation
+d'installation personnelle effectuée.
+
+## beta.003 — export d'essai de la progression
+
+Version interne `beta.003`, JAR `sanctuary-beta.003.jar`, export local
+`build/Sanctuary-beta.003.mrpack`. Le parcours HELLO_WORLD, achats, mort et
+redémarrage est validé en client natif ; la suite serveur générale conserve
+deux échecs worldgen préexistants. Voir [les résultats et limites](progression-beta003-release.md).
+
+Après les contrôles, `./gradlew assemblePack -x check` assemble le JAR final
+et vérifie les manifestes. Depuis `build/packwiz`, `packwiz modrinth export
+-o ../Sanctuary-beta.003.mrpack` produit l'archive. Intégrité ZIP, versions,
+JAR embarqué identique au build, ressources FR/EN et empreinte Fabric API
+vérifiés ; reçu dans `build/progression003-artifact.json`.
+
+SHA-256 MRpack : `2b31d67befb4838d0f6da307c3ac0d1dfe5ba41978e9d8b73d5c2983108b9db0`.
+SHA-256 JAR : `44544793d318d2be8bf5cdc6ab4e80d752b886debcf383bedca04ed04da0ae1d`.
+Le canal publié et l'instance Prism personnelle restent à leur état antérieur.
+
+## beta.002 — export d’essai des climats et temples
+
+Version interne `beta.002`, JAR `sanctuary-beta.002.jar` et MRpack
+`Sanctuary-beta.002.mrpack`. Les corrections demandées après l’essai de beta.001
+sont décrites dans [le contrat des expéditions](expeditions-beta002.md).
+La distribution reste un export local ; aucun canal publié ou instance Prism
+personnelle n’est modifié par cette préparation.
+
+## beta.001 — transition préparée localement
+
+Le mod et le pack suivent désormais [le compteur `beta.xxx`](versioning.md).
+Le premier numéro est `001` ; les futurs tags reprennent la version exacte,
+sans préfixe `v`. Le relief initial reste celui de l’alpha.30.7 ; les nouveaux
+mondes ouvrent [quatre anciennes expéditions](expeditions-beta001.md). Cette préparation
+ne publie pas de release, n’avance pas le canal et ne synchronise pas Prism.
+La dernière livraison consignée reste l’alpha.30.7 ci-dessous.
+
## Alpha.30.7 publiée — 12 septembre 2026
La [release alpha.30.7](https://git.botsu.net/koka/sanctuary-beta/releases/tag/v0.1.0-alpha.30.7)
@@ -843,7 +1383,7 @@ Après les vérifications, le commit et le push de la branche du ticket, le scri
de publication réalise les étapes de release et de canal ci-dessous :
```sh
-python3 scripts/publish_pack.py --notes-file chemin/vers/notes.md --asset build/Sanctuary-0.1.0-alpha.12.mrpack
+python3 scripts/publish_pack.py --notes-file chemin/vers/notes.md --asset build/Sanctuary-beta.001.mrpack
```
Les options sont facultatives. Ce script **publie** sur le Git configuré dans
@@ -857,12 +1397,13 @@ de livraison restent à exécuter avant cette commande.
La procédure complète, également utilisable manuellement :
-1. Modifier ensemble les versions de `gradle.properties` et `packwiz/pack.toml`.
+1. Incrémenter ensemble les versions de `gradle.properties` et `packwiz/pack.toml`
+ selon la [convention `beta.xxx`](versioning.md).
2. Exécuter `./gradlew check build assemblePack` et les vérifications du ticket.
3. Préparer les métadonnées de la livraison, avec l'URL exacte du futur JAR :
```sh
- python3 scripts/pack.py release https://git.botsu.net/koka/sanctuary-beta/releases/download/v0.1.0-alpha.12/sanctuary-0.1.0-alpha.12.jar
+ python3 scripts/pack.py release https://git.botsu.net/koka/sanctuary-beta/releases/download/beta.001/sanctuary-beta.001.jar
```
4. Pousser le commit source vérifié. Créer une release Gitea correspondant à ce
diff --git a/docs/player-stacks-beta027.md b/docs/player-stacks-beta027.md
new file mode 100644
index 0000000..11504e9
--- /dev/null
+++ b/docs/player-stacks-beta027.md
@@ -0,0 +1,58 @@
+# beta.027 — piles de joueurs
+
+## Résultat attendu
+
+**Maj + clic droit sur un joueur** permet de monter sur sa tête, même lorsqu’il
+est lui-même assis sur un autre joueur. Chaque tête garde une seule place :
+pour agrandir la pile, viser une tête libre, à portée normale.
+Un porteur peut aussi grimper sur un autre joueur avec son groupe déjà porté.
+
+Chaque personne ou animal au-dessus multiplie la vitesse du porteur par **0,75**,
+avec un plancher de **25 %** de sa vitesse habituelle. Une personne au-dessus :
+75 % ; deux : 56,25 % ; trois : 42,19 % ; quatre : 31,64 % ; cinq ou plus : 25 %.
+Le calcul s’applique à tous les porteurs de la pile. Les autres effets de vitesse
+continuent de s’appliquer. La pénalité se recalcule immédiatement à chaque montée
+ou descente et disparaît dès que le porteur est libre.
+
+Relâcher Maj après être monté, puis appuyer à nouveau pour descendre.
+**Maj + clic droit sans viser un joueur extérieur à la pile** dépose le groupe porté.
+Viser un joueur extérieur à la pile permet de grimper avec son groupe.
+Les coups entre tous les membres d’une même pile sont refusés.
+
+## Limites et données
+
+Le serveur refuse les cycles, les têtes occupées, les montures ordinaires,
+les porteurs endormis/allongés/en vol plané et un plafond trop bas pour l’ensemble
+du groupe. Il vérifie aussi la bordure, la hauteur et les chunks déjà chargés.
+Une dépose cherche une place pour tout le groupe supérieur. Les passagers de ce
+groupe restent ensemble ; la disparition d’un joueur au milieu sépare les parties
+survivantes. Les contrôles de collision continuent pendant le déplacement.
+
+Les relations utilisent les passagers natifs et l’attachement temporaire existant
+`sanctuary:carried`. Aucun nouveau format de sauvegarde, aucune migration et
+aucune modification des inventaires, aptitudes ou règles de progression.
+Les animaux gardent leur identité native et les joueurs ne sont pas enregistrés
+dans la sauvegarde d’un autre joueur. Client et serveur beta.027 ensemble.
+
+## Vérifications
+
+**27 tests serveur natifs réussis**, dont 8 nouveaux tests de portage : pile de
+huit joueurs, maintien sur 20 ticks, portée et siège unique, refus des cycles et
+des montures ordinaires, plafond du groupe complet, cumul des vitesses, retrait
+immédiat de la pénalité, coexistence avec les autres modificateurs, coups entre
+membres éloignés, dépose d’une pile, mort au milieu, déchargement, absence de
+passagers enregistrés dans les joueurs et identité de l’animal porté. Les
+régressions des familiers et mouvements passent dans la même suite.
+
+Validation finale et assemblage **réussis** (3 min 41 s) :
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=carry,familiar,movement`.
+La session macOS est verrouillée : l’observation graphique reste à confirmer.
+
+Export local vérifié : [Sanctuary-beta.027.mrpack](../build/Sanctuary-beta.027.mrpack).
+Les 1 253 classes du JAR correspondent à la compilation. Seuls `CarryService`
+(et ses classes internes) et `CarryDropMixin` diffèrent de beta.026. La carte,
+Demeure et JEI gardent leur code précédent. Les JAR imbriqués correspondent aux
+modules construits ; l’export beta.026 est intact. Aucun déploiement effectué.
+
+SHA-256 MRpack : `13afddafca072151ba2b69ad5b3d7ee8de6f4f3841f3ace0cf598b87806e14f1`.
+Reçu : `build/carry027-artifact.json` ; vérificateur : `build/verify-carry027.py`.
diff --git a/docs/poultry-aeronautics-beta048.md b/docs/poultry-aeronautics-beta048.md
new file mode 100644
index 0000000..3ff0fd3
--- /dev/null
+++ b/docs/poultry-aeronautics-beta048.md
@@ -0,0 +1,136 @@
+# beta.048 — aéronautique de poule
+
+Branche `codex/poultry-aeronautics-beta048`.
+
+## Contrat
+
+- Maj + clic droit sur un joueur assis dans un bateau permet de monter sur sa
+ tête et de prolonger sa pile. Un animal assis dans le bateau ne sert pas de siège.
+- Deux formats supplémentaires : 2 × 2, quatre sièges ; 3 × 3, neuf sièges.
+ Chaque siège accueille un joueur ou un animal. Un animal moteur réel assis dans
+ le bateau fournit la propulsion. Un familier ou un animal porté sur une tête
+ ne fournit pas de moteur.
+- Les onze bois avec bateau/radeau dans Minecraft 26.3-pre-2 sont couverts,
+ y compris peuplier et bambou. Pas de variante avec coffre dans ce ticket.
+- Recettes approuvées : quatre ou neuf bateaux/radeaux normaux du même bois,
+ disposés en carré à l'établi, donnent le format correspondant. Les recettes
+ rejoignent la collection Bateaux et radeaux, y compris déjà débloquée.
+- Tous les joueurs directement assis commandent le bateau collectif. On moyenne
+ leurs directions et leur demande de montée. Les joueurs portés ajoutent du
+ poids sans participer aux commandes. Des ordres contraires se compensent.
+- Le bateau simple avec une poule et un pilote conserve exactement son vol
+ beta.046. Toute personne supplémentaire ralentit la montée et accélère la
+ descente. Plusieurs poules améliorent la vitesse et réduisent l'inertie.
+- Le vol chargé conserve une montée possible lorsque tous les pilotes appuient
+ sur Espace. La vitesse horizontale minimale, lorsque tous avancent ensemble,
+ est de quatre blocs/seconde. Le doublement du Happy Ghast reste indépendant
+ de ce plancher. Aucun moteur signifie aucun vol animal.
+- Les grands bateaux simulent leurs mouvements côté serveur, avec les entrées
+ natives des pilotes ; les clients reçoivent la position du véhicule.
+- La vitesse du Happy Ghast est multipliée par deux séparément, sans changer
+ la référence utilisée pour équilibrer les bateaux.
+
+## Embarquement et moteurs
+
+Maj + clic droit prend l'animal sur la tête ; clic droit sur le bateau l'installe
+sur un siège libre. Ce geste fonctionne aussi avec la chauve-souris, qui n'entre
+pas toujours par la collision native. Un bateau plein laisse l'animal porté.
+La capacité est vérifiée côté serveur avant de détacher l'animal.
+
+| Animal | Portance | Vitesse | Maniabilité | Descente |
+| --- | ---: | ---: | ---: | ---: |
+| Poule | 1 | 1 | 1 | 1 |
+| Chauve-souris, jour | 0,75 | 0,95 | 1,30 | 1 |
+| Chauve-souris, nuit | 1,15 | 1,30 | 1,55 | 0,85 |
+| Perroquet | 0,75 | 1,35 | 1,50 | 1,10 |
+| Abeille | 1,50 | 0,80 | 0,90 | 0,65 |
+| Blaze | 2 | 1,10 | 0,80 | 1,15 |
+| Allay | 1,80 | 1,50 | 2,20 | 0,60 |
+| Breeze | 1,30 | 1,60 | 1,80 | 0,75 |
+
+Ces coefficients constituent la première version jouable, ajustable après les
+parties. L'Allay est un excellent moteur polyvalent ; le Blaze privilégie la
+poussée verticale. La chauve-souris utilise l'obscurité native des dimensions
+avec ciel. Les effets des différents moteurs se combinent.
+
+La masse vaut le nombre de passagers vivants hors moteurs, avec un minimum de
+un, plus 0,5 par agrandissement du côté. Les bébés gardent leur exemption de poids.
+La puissance vaut la somme des portances divisée par cette masse. Les cibles de
+montée et de descente sont pondérées, avec un minimum de montée de 30 % ; le
+maximum horizontal est de 20 blocs/s. Un moteur n'accorde pas d'immunité aux
+attaques ordinaires des animaux : les règles natives de capture restent valables.
+
+## Collisions des piles
+
+Les personnes sont comptées dans chaque colonne verticale, pilote compris,
+indépendamment des autres sièges du bateau. Le seuil initial est quatre personnes.
+
+- Jusqu'à trois : seule la personne touchée descend lorsque les passagers du
+ dessus peuvent se raccrocher en sécurité au porteur inférieur.
+- À partir de quatre : la personne touchée descend avec les passagers au-dessus,
+ qui restent assis sur elle. Les personnes en dessous restent en place.
+- Si raccourcir une petite pile placerait un passager dans un bloc, son groupe
+ reste ensemble pendant la descente. Aucune réattribution ne force une collision.
+- Les colonnes voisines sont indépendantes. Un plafond touchant le sommet ne
+ déclenche pas la chute des porteurs inférieurs. Si plusieurs personnes d'une
+ même colonne heurtent un obstacle, la collision la plus basse est traitée.
+
+## Sauvegardes et ressources
+
+Les nouveaux bateaux ont des identifiants `sanctuary:_boat_2x2` et
+`sanctuary:_boat_3x3` (radeau : `bamboo_raft_*`). Le fond est composé de tuiles du
+modèle natif sans rebords, répétées sans changement d'échelle. Les rebords sont
+reconstruits uniquement sur le contour extérieur, puis deux rames de taille
+native sont ajoutées. Le bambou conserve ses tuiles de radeau sans rebords.
+Aucune texture native n'est remplacée ou étirée. Les sièges suivent les centres
+des tuiles, et le masque d'eau couvre le fond complet. Chaque tuile de coque
+mesure 28 × 16 pixels natifs ; le radeau répète son fond 28 × 20 avec sa couche
+supérieure 28 × 16. Les épaisseurs des rebords et des rames restent natives.
+Leurs sièges et leur bois sont déterminés par le type d'entité ; les passagers
+ordinaires suivent la sérialisation Minecraft. Les piles de joueurs restent
+les relations temporaires déjà utilisées par Sanctuary.
+
+Aucun changement de format des mondes, de chunks ou des journaux. Les recettes
+sont ajoutées au savoir existant par la réconciliation normale des collections.
+Le bonus Happy Ghast est transitoire, appliqué à chaque chargement d'entité ; il
+ne s'accumule pas après sauvegarde ou reconnexion. L'entrée de déplacement
+piloté est compensée parce que Minecraft y utilise déjà FLYING_SPEED : le bonus
+s'applique une seule fois à la vitesse effective, au lieu de la quadrupler.
+
+## Vérification
+
+Validation sur Minecraft **26.3-pre-2**, Java 25 :
+
+```sh
+./gradlew check build assemblePack assembleTestPack -PsanctuaryFocusedTests=poultry,carry,skyflight,collections,starinspect
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryQuickTests=true -PsanctuaryPoultryClientTests=true -PsanctuaryClientNoVsync=true
+```
+
+- Build complet et **33 tests serveur** réussis, dont dix nouveaux scénarios :
+ 22 recettes, mélanges refusés, capacités et sauvegarde des passagers, sept
+ espèces embarquées par le geste de portage, refus sans perte sur bateau plein,
+ commandes partagées, poids des piles, exclusion des familiers comme moteurs,
+ collisions des petites et grandes piles, amerrissage et redécollage.
+- Le Happy Ghast est contrôlé par ses attributs, sa sauvegarde et son déplacement
+ natif effectif : le bonus ne s'accumule pas et multiplie le mouvement par deux.
+- **13 440 profils** de charge contrôlés pour les sept espèces, jour/nuit,
+ mélange des moteurs, montée minimale, vitesse et maniabilité bornées.
+- Client natif FR/EN : 22 modèles, fonds répétés sans mise à l'échelle, rebords
+ extérieurs, deux rames, masque d'eau complet ; vraies touches de montée,
+ descente, déplacement et virage sur le véhicule contrôlé par le serveur.
+ Vol prolongé au-delà du délai de surveillance, radeau avec les sept moteurs,
+ bateau simple à chauve-souris et mesure du Happy Ghast à vitesse doublée.
+- Captures relues : [bateau 2 × 2](../build/poultry048-evidence/0000_sanctuary-beta048-2x2-flight.png)
+ et [radeau 3 × 3](../build/poultry048-evidence/0001_sanctuary-beta048-3x3-seven-motors.png).
+
+La coordination de plusieurs pilotes utilise des joueurs simulés côté serveur.
+Le parcours graphique utilise un vrai client local ; une partie avec plusieurs
+clients distants et de la latence reste à essayer pour ajuster les sensations.
+L'équilibrage des espèces constitue une première version jouable.
+
+Les [exports normal](../build/Sanctuary-beta.048.mrpack) et
+[monde plat rapide](../build/Sanctuary-Test-beta.048.mrpack) sont vérifiés.
+Le reçu `build/poultry048-artifact.json` atteste 1 527 classes conformes au build,
+les ressources FR/EN, les dépendances imbriquées, les nouvelles recettes et la
+conservation des PNG, de JEI et des archives beta.047. Aucun déploiement dans
+l'instance personnelle, aucune acceptation d'EULA, aucun monde personnel modifié.
diff --git a/docs/prochains-tickets.md b/docs/prochains-tickets.md
new file mode 100644
index 0000000..f876419
--- /dev/null
+++ b/docs/prochains-tickets.md
@@ -0,0 +1,324 @@
+# Prochains tickets — carte immersive, progression et New Game+
+
+Cadrage du 13 septembre 2026, après l'essai de beta.006.
+Branche documentaire : `codex/roadmap-progression-ngplus`.
+**Statut : MAP-03 implémenté en beta.007, PROG-02A en beta.008, PROG-02B en beta.009, PROG-02C en beta.010 ; CYCLE-01, PROG-03, FACTION-01 et NGP-01 sont réunis dans beta.011.**
+Le cadrage initial organisait la prochaine séance, évoquée pour demain.
+Le créateur a ensuite demandé la carte immersive ; ses vérifications sont
+consignées dans [la livraison beta.007](atlas-immersive-beta007.md).
+
+La version **beta.013** aligne les compétences sur une ligne et abaisse le
+dernier achat à 48 niveaux ([contrat](progression-inline-beta013.md)), après
+les transferts et barres rapides de [beta.012](inventory-flow-beta012.md).
+Les numéros suivants sont des cibles
+indicatives, à attribuer au moment des livraisons sans réutiliser une version.
+Les identifiants de tickets ci-dessous sont locaux au dépôt.
+
+## Ordre de travail
+
+Le créateur demande désormais d'achever la progression avant le New Game+,
+et de concevoir les factions en lien avec celui-ci. Cela complète la priorité
+antérieure carte → Demeure → factions/claims → New Game+.
+
+| Ordre | Ticket | Résultat vérifiable | Prérequis | Livraison visée |
+| --- | --- | --- | --- | --- |
+| Cadrage dès maintenant | CYCLE-01 | Contrat commun progression, recommencement, prestige et factions | Décisions du créateur | [Contrat beta.011](cycle-factions-beta011.md) |
+| 1 | MAP-03 | Carte plein écran, transparence et commandes superposées | MAP-02 livré en beta.006 | [beta.007](atlas-immersive-beta007.md) |
+| 2 | PROG-02A | Compétence Minage et vein mining jouables | Contrat des rangs et des entrées | [beta.008](mining-beta008.md) |
+| 3 | PROG-02B | Compétence Construction et vein building jouables | PROG-02A pour les commandes communes | [beta.009](building-beta009.md) |
+| 4 | PROG-02C | Compétence Inventaire fonctionnelle, objets préservés | Politique d'inventaire CYCLE-01 | [beta.010](inventory-beta010.md) |
+| 5 | PROG-03 | Parcours de progression complet et fin atteignable | PROG-02A/B/C et règle de fin | [beta.011](cycle-factions-beta011.md) |
+| 6 | FACTION-01 | Factions persistantes et capacités articulées au prestige | CYCLE-01 ; progression complète avant validation finale | [beta.011](cycle-factions-beta011.md) |
+| 7 | NGP-01 | Premier recommencement complet, du personnage aux factions | PROG-03, FACTION-01, CYCLE-01 | [beta.011](cycle-factions-beta011.md) |
+
+Le [contrat commun beta.011](cycle-factions-beta011.md) remplace les questions
+ouvertes ci-dessous pour les prix, les rôles et la conservation. Ces quatre
+tickets interdépendants sont livrés ensemble sur `codex/factions-new-game-plus`,
+à la demande du créateur. Les critères initiaux restent conservés ci-dessous.
+La suite est CLAIM-01 : le présent lot n'attribue aucune protection de chunk.
+
+```mermaid
+flowchart LR
+ M[Carte immersive] --> A[Minage]
+ A --> B[Construction]
+ B --> I[Inventaire]
+ I --> P[Progression complète]
+ C[Contrat cycle et factions] --> I
+ C --> F[Factions]
+ P --> F
+ F --> N[New Game+ et prestige]
+ P --> N
+```
+
+## CYCLE-01 — Définir ce qui termine et ce qui recommence
+
+**Branche prévue :** `codex/progression-cycle-contract`.
+**Résultat :** une règle de fin de progression et une matrice de conservation
+suffisamment précises pour implémenter le reset sans inventer de règles.
+
+Décisions déjà acquises : les six compétences sont Vie, Faim, Minage,
+Construction, Souffle et Inventaire. Une mort ordinaire conserve compétences
+et aptitudes ; elle suit la règle d'XP récupérable existante. Un recommencement
+de personnage est distinct : les compétences sont à remonter, les aptitudes
+acquises sont conservées. Demeure mesure l'habitation ; ce n'est pas une
+propriété ni une autorisation de modifier les blocs.
+
+**Condition confirmée par le créateur : les six compétences au maximum.**
+Vie, Faim, Minage, Construction, Souffle et Inventaire doivent chacune atteindre
+leur plafond. Il n'est pas nécessaire d'avoir acheté toutes les aptitudes.
+Les aptitudes déjà acquises sont conservées au recommencement. Le serveur
+évalue les six rangs ; une future aptitude n'ajoute pas un prérequis au NGP.
+
+À fixer dans ce ticket :
+
+- La présentation des six compétences et de leurs plafonds dans le Blocodex,
+ ainsi que le traitement d'une évolution future des plafonds : un équilibrage
+ ne doit pas modifier silencieusement une fin de cycle déjà enregistrée.
+- Le rapport entre New Game+, compteur de prestige et récompense : ce sont
+ des notions à relier explicitement, pas trois synonymes à supposer.
+- La règle des charges ou places de faction : capacité d'en créer une,
+ d'agrandir une faction, d'en rejoindre plusieurs ou autre effet ; quantité,
+ détenteur de la capacité et éventuelle mise en commun.
+- Pour identité, bio, couleur, aptitudes, compétences, achats, XP restante,
+ objets, recettes/découvertes, Atlas, Demeure, exploits, argent et affiliations :
+ indiquer conservé, réinitialisé ou archivé. Les aptitudes conservées ne peuvent
+ pas dépendre accidentellement d'un ancien rang qui les rendrait inutilisables.
+- Le sort des objets quand les emplacements d'inventaire diminuent, et celui
+ d'une faction lorsque son créateur recommence. Aucune suppression implicite
+ d'objets, de membres, de constructions ou d'archives collectives.
+
+**Acceptation :** deux exemples chiffrés avant/après (premier puis second
+cycle), un cas avec inventaire plein et un cas avec faction existante. Vérifier
+qu'aucun prérequis ne forme une boucle impossible : finir la progression ne
+peut pas exiger une faction si sa création exige le premier prestige.
+Ce ticket ne régénère pas le monde et n'introduit pas la quête finale donnant
+collectivement accès au créatif ou aux droits opérateur.
+
+## MAP-03 — Faire de la carte le fond du Blocodex
+
+**Branche :** `codex/atlas-immersive`.
+**Implémentation :** [beta.007 et ses vérifications](atlas-immersive-beta007.md).
+**Résultat :** la carte occupe l'écran ; l'interface se pose dessus.
+
+Le rendu de beta.006 était limité à 720 unités GUI en largeur, réservait deux lignes
+de commandes et un pied de coordonnées, puis découpait la texture dans la bande
+restante. Cette combinaison produisait le rectangle étroit observé en beta.006.
+
+Demandes visuelles retenues :
+
+- Étendre la surface cartographique jusqu'aux bords de l'écran, sous les zones
+ assombries du haut et du bas. La partie directement lisible rejoint les
+ boutons supérieurs et descend jusqu'au bouton de retour, actuellement
+ **Done / Terminé** (appelé « down » dans la discussion).
+- Conserver un carré éclairci au centre, avec la carte plus sombre autour.
+ Ce carré est un traitement de lumière : il ne découpe ni ne limite la carte.
+ Adapter sa taille au GUI ; régler les opacités sur des captures en jeu.
+- Rendre transparent ce qui n'a pas de surface cartographique à dessiner.
+ Le vide observé et l'inconnu gardent leurs états distincts côté serveur,
+ même si leur représentation utilise la transparence. Le fond du menu peut
+ rester visible ; aucun terrain cartographique inconnu n'est reconstruit.
+- Superposer coordonnées et zoom en bas à gauche, avec contraste suffisant.
+ Les onglets, le retour, le recentrage, le zoom, la grille, Demeure et le mode
+ opérateur restent des commandes accessibles ; leurs panneaux n'enlèvent plus
+ deux bandes de hauteur à la carte.
+- Donner la priorité des clics aux boutons. Le reste de la carte, y compris
+ les zones assombries, accepte déplacement et zoom. Garder les glissements
+ successifs et le relâchement hors zone validés en beta.006.
+
+**Acceptation :** captures à 640×480, 1280×720 et écran large, plusieurs échelles
+GUI ; carte visible jusque sous les habillages, coordonnées lisibles, commandes
+sans chevauchement. Vérifier transparence du vide, brouillard, panorama déplacé,
+zone sans relevés, mode verrouillé, vue opérateur, révocation et retour parent.
+La grille et les empreintes restent alignées avec la carte sur tout l'écran.
+
+**Données :** rendu client et disposition ; conserver les relevés et identifiants
+existants. Aucun format de sauvegarde ou changement de génération attendu.
+**Hors lot :** minimap, marqueurs, partage et nouveaux droits de claim.
+
+## PROG-02A — Minage et vein mining
+
+**Branche :** `codex/progression-vein-mining`.
+**Implémentation :** [beta.008, contrat et vérifications](mining-beta008.md).
+**Résultat :** acheter des rangs de Minage et extraire un groupe de blocs par
+un geste volontaire, prévisualisé et limité par la progression du serveur.
+
+- Remplacer « À venir » par rang, effet présent, prochain effet et coût.
+ Minage reste une compétence dans la colonne gauche.
+- Reprendre la sélection historique de blocs identiques connectés par leurs
+ faces, son aperçu, son arrêt et ses limites. L'orientation en plan, évoquée
+ dans la vision, ne doit pas être annoncée comme déjà fournie par cet
+ algorithme de veine ; son éventuel ajout demande un mode explicite.
+- Une touche configurable maintenue avec le clic gauche engage l'action.
+ Une souris sans bouton latéral doit pouvoir l'utiliser ; B garde le Blocodex.
+ Prévoir le choix d'une taille inférieure à celle autorisée par le rang.
+- Le serveur recalcule la sélection, applique les règles des outils, la durée,
+ la durabilité, la faim, les enchantements et les permissions de chaque bloc.
+ Interrompre au relâchement, changement d'outil, mort ou déconnexion.
+- Utiliser les actions natives pour les drops et l'XP ; une casse effective
+ produit une seule découverte/statistique/trace Demeure. La sélection et
+ l'aperçu ne produisent aucune récompense ni influence.
+
+**Référence historique, à équilibrer :** rangs 0–7, limites
+`1 / 4 / 8 / 16 / 32 / 64 / 128 / 256`, recherche à sept blocs sur chaque axe.
+Le multiplicateur de vitesse historique va de 25 % à 100 %. Cette lenteur
+initiale, ces quantités et les anciens coûts ne constituent pas encore un
+équilibrage approuvé pour la bêta. Les valeurs d'essai seront documentées.
+
+**Acceptation :** rang zéro, achat et plafond, outil adapté/inadapté, usure jusqu'à
+rupture, Fortune/Silk Touch, cible indestructible, coffre exclu, arrêt immédiat,
+deux joueurs sur la même veine, chunk non chargé, permission refusée et requête
+client demandant un rang supérieur. Son du groupe maîtrisé et aperçu cohérent.
+
+**Données :** rédiger le contrat d'évolution des rangs et de l'historique avant
+le code ; relire beta.003–006 sans changer les achats, aptitudes, identité ou
+couleur. Reconnexion, redémarrage et mort conservent les rangs acquis.
+
+## PROG-02B — Construction et vein building
+
+**Branche :** `codex/progression-vein-building`.
+**Implémentation :** [beta.009, contrat et vérifications](building-beta009.md).
+**Résultat :** acheter des rangs de Construction puis poser une surface annoncée
+par l'aperçu, en consommant les blocs réellement utilisés.
+
+- Activer la compétence de gauche avec rang, effet et coût. Reprendre les plans
+ historiques : face cliquée et orientation du joueur déterminent sol, mur
+ ou plafond et ordre de pose.
+- Partager les conventions de commande avec Minage : touche reconfigurable
+ et clic droit, taille ajustable dans le plafond acheté, arrêt au relâchement.
+ La touche B historique du Builder doit être remplacée.
+- Recalculer la sélection sur le serveur ; poser par l'action Minecraft
+ normale pour respecter orientation, supports, collisions et permissions.
+ Revalider chaque cible si le monde change pendant l'opération.
+- Consommer seulement les objets effectivement posés ; arrêter proprement à
+ l'épuisement du stock. Préserver les refus et les contenus des blocs.
+ Chaque pose réussie alimente une seule fois Blocodex, statistiques et Demeure.
+- Séparer clairement capacités de construction, mode créatif et statut
+ opérateur. Un opérateur en survie ne reçoit pas des matériaux gratuits.
+
+**Référence historique, à équilibrer :** rangs 0–7, diamètres
+`1 / 3 / 5 / 7 / 9 / 11 / 13 / 15` ; cadence de survie historique 0,5 pose par
+tick. La portée 3–5,5 blocs historique est distincte du diamètre du plan.
+Décider explicitement si la compétence reprend aussi cette portée, sans
+modifier la portée d'attaque. Coûts et cadence à éprouver avec Minage.
+
+**Acceptation :** sol/mur/plafond, quatre orientations, dalles et escaliers,
+stock insuffisant, cible occupée, annulation en cours, changement d'objet,
+limite de chunk chargé, deux bâtisseurs concurrents et permission refusée.
+L'aperçu ne consomme pas d'objet ; une pose refusée n'est pas comptabilisée.
+Les rangs et achats survivent à la mort et au redémarrage.
+
+## PROG-02C — Terminer la compétence Inventaire
+
+**Branche :** `codex/progression-inventory`.
+**Implémentation :** [beta.010, contrat et vérifications](inventory-beta010.md).
+**Résultat :** une sixième compétence achetable et utile, avec une capacité
+réelle, la même côté serveur et dans tous les menus.
+
+Cadrer le nombre de rangées, le rôle de la barre rapide et les plafonds.
+La proposition historique d'une à six rangées reste une référence à confirmer,
+pas l'état actuel de beta.006. Les parties déjà remplies exigent un contrat
+d'adoption : activer un petit inventaire ne peut pas supprimer des objets.
+
+**Acceptation :** clic, glisser-déposer, déplacement rapide, collecte, fabrication,
+conteneurs, mort, `keepInventory`, reconnexion et rechargement, sans duplication
+ni disparition. Contrôler le protocole des emplacements ; un client ne peut pas
+utiliser une case verrouillée. Préparer le cas de réduction au New Game+ selon
+CYCLE-01, sans activer un reset dans ce ticket.
+
+## PROG-03 — Rendre la progression achevable
+
+**Branche prévue :** `codex/progression-complete`.
+**Résultat :** un nouvel habitant peut comprendre, financer et terminer le
+parcours retenu ; le serveur sait dire précisément ce qu'il lui manque.
+
+- Les six compétences sont fonctionnelles et testées ensemble. Fixer rangs,
+ plafonds et coûts, et éprouver les interactions Faim/Minage/Construction.
+- Vérifier les aptitudes présentes, leurs conditions d'achat et leur
+ conservation au prochain cycle. Les aptitudes futures gardent leurs tickets
+ propres et ne retardent pas la fin des six compétences ; aucun bouton
+ achetable ne promet un effet absent.
+- Donner une indication accessible pour obtenir les premiers niveaux. Choisir
+ entre tutoriel des sources d'XP existantes et objectifs de découverte à usage
+ limité, puis vérifier un début de partie en survie sans commandes opérateur.
+ Toute récompense de première découverte doit définir sa persistance au NGP.
+- Appliquer la condition confirmée **six compétences au maximum** et afficher
+ les compétences encore incomplètes dans le Blocodex. Le contrôle repose sur
+ les rangs serveur. Des aptitudes non achetées n'empêchent pas le NGP.
+
+**Acceptation :** cinq compétences maximales et une incomplète refusent le
+NGP ; les six maximales l'autorisent même si des aptitudes restent non acquises.
+Vérifier parcours début/milieu/fin, insuffisance d'XP, achats doublés,
+mort avant et après achat, déconnexion, sauvegarde et reprise. Le futur NGP
+reste inaccessible tant que ses prérequis ne sont pas remplis. Documenter
+les réglages retenus et les limites ouvertes avant de déclarer la progression
+terminée. Fournir un MRpack d'essai de cette boucle complète.
+
+## FACTION-01 — Factions persistantes, capacités et prestige
+
+**Branche prévue :** `codex/factions-foundation`.
+**Résultat :** créer, rejoindre et administrer une faction avec des identités
+stables, et expliquer les capacités liées au prestige dans le Blocodex.
+
+Définir rôles, invitations/acceptation, départ, transfert et dissolution,
+ainsi que la distinction entre couleur d'habitant et identité de faction.
+Implémenter les charges/places selon CYCLE-01 ; leurs montants et bénéficiaires
+ne sont pas déduits des anciens « slots ». Fournir l'opération serveur qui
+recevra la récompense d'un cycle une seule fois ; NGP-01 en sera le déclencheur.
+
+**Acceptation :** au moins deux habitants, invitations concurrentes, absence
+de l'administrateur, déconnexion, rechargement et capacité épuisée. Un reset
+personnel ne peut pas effacer implicitement une faction ou ses membres.
+La progression ordinaire, les permissions opérateur et les rôles de faction
+restent distincts. Aucun droit de modification de terrain n'est déduit d'une
+empreinte Demeure ou de l'appartenance à une faction.
+
+## NGP-01 — Premier cycle complet
+
+**Branche prévue :** `codex/new-game-plus`.
+**Résultat :** un habitant éligible termine son parcours, choisit de recommencer,
+retrouve ses aptitudes et reçoit l'effet de prestige/faction convenu.
+
+- Afficher avant le choix ce qui est conservé, remis à zéro et archivé.
+ Le déclenchement est volontaire et validé côté serveur.
+- Appliquer ensemble la nouvelle progression, le traitement d'inventaire et
+ l'effet de prestige/faction. Identifier le cycle pour qu'une répétition de
+ requête ou une reprise après interruption ne donne pas deux récompenses.
+- Garder la mort ordinaire séparée ; annuler une demande ne modifie aucune
+ donnée. Le caractère individuel du recommencement ne permet pas de déduire
+ une régénération du monde commun.
+- Enregistrer un événement daté de cycle, avec identifiants stables d'habitant
+ et de faction lorsque pertinent, pour la mémoire du serveur et le futur
+ site. Ce lot ne construit pas la connexion Web.
+
+**Acceptation :** premier puis deuxième cycle, une compétence sous le maximum,
+six compétences au maximum sans toutes les aptitudes, aptitudes
+conservées, compétences remises à leur départ décidé, objets traités sans perte,
+faction existante, répétition de requête, interruption et redémarrage. Tester
+la reprise à chaque étape de la transition ; aucune récompense de découverte
+ou de prestige ne doit être encaissée deux fois pour un même événement.
+
+## Après ce jalon
+
+**CLAIM-01** traitera la réservation et la protection des chunks à partir des
+factions et d'un contrat dédié : emprise, capacités, droits, conflits, abandon
+et incidence du prestige. La grille et Demeure restent des lectures du monde
+jusqu'à ce ticket. Mini-carte, marqueurs, partage, recettes, plans et autres
+aptitudes gardent leurs tickets propres. Leur absence ne bloque pas le New
+Game+ lorsque les six compétences sont au maximum.
+
+## Références et validation des livraisons
+
+La référence historique est consultée en lecture seule dans `../26.2/sanctuary` :
+[contrat de progression](../../26.2/sanctuary/PROGRESSION.md),
+[limites par rang](../../26.2/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryBulkActionLimits.java),
+[sélection des veines](../../26.2/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/mining/SanctuaryVeinSelection.java)
+et [plans de pose](../../26.2/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/building/SanctuaryBuildPlane.java).
+Ces sources attestent les mécanismes cités, sans prouver leur portabilité.
+La cible de travail reste celle de `gradle.properties`, actuellement 26.3-pre-2.
+
+Pour chaque ticket de code : contrat de données préalable si nécessaire,
+libellés FR/EN, `./gradlew check build`, tests natifs ciblés et parcours client
+adaptés au comportement, puis `./gradlew assemblePack` et MRpack immuable
+lors d'une livraison. Incrémenter mod et pack ensemble. Les actions groupées
+respectent les protections effectives ; aucun test ne doit inventer des claims
+absents. Conserver les mondes personnels et les anciens artefacts.
diff --git a/docs/progression-beta003-contract.md b/docs/progression-beta003-contract.md
new file mode 100644
index 0000000..68c5c82
--- /dev/null
+++ b/docs/progression-beta003-contract.md
@@ -0,0 +1,65 @@
+# beta.003 — contrat de progression et d'activation
+
+Ticket PROG-01, autorisé le 13 septembre 2026. Ce contrat précède les changements
+de sauvegarde ; il ne modifie aucun générateur ni aucune expansion.
+
+## Activation
+
+Les nouveaux mondes utilisant le générateur Sanctuary activent la progression.
+Les mondes possédant déjà des données de joueurs et aucun registre beta.003
+conservent leur fonctionnement. Pour les adopter explicitement : sauvegarder
+le monde, arrêter le serveur, puis mettre `enableExistingWorlds` à `true` dans
+`config/sanctuary/progression.json`. Au prochain démarrage, chaque joueur crée
+sa fiche et reçoit les capacités initiales 3/3/3 ; inventaire, XP, statistiques,
+advancements et terrain sont conservés. La santé, nourriture et respiration
+courantes sont ramenées aux nouvelles capacités. Aucun import automatique des
+sauvegardes de 26.2. Les mondes vanilla ne sont pas activés.
+
+## Données additives
+
+`data/sanctuary-inhabitants.json` conserve un registre versionné des identités,
+la graine de palette, les attributions RGB et la première arrivée. L'UUID
+authentifié est l'identité ; le client ne choisit ni UUID ni pseudo de compte.
+La création est validée pendant la configuration réseau, avant l'entrée en jeu.
+La sélection d'une couleur est revalidée au moment de l'enregistrement.
+
+L'attachement Fabric `sanctuary:progression` du joueur conserve une enveloppe
+versionnée : rangs, aptitude Noms et journal des achats. Il est enregistré dans
+le même fichier joueur que l'XP et copié lors d'une mort. Les achats retirent
+les niveaux et mettent à jour l'attachement sur le thread serveur, avec un
+numéro de révision empêchant la répétition d'une même requête. La sauvegarde
+vanilla persiste ensemble le solde et le résultat. Un arrêt brutal peut perdre
+les actions depuis la dernière sauvegarde, comme les autres données Minecraft.
+Le registre d'identités est écrit atomiquement et relu ; un registre invalide
+ou modifié extérieurement bloque la progression et n'est pas réinitialisé.
+
+Les sept améliorations de chaque capacité coûtent provisoirement
+`1, 2, 4, 8, 16, 32, 64` niveaux. Noms coûte provisoirement 4 niveaux.
+Ces prix sont configurables côté serveur. S'asseoir est disponible dès le début.
+La mort laisse par défaut 70 % des points d'XP disponibles en orbes vanilla,
+soit 6 069 points pour un joueur au niveau 60 sans fraction de barre (environ
+52 niveaux récupérés depuis zéro). `deathXpFraction` accepte 0 à 1.
+`keepInventory=true` conserve l'XP sans créer d'orbes supplémentaires.
+Le prestige et le New Game Plus ne sont pas introduits dans ce lot.
+
+## Noms des créatures
+
+Les familles et la sélection déterministe par UUID proviennent du code Sanctuary
+26.2. `config/sanctuary/mob-names.json` ajoute des noms par identifiant de type
+d'entité, avec activation globale et choix d'ajouter ou remplacer les noms
+par défaut pour chaque type. Une étiquette nommée par un joueur reste prioritaire.
+Le nom est une révélation personnelle de la créature visée, sans renommer
+l'entité dans la sauvegarde. Changer les listes peut changer les noms calculés.
+
+Les faits d'arrivée et d'achat sont datés en UTC, rattachés à l'UUID dans le
+registre ou le fichier joueur. Leur future projection commune utilisera
+`sanctuary:inhabitant_created` et `sanctuary:progression_purchased`. Cette
+projection, l'export Web et la timeline publique restent un autre ticket.
+
+## Retour arrière
+
+Restaurer la sauvegarde préalable est la méthode pour annuler une adoption.
+Ne pas supprimer seulement le registre : les capacités et achats des joueurs
+font partie du même contrat. Garder une copie avant de charger avec un ancien
+mod susceptible d'ignorer les attachements. Les fichiers invalides sont à
+réparer hors ligne ; aucun outil ne les écrase pour recommencer silencieusement.
diff --git a/docs/progression-beta003-release.md b/docs/progression-beta003-release.md
new file mode 100644
index 0000000..b65a118
--- /dev/null
+++ b/docs/progression-beta003-release.md
@@ -0,0 +1,140 @@
+# beta.003 — premier habitant et première aptitude
+
+Livraison locale d'essai, 13 septembre 2026.
+Minecraft **26.3-pre-2**, Fabric Loader **0.19.5**, Fabric API **0.160.0+26.3**.
+Aucun ajout de JEI, Xaero ou autre dépendance non vérifiée pour cette cible.
+
+## Essayer
+
+Importer `build/Sanctuary-beta.003.mrpack`, puis créer un **nouveau monde
+Sanctuary**. La fiche HELLO_WORLD apparaît avant l'entrée en jeu : skin et
+pseudo du compte, bio facultative, couleur personnelle. Des boutons Minecraft
+classiques portent leur code en SGA, avec seulement les glyphes teintés de la
+couleur correspondante ; l’infobulle garde le code hexadécimal classique.
+Convention Sanctuary : les chiffres `0..F` correspondent aux seize glyphes
+SGA `A..P`, car la police vanilla ne contient pas de chiffres. Six glyphes
+conservent donc exactement les 24 bits RGB, sans symbole manquant. Après validation,
+le serveur annonce `helloworld {pseudo}` une seule fois.
+
+Le **Blocodex s'ouvre avec B ou depuis Échap**. Il regroupe la fiche, les
+améliorations, l'histoire personnelle et le catalogue de blocs existant.
+L'onglet Progression sépare les **compétences à gauche** des **aptitudes à
+droite**, dans une liste à défilement indépendant. Les six compétences sont
+Vie, Faim, Minage, Construction, Souffle et Inventaire (Health, Hunger, Mining,
+Building, Breath, Inventory en anglais). Minage, Construction et Inventaire
+sont indiquées **À venir**, sans achat : leurs mécaniques feront l'objet du
+prochain ticket, conformément au choix du créateur. Noms et S'asseoir figurent
+dans les aptitudes.
+Il ne demande aucun item à fabriquer. Le livre de recettes de l'inventaire
+reste accessible ; son remplacement visuel appartient à la suite des menus.
+
+- Départ : 3 cœurs, 3 icônes de nourriture, 3 bulles sous l'eau.
+- Sept améliorations par capacité, jusqu'à 10 ; prix provisoires
+ `1, 2, 4, 8, 16, 32, 64` niveaux. Le premier cœur coûte donc 1 niveau.
+- **Noms des mobs** : 4 niveaux. Révèle le nom de la créature visée.
+ Les étiquettes posées par les joueurs gardent la priorité.
+- **S'asseoir** : gratuit dès le départ. Deux appuis brefs sur Accroupir,
+ en restant immobile au sol, ou bouton de la fiche. Accroupir pour se relever.
+- Une mort conserve les achats. Les orbes vanilla contiennent par défaut
+ 70 % des points d'XP disponibles et peuvent être ramassées par tout joueur.
+ `keepInventory` garde son effet habituel.
+
+## Configuration serveur
+
+Les fichiers sont créés au premier démarrage d'un monde Sanctuary dans
+`config/sanctuary/`. Les réglages de progression nécessitent un redémarrage.
+Exemple complet de `progression.json` :
+
+```json
+{
+ "schema": 1,
+ "enableExistingWorlds": false,
+ "initialColors": 20,
+ "statCosts": [1, 2, 4, 8, 16, 32, 64],
+ "namesCost": 4,
+ "deathXpFraction": 0.7
+}
+```
+
+`mob-names.json` permet d'ajouter ou de remplacer les familles de noms de 26.2
+pour chaque identifiant d'entité, y compris ceux des futurs mods :
+
+```json
+{
+ "schema": 1,
+ "enabled": true,
+ "types": {
+ "minecraft:zombie": {
+ "includeDefaults": true,
+ "names": ["PoupouLeZombie", "AncienExplorateur"]
+ },
+ "minecraft:cow": {
+ "includeDefaults": false,
+ "names": ["Marguerite", "Rosalie"]
+ }
+ }
+}
+```
+
+Recharger avec **`/sanctuary names reload`**, depuis la console ou un compte
+opérateur. Une configuration invalide conserve la dernière configuration
+valide et produit une erreur explicite. Limites : 256 types, 128 noms par type,
+64 caractères par nom, fichier de 60 000 octets maximum. Les caractères de
+contrôle et codes de formatage sont refusés. Les étiquettes restent prioritaires.
+Changer une liste peut changer les noms déterministes déjà affichés.
+
+La palette s'étend lorsqu'elle est pleine. Aucune attribution antérieure
+n'est déplacée. L'adoption d'une sauvegarde beta.002 ou alpha est volontaire,
+sans modifier ses chunks : voir le [contrat de données](progression-beta003-contract.md).
+
+## Vérifications
+
+Essais macOS / Java 25, dans les répertoires de développement ignorés :
+
+- `./gradlew check build --continue` exécuté : contrôles purs réussis, dont
+ identités, concurrence des couleurs, extension de palette, corruption
+ refusée, persistance, rangs et courbe d'XP. La suite native générale donne
+ **37/39**, dont **4/4 nouveaux GameTests de progression réussis** : capacités,
+ nourriture et sprint, achats autoritaires et idempotents, respawn, siège
+ gratuit, noms configurables et permissions opérateur.
+- Les deux échecs sont les assertions worldgen déjà consignées dans
+ [le versionnement](versioning.md) : hauteur attendue de 384 au lieu de 640
+ dans `populationTerrainAndNaturalSpawnRemainPresent`, et support sous un
+ sédiment de graine 0 dans `retainedHydrologySurvivesNativeDecoration`.
+ **Le contrôle global n'est donc pas vert.** Ce ticket ne les corrige pas.
+- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true
+ -PsanctuaryProgressionClientTests=true` réussit sur le code final : véritable
+ HELLO_WORLD avant l'apparition et l'annonce d'arrivée, 3/3/3, achat d'un
+ cœur puis de Noms, noms personnalisés et priorité des étiquettes, accès B
+ et Échap, six compétences, défilement indépendant des aptitudes et contrôles
+ de dimensions aux deux échelles de GUI. Une mort réelle puis un redémarrage
+ conservent la fiche et les deux achats sans répéter l'accueil. Le test
+ vérifie aussi 6 069 points d'XP laissés pour un solde de niveau 60.
+- Les captures natives finales ont été inspectées : boutons Minecraft à
+ glyphes SGA colorés, six compétences à gauche et liste d'aptitudes à droite.
+ Copies dans `build/beta003-preview/`.
+- `./gradlew assemblePack -x check` réussit après ces contrôles : JAR final,
+ versions et index packwiz vérifiés. Les contrôles déjà exécutés ne sont
+ pas relancés pendant cet assemblage.
+- Export packwiz vérifié : CRC ZIP, versions interne et externe `beta.003`,
+ unique JAR Sanctuary identique au build, classes et mixins attendus,
+ ressources JSON et clés FR/EN, empreinte de Fabric API et index packwiz.
+ L'archive ne contient ni monde, ni réglage personnel, ni mod de test.
+
+**[MRpack local](../build/Sanctuary-beta.003.mrpack)** — 2 208 926 octets.
+SHA-256 : `2b31d67befb4838d0f6da307c3ac0d1dfe5ba41978e9d8b73d5c2983108b9db0`.
+JAR SHA-256 : `44544793d318d2be8bf5cdc6ab4e80d752b886debcf383bedca04ed04da0ae1d`.
+Reçu : `build/progression003-artifact.json`.
+
+Journaux : `build/beta003-check-build.log`,
+`build/progression-client-final.log`, `build/beta003-assemble-pack.log`.
+Les essais Windows et une session entre plusieurs clients distants restent
+ouverts ; le parcours client validé utilise le serveur intégré natif.
+
+## Suite ouverte
+
+Prestige/NG+, overlay des points de vie des mobs, découverte des items et
+entités dans le catalogue, nouveaux moyens d'XP, carte, recettes communautaires,
+étoiles, factions et connexion Web restent des tickets distincts. L'historique
+présente les faits de cette première progression ; il n'est pas encore une
+timeline unifiée de Demeure, de l'Atlas et du Web.
diff --git a/docs/progression-beta003.md b/docs/progression-beta003.md
new file mode 100644
index 0000000..0b73fb7
--- /dev/null
+++ b/docs/progression-beta003.md
@@ -0,0 +1,206 @@
+# PROG-01 — Commencer la progression
+
+Discussion du 12 septembre 2026. Branche `codex/progression-beta003`.
+**Premier lot livré localement le 13 septembre 2026 en beta.003.**
+Le [contrat de données et d'activation](progression-beta003-contract.md) précède
+le code ; les [notes de livraison](progression-beta003-release.md) distinguent
+le lot jouable de la vision et des réglages encore ouverts.
+
+## Direction retenue
+
+Sanctuary est un jeu ludergent : les différences de connaissances, de moyens,
+de lieux et d'intérêts peuvent faire naître des relations, des projets puis des
+organisations. La progression doit soutenir ces possibilités. Les métiers,
+factions et parcours sociaux ne sont pas des classes imposées à la création.
+
+Le **Blocodex est la nature diégétique des menus et du GUI**. Son langage visuel
+est une tablette avec un grand écran et des boutons ; il se métamorphose selon
+le contexte et les aptitudes disponibles. Il ne nécessite pas un item-tablette
+à fabriquer ou à porter. Sa matérialisation en objet serait un autre chantier.
+Le premier lot utilise quatre onglets. Dans Progression, les six compétences
+occupent la colonne gauche : Health, Hunger, Mining, Building, Breath, Inventory.
+Les aptitudes forment une liste défilante indépendante à droite. Le créateur a
+choisi de réserver la place de Mining, Building et Inventory dans beta.003,
+avec la mention « À venir » ; leurs mécaniques de 26.2 attendent le prochain ticket.
+
+## Décisions de jeu
+
+| Sujet | Décision de l'auteur |
+| --- | --- |
+| Départ | Trois cœurs, trois icônes de nourriture et trois bulles de respiration. |
+| Achats | L'XP et les niveaux permettent d'acheter des améliorations. Courbe et prix détaillés à équilibrer. |
+| Compétences | Progressions à remonter lors d'un recommencement du personnage. Le classement exact des améliorations reste à établir. |
+| Aptitudes | Déblocages conservés lors de ce recommencement. |
+| Mort ordinaire | Les compétences et aptitudes acquises restent. L'XP non dépensée quitte le joueur ; une part importante éclate en orbes récupérables par lui ou par d'autres joueurs. |
+| Quantité d'XP laissée | Viser une récupération substantielle, par exemple l'équivalent d'environ 40–50 niveaux pour un personnage qui en avait 60. Aucun taux exact arrêté ; calculer et équilibrer en points d'XP. |
+| Prestige / New Game Plus | Recommencement distinct de la mort ordinaire. Déclenchement, périmètre et récompenses encore ouverts. |
+| Carte | Minimap, grande carte et marqueurs sont trois aptitudes distinctes. Les découvertes se transmettent de joueur à joueur. |
+| Recettes | Séparer accès au catalogue et révélation des recettes. La fabrication reste libre si le joueur sait composer la recette. |
+| Advancements | Priorité aux accomplissements Minecraft. Ceux de Sanctuary portent surtout les événements et jalons du serveur ; ils peuvent débloquer les prochaines expansions. |
+| Catalogue Blocodex | Viser les blocs, items et entités. Les manques peuvent susciter des pistes de découverte. Le socle actuel ne couvre encore que les blocs. |
+| Rythme du monde | Temps réel, visiteurs et activités donnant des occasions de découverte, d'échange et de jeu collectif. Quêtes, concours, courrier, Gazette et ordinateurs restent à concevoir par lots. |
+
+Les précédentes propositions de minage initial lent, portée, inventaire d'une
+à six rangées et coûts `1, 2, 4, 8, 16, 32, 64` restent des références à tester.
+Elles ne forment pas un équilibrage accepté de beta.003. La relation entre
+prestiges, anciens « slots de factions » et nouvelles « charges de faction »
+reste également à préciser.
+
+Le tutoriel avec un budget limité d'XP de premières découvertes est une
+**proposition de travail**. Son montant et sa conservation au prestige ne sont
+pas encore des décisions de l'auteur. Une récompense initiale répétable à
+chaque reset permettrait de farmer ce budget et devra être évitée si cette
+proposition est retenue.
+
+## HELLO_WORLD et interface
+
+La première connexion à un serveur passe par la création de la fiche avant
+l'apparition dans le monde et avant l'annonce d'arrivée. L'écran emploie les
+widgets Minecraft : pseudo et skin présentés, bio saisissable, couleur choisie
+parmi les couleurs disponibles. Le caractère éditable du pseudo ou du skin
+n'a pas été défini ; leur présence sur la fiche ne promet pas un éditeur.
+Les couleurs emploient des boutons Minecraft classiques ; seuls les glyphes
+SGA du code sont colorés. L'infobulle conserve l'hexadécimal lisible.
+
+Le serveur initialise une palette personnalisée depuis la graine du monde,
+avec **20 couleurs par défaut** et une taille configurable. La palette est
+extensible ; les habitants conservent leur couleur. L'extension n'altère donc
+aucune attribution antérieure. Les couleurs ne sont pas limitées aux teams
+vanilla. Une reconnexion retrouve la fiche sans répéter la création.
+
+Après validation, le serveur adresse le message `helloworld {pseudo}`, par
+exemple `helloworld poupoutain`. Le prototype adresse cet accueil au serveur, une seule fois par habitant.
+Les droits d'habitant décrits par la Constituante commencent au HELLO_WORLD
+et ne sont pas achetés avec de l'XP.
+
+Le menu principal prend l'identité Sanctuary, avec un resource pack dédié et
+les entrées principales Single Player, Multiplayer et Friends ; Realms est
+retiré. Le menu Échap doit donner accès à Statistics, Advancements, Progression,
+à la carte et aux autres fonctions disponibles. La disposition se recompose
+à mesure des déblocages et selon la taille d'écran. Le bas du menu accueille
+les charges de faction et le solde du coffre-fort, ce dernier en bas à gauche.
+Ces indicateurs demandent leurs véritables systèmes serveur avant affichage.
+
+Le bouton de l'inventaire à la place du livre de recettes, B et les accès du
+menu Échap sont les **accès proposés** à cette interface commune. Leur mise en
+page reste à éprouver ; il ne faut pas créer trois applications concurrentes.
+
+## Aptitudes déjà demandées
+
+| Aptitude | Intention | Détail encore ouvert |
+| --- | --- | --- |
+| Noms des mobs | Afficher les noms des créatures. | Registres de noms et coût ; l'ancienne version fournit une référence procédurale. |
+| Vie des mobs | Un overlay révèle les points de vie. | Le geste évoqué semble être l'accroupissement ; cible, portée et présentation à préciser avant ce lot. |
+| S'asseoir | Aptitude débloquée de base, disponible dès l'arrivée sans achat ni coût d'XP. | Geste et positions admises ; l'ancien double accroupissement reste une référence. |
+| Minimap | Accès à la petite carte. | Contrôle des accès et compatibilité Xaero. |
+| Grande carte | Accès à la carte plein écran. | Contrôle des accès et partage des découvertes. |
+| Marqueurs | Utilisation des marqueurs cartographiques. | Création, transmission et connaissances nécessaires. |
+| Catalogue de recettes | Consultation des recettes révélées. | Intégration JEI et présentation Blocodex. |
+
+Enchantement, alchimie, waystones, noms, shop, pouvoir actif du familier et
+autres déblocages retrouvés en 26.2 sont des candidats historiques ; leur
+présence dans l'ancien code ne décide ni leur reprise ni leur prix.
+
+## Histoire commune, ciel et site
+
+Le ciel commence sans étoiles. La première arrivée d'un habitant crée son
+étoile ; les advancements Minecraft réalisés par chaque habitant enrichissent
+le ciel partagé, avec des étoiles adjacentes selon l'accomplissement. La
+rotation stellaire doit accomplir un tour en sept jours réels. Organisation
+par habitant ou par advancement, géométrie des groupes, quantité d'étoiles
+adjacentes et traitement des cycles restent ouverts.
+
+Le mod et le site doivent partager les identités et le sens des faits :
+habitant, personnage/cycle, faction, lieu, découverte, transmission,
+accomplissement, expansion, document et étoile. Le bouton Historique donnera
+accès aux événements pertinents, notamment ceux de Demeure et de la carte.
+La connexion au Web doit permettre des conséquences dans les deux sens, avec
+validation des changements du jeu par le serveur.
+
+Certaines révélations ouvrent un contenu Web pour tous ; les connaissances
+personnelles gardent leurs règles de transmission. La portée de publication
+appartient donc à chaque événement. Les solutions d'énigmes et détails de la
+quête finale ne sont pas inclus dans ce ticket de socle.
+
+L'actuel [historique matériel](blocodex.md) est agrégé par jour, sans auteur ni
+lieu. Il ne permet pas de reconstruire la timeline demandée. Le futur journal
+devra conserver les identifiants, participants, date, contexte et causalité
+utiles, éviter les doublons après reprise et fonctionner sans disponibilité
+permanente du site. Le contrat d'événements détaillé reste à écrire.
+
+## Audit des mods communautaires
+
+Lecture de l'API publique Modrinth le **12 septembre 2026**, avec filtres exacts
+`loaders=["fabric"]` et `game_versions=["26.3-pre-2"]`. Le pack courant ne
+référence que Fabric API en dépendance externe. Cet audit vérifie des
+déclarations de distribution ; il ne constitue pas un essai d'exécution et
+n'exclut pas une distribution différente sur un autre canal.
+
+| Projet | Fichiers déclarés pour la cible exacte | Dernière publication Fabric observée |
+| --- | --- | --- |
+| JEI | 0 | `30.32.0.215`, Minecraft 26.2 |
+| Xaero's Minimap | 0 | `fabric-26.2-26.5.0`, Minecraft 26.2 |
+| Xaero's World Map | 0 | `fabric-26.2-1.46.0`, Minecraft 26.2 |
+| Sodium | 0 | `mc26.3r1-0.9.2-beta.1-fabric`, Minecraft 26.3-rc-1 |
+| Iris | 0 | `1.11.3+26.1-fabric`, Minecraft 26.1–26.1.2 |
+
+Sources API interrogées : [JEI](https://api.modrinth.com/v2/project/jei/version?loaders=%5B%22fabric%22%5D&game_versions=%5B%2226.3-pre-2%22%5D),
+[Minimap](https://api.modrinth.com/v2/project/xaeros-minimap/version?loaders=%5B%22fabric%22%5D&game_versions=%5B%2226.3-pre-2%22%5D),
+[World Map](https://api.modrinth.com/v2/project/xaeros-world-map/version?loaders=%5B%22fabric%22%5D&game_versions=%5B%2226.3-pre-2%22%5D),
+[Sodium](https://api.modrinth.com/v2/project/sodium/version?loaders=%5B%22fabric%22%5D&game_versions=%5B%2226.3-pre-2%22%5D),
+[Iris](https://api.modrinth.com/v2/project/iris/version?loaders=%5B%22fabric%22%5D&game_versions=%5B%2226.3-pre-2%22%5D).
+
+Le premier lot natif peut avancer indépendamment. L'intégration de chaque mod
+demande ensuite un fichier compatible, ses dépendances, puis des essais de
+ses accès et de sa coexistence avec Sanctuary. Un changement de version
+Minecraft constitue un lot distinct avec vérification du monde existant.
+
+## Premier résultat jouable retenu pour beta.003
+
+**Parcours : créer sa fiche → arriver → ouvrir le Blocodex → acheter une
+première amélioration et Noms → retrouver cette progression après mort et reconnexion.**
+
+1. Construire le HELLO_WORLD et la mémoire d'habitant : fiche, couleur stable,
+ reprise après interruption et message d'accueil unique.
+2. Donner une première forme commune au Blocodex et au menu Échap, en
+ conservant les connaissances de blocs déjà disponibles. La faculté de
+ s'asseoir fait partie des aptitudes acquises dès l'arrivée.
+3. Appliquer le départ 3/3/3, présenter les améliorations et permettre un
+ premier achat serveur. Le premier rang de vie à **1 niveau** est le réglage
+ de prototype proposé depuis l'ancienne courbe, à tester en jeu.
+4. Enregistrer les premiers faits nécessaires : arrivée initiale et achat,
+ consultables dans l'historique. Définir leur contrat avant l'écriture.
+
+Ce parcours éprouve ensemble identité, interface, achat et persistance. Il
+n'exige pas de créer les factions, la boutique, les visiteurs ou le site.
+La demande du 13 septembre inclut dès ce lot **Noms des mobs**, importé de
+26.2 avec listes serveur personnalisées par type. L'overlay de vie des mobs
+reste ultérieur. S'asseoir n'est pas un achat de progression. L'intégration
+des cartes et recettes suit la disponibilité des dépendances.
+
+### Vérification attendue
+
+- Sur un serveur de développement neuf, aucune apparition ni annonce
+ d'arrivée avant validation de la fiche ; interruption et reconnexion sûres.
+- Deux créations concurrentes ne prennent pas la même couleur. Étendre la
+ palette conserve les couleurs existantes.
+- Un nouvel habitant peut s'asseoir sans XP ni achat d'aptitude.
+- Départ 3/3/3 cohérent entre serveur, effets de jeu et affichage. Vérifier
+ aussi alimentation, respiration et récupération de vie avec ces capacités.
+- L'achat retire le coût annoncé une seule fois, refuse un solde insuffisant,
+ applique le rang et le conserve après respawn, reconnexion et redémarrage.
+- Le Blocodex existant reste lisible ; les nouvelles interfaces ont leurs
+ libellés FR/EN et restent utilisables aux différentes échelles de GUI.
+- Historique sans double arrivée ni double achat après reprise.
+
+Le prototype laisse 70 % des points d'XP non dépensés en orbes vanilla,
+récupérables par tous, avec `deathXpFraction` configurable. `keepInventory`
+conserve son comportement. Ce réglage reste à équilibrer en jeu.
+
+L'implémentation est vérifiée dans des mondes de développement neufs.
+L'adoption d'une partie existante suit le contrat additif lié en tête ; aucune
+installation personnelle n'est modifiée par cette livraison.
+
+Pour livrer du code : `./gradlew check build`, puis `./gradlew assemblePack`
+pour la distribution, avec les versions mod/pack incrémentées en `beta.003`.
+Les résultats et le MRpack local sont consignés dans les notes de livraison.
diff --git a/docs/progression-inline-beta013.md b/docs/progression-inline-beta013.md
new file mode 100644
index 0000000..25c0f07
--- /dev/null
+++ b/docs/progression-inline-beta013.md
@@ -0,0 +1,135 @@
+# beta.013 — compétences sur une ligne et prix du prestige
+
+Ticket sur `codex/progression-inline-costs`, demandé le 13 septembre 2026.
+Contrat préalable à l'implémentation.
+
+## Résultat attendu
+
+Chaque compétence occupe une ligne : icône, nom, jauge ASCII, valeur/plafond,
+retrait opérateur et achat. Les colonnes s'adaptent à la largeur disponible ;
+les noms trop longs défilent dans leur cellule et restent complets au survol.
+Les aptitudes gardent leur panneau distinct et leur défilement.
+
+Les sept achats coûtent par défaut **1, 2, 4, 8, 16, 32, 48 niveaux**.
+L'Inventaire utilise les cinq premiers. Le dernier achat passe ainsi de 64 à 48.
+Le New Game+ exige toujours les six compétences au maximum et donne, par défaut,
+un prestige et une charge personnelle. Une charge fonde une faction de deux places ;
+rejoindre sur invitation ne demande pas de prestige. La page Factions explique
+où gagner les charges et mène à la page Cycle. Les règles de faction restent celles
+de beta.011, configurables côté serveur.
+
+Ajouts demandés pendant le ticket : après un prestige, toutes les cases visibles
+de récupération sont grisées, même vides et même dans les deux rangées
+supplémentaires. Les objets restent lisibles et récupérables. Ce repère suit
+l'inventaire personnel, les conteneurs et leur défilement ; aucune règle de
+transfert ne change. La fiche Habitant ajoute au nom le prestige en chiffres
+romains (`Poupoutain I`, `Poupoutain II`), sans changer le pseudo enregistré.
+
+La carte conserve son image lors d'un rafraîchissement réseau et les relevés déjà
+reçus lors d'un déplacement. Le cache en mémoire est borné à 16 384 chunks et
+disparaît à la fermeture, au changement de dimension/mode ou au retrait des droits.
+Les sauvegardes Atlas et le protocole de lecture restent identiques.
+Le repère personnel utilise le sprite natif du locator et la couleur de l'habitant.
+Les marqueurs du locator se superposent à la barre d’XP native : le fond et
+le remplissage d’expérience restent visibles derrière les points. Le serveur renseigne la couleur de l'habitant dans son icône native
+de waypoint à la connexion et à la restauration ; aucun changement de portée,
+de furtivité ou de permissions du locator. Cette propriété native est conservée
+avec les données Minecraft du joueur, sans nouveau format Sanctuary.
+
+La molette agit selon le survol : hotbar → sélection horizontale ; stockage
+débloqué → rotation des rangées ; offres de villageois → défilement natif.
+Le titre d’inventaire et les cases de récupération gardent le défilement des
+rangées masquées dans les grands conteneurs. Tab/Maj+Tab restent disponibles.
+Les rotations utilisent le protocole beta.012 ; une courte file bornée attend
+les confirmations serveur et s’annule à la fermeture du menu. Aucun objet porté
+par le curseur ni glisser-déposer ne déclenche de rotation.
+
+## Migration de configuration
+
+La configuration de progression passe au schéma 3. Lors du chargement par le
+serveur, les schémas 1 et 2 sont repris : seule la courbe exactement égale à
+`[1,2,4,8,16,32,64]` devient `[1,2,4,8,16,32,48]`. Une courbe personnalisée
+reste intacte, ainsi que les autres réglages. Le schéma 1 reçoit toujours le
+prix Atlas par défaut de 4, selon le contrat beta.005.
+
+La configuration migrée est validée puis enregistrée atomiquement en vérifiant
+que le fichier d'origine n'a pas changé. Un fichier invalide est refusé, jamais
+remplacé par les valeurs par défaut. Le schéma 3 n'est pas réécrit au rechargement ;
+une courbe choisie explicitement dans ce schéma, y compris avec un prix final de 64,
+reste la décision du serveur. Client et serveur utilisent beta.013 ensemble.
+
+Les données des habitants ne changent pas de format. Aucun rang, achat, date,
+objet, aptitude, faction, charge ou prestige n'est retiré. Les achats existants
+gardent leurs prix : le retrait opérateur d'un rang payé 64 rembourse 64 ;
+un nouvel achat de ce rang coûte ensuite 48 avec la nouvelle courbe par défaut.
+Aucune modification du terrain ni de la génération. Export local pour essai.
+
+## Vérifications
+
+Les tests de contrat vérifient la reprise des schémas 1/2, la conservation d'une
+courbe personnalisée, le refus d'un fichier corrompu et l'absence de réécriture
+du schéma 3. Un achat historique à 64 est remboursé à 64, tandis qu'un nouvel
+achat du même rang coûte 48. Les tests de Minage et Construction attendent
+désormais 111 niveaux pour les sept rangs, au lieu de 127.
+
+Le parcours graphique natif passe avec Minecraft **26.3-pre-2**, Fabric Loader
+**0.19.5**, Fabric API **0.160.0+26.3** et Java 25 :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
+ -PsanctuaryInlineClientTests=true -PsanctuaryInventoryClientTests=true
+```
+
+- Colonnes alignées en français et en anglais, GUI 1 et automatique, ainsi qu'en
+ fenêtre de 640 × 480 (GUI logique 320 × 240). Les jauges, valeurs et boutons
+ ont les mêmes abscisses pour les six compétences.
+- Bouton + absent sans XP, dernier achat refusé à 47 et accepté à 48 niveaux ;
+ retrait opérateur puis remboursement. Parcours explicatif Factions → Cycle.
+- Véritable confirmation New Game+ : prestige I sur la fiche, nom enregistré
+ inchangé, objets conservés. Les 45 cases de récupération dessinées sont grisées,
+ y compris les cases vides et supplémentaires ; coffres de trois et six rangées
+ contrôlés aux deux extrémités de leur défilement.
+- Un relevé hors de la zone de capture passive reste dessiné pendant un glisser,
+ son relâchement et 150 ticks de rafraîchissement ; le centre choisi reste fixe.
+ Le retrait des droits opérateur vide bien la vue concernée.
+- La carte emploie le sprite natif. Un waypoint reçu par le protocole Minecraft
+ apparaît à `yXP − 2`, dans sa couleur, avec fond et remplissage XP présents et
+ sans fond de locator supplémentaire. La couleur projetée côté serveur est
+ également contrôlée. Ce test utilise un waypoint de laboratoire, pas une
+ session multijoueur à trente personnes.
+- Dispatch natif de la molette : sélection de case synchronisée au serveur,
+ rotations avant/arrière et plusieurs crans en attente, Tab/Maj+Tab ; vingt
+ offres dans un menu de commerce natif défilent sans modifier la hotbar, et
+ les gestes sur l'inventaire ne font pas défiler les offres.
+
+Journal graphique : `build/progression013-client-delivery.log` ; les 17 captures
+sont conservées dans `build/progression013-evidence/`. Les mondes de laboratoire
+et les classes de test restent dans les dossiers de développement ignorés.
+Les menus de mods communautaires extérieurs ne font pas partie de ce contrôle.
+
+La livraison finale réussit :
+
+```sh
+./gradlew check build assemblePack :sanctuary:runGameTest \
+ -PsanctuaryFocusedTests=progression,map,demeure,mining,building,inventory,cycle,inventoryflow
+```
+
+**50/50 tests natifs serveur passent**, avec les contrôles de contrat du projet.
+Journal : `build/progression013-delivery-verified.log` (`BUILD SUCCESSFUL`,
+4 min 10 s). La première passe avait signalé uniquement les deux anciens totaux
+de 127 niveaux dans les tests de Minage et Construction, corrigés pour 111.
+
+## Artefact d'essai
+
+[Sanctuary-beta.013.mrpack](../build/Sanctuary-beta.013.mrpack), **2 503 734 octets**.
+Export packwiz après le build final ; versions internes `beta.013`, JAR embarqué
+identique au build, Demeure imbriqué identique à son module, absence de classes
+de test, dépendance Fabric API, ressources et clés FR/EN vérifiées.
+Les exports beta.003 à beta.012 gardent leurs empreintes précédentes.
+
+- SHA-256 MRpack : `3e4348ea2817d3fb45e432cfbd5feb01e84d29e3938e3b08370c7dc6eca77398`.
+- SHA-256 JAR Sanctuary : `28e8a486053cc83dd4a8375f805deaf8922c2282e6c995e53ba7c4e6599f62ce`.
+- Reçu : `build/progression013-artifact.json`.
+
+Cet export local ne publie pas le canal packwiz et ne synchronise pas
+l'installation Prism personnelle.
diff --git a/docs/progression-textures-beta015.md b/docs/progression-textures-beta015.md
new file mode 100644
index 0000000..a8471e1
--- /dev/null
+++ b/docs/progression-textures-beta015.md
@@ -0,0 +1,50 @@
+# beta.015 — icônes de progression communes
+
+Ticket du 13 septembre 2026, branche `codex/progression-textures-beta015`.
+
+Les six nouveaux PNG du créateur remplacent les icônes du Blocodex, sans
+retouche ni redimensionnement : format 9 × 9 et transparence conservés.
+Les identifiants de ressources utilisés par le mod restent identiques.
+
+Précision du créateur : les bulles de respiration du HUD gardent les textures
+vanilla. La barre de faim garde aussi ses textures actuelles en attendant les
+fichiers dédiés qu'il fournira. Les six PNG de ce ticket sont les icônes des
+compétences ; ils ne remplacent pas les sprites de ces barres.
+
+| Fichier fourni | Ressource du mod et du resource pack | Compétence |
+| --- | --- | --- |
+| `progression_heart.png` | `heart.png` | Vie / Health |
+| `progression_food.png` | `hunger.png` | Faim / Hunger |
+| `progression_mining.png` | `mining.png` | Minage / Mining |
+| `progression_building.png` | `building.png` | Construction / Building |
+| `progression_breath.png` | `breathing.png` | Souffle / Breath |
+| `progression_inventory.png` | `inventory.png` | Inventaire / Inventory |
+
+Les fichiers embarqués sont dans
+`mods/sanctuary/src/main/resources/assets/sanctuary/textures/gui/progression/`.
+Leurs copies identiques pour le resource pack sont dans
+`ressources-pack/sanctuary/assets/sanctuary/textures/gui/progression/`.
+Les futures modifications doivent garder ces deux jeux de six fichiers identiques.
+
+Le resource pack complet reste en préparation : ce ticket fournit ses icônes
+avec les mêmes chemins `assets/sanctuary/...`. Les anciens exports et archives
+conservent les visuels de leur version. Aucun changement de Java, de règle de jeu,
+de sauvegarde, de génération ou de dépendance.
+
+## Vérifications effectuées
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=progression` réussit
+en 4 min 10 s ; les six tests natifs de progression passent. Journal :
+`build/textures015-check-build.log`.
+
+Les six originaux, les fichiers du mod, les copies du resource pack et le JAR
+sont identiques. Dimensions, CRC des blocs PNG, pixels compressés et transparence
+sont validés. L'export conserve exactement le bytecode Java de beta.014 et ne
+contient aucun remplacement des sprites de faim ou de respiration du HUD.
+Les MRpacks beta.003–014 conservent leurs empreintes. Aucun nouveau test
+fonctionnel ajouté et aucun déploiement dans une instance personnelle.
+
+Export local : [Sanctuary-beta.015.mrpack](../build/Sanctuary-beta.015.mrpack).
+Vérificateur : `build/verify-beta015-pack.py` ; reçu : `build/textures015-artifact.json`.
+
+Taille : 2528670 octets. SHA-256 : `b08486731caef8735c5c4f9dad0aac2ff26515fcfcf7482d4efdcb575f8996b0`.
diff --git a/docs/progressive-fog-beta053.md b/docs/progressive-fog-beta053.md
new file mode 100644
index 0000000..f9d3e7c
--- /dev/null
+++ b/docs/progressive-fog-beta053.md
@@ -0,0 +1,61 @@
+# beta.053 — brume progressive, saturation et accueil
+
+Branche `codex/progressive-fog-beta053`. Contrat : avec Sanctuary · Vanilla Light
+activé, la brume atmosphérique commence à une distance nulle du point de vue et
+augmente progressivement jusqu'à la limite visible. En première personne, son
+origine est le regard du joueur ; en troisième personne, elle reste celle de la
+caméra native. Il s'agit de la progression demandée, sans ajout de ray tracing.
+
+L'effet utilise les distances de brume du moteur, dans les shaders qui dessinent
+déjà terrain, entités et transparences. Pas de passe supplémentaire, de texture
+de profondeur à relire, d'historique ou de calcul dépendant des chunks. La teinte,
+la portée maximale, la météo et les limites de visibilité restent natives.
+Eau, lave, neige poudreuse, cécité et obscurité conservent leur brume spéciale.
+Les captures panoramiques techniques restent brutes. Désactiver Vanilla Light
+restaure la transition native ; un moteur externe suspend toujours l'effet.
+
+Aucun changement de sauvegarde, de génération, de serveur ou d'asset PNG.
+Les libellés de l'option sont complétés en français et en anglais.
+
+Correction Hello World ajoutée à la demande : les trois œufs sont désormais
+alignés à gauche avec les libellés, la palette et le champ du nom. Leur
+espacement, leurs infobulles et leur fonctionnement ne changent pas.
+
+## Saturation
+
+Options → Shaders ajoute un curseur natif de 0 à 200 %. À 100 %, le rendu
+Sanctuary habituel est conservé ; 0 % donne un monde en noir et blanc et 200 %
+renforce les couleurs. Le HUD et les menus conservent leurs couleurs. Le réglage
+agit immédiatement quand Vanilla Light est activé et reste enregistré localement
+dans `config/sanctuary-shaders.json` (`saturation`, entier, défaut 100 si absent).
+Les valeurs sont bornées ; aucun changement de données de monde.
+
+La saturation utilise la passe couleur existante et un petit buffer uniforme
+réutilisé. Le glissement du curseur ne recharge aucun shader ni aucune ressource.
+Les buffers sont libérés avec la passe lors de son remplacement ou de l'arrêt.
+
+## Validation
+
+Validation terminée sur Minecraft 26.3-pre-2 avec Java 25 :
+
+- `./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest -PsanctuaryQuickTests=true` : succès. Les GameTests serveur sont exclus de cette livraison exclusivement cliente ; aucune EULA de serveur dédié acceptée.
+- Client natif `Fog053ClientChecks` : brume active/inactive, progression proche/lointaine, première/troisième personne, FOV 30/110, météo, fluides, cécité et obscurité, rechargement des ressources et capture panoramique brute avec restauration.
+- Saturation 0/100/200 % vérifiée sur de vraies captures GPU ; curseur natif FR/EN, bornes, persistance et absence de rechargement du shader lors des changements vérifiés.
+- Hello World FR/EN en deux tailles : alignement des œufs avec le nom et la palette, conservation du brouillon, admission réelle avec nom et reconnexion sauvegardée.
+
+Les captures sont dans `build/fog053-evidence/`. Sur une même région du monde,
+l'écart moyen entre les canaux extrêmes vaut 0,00 / 50,42 / 101,16 pour les
+trois saturations ; le pixel de cœur vérifié reste `FF1313` dans les trois cas.
+Ces vérifications ne constituent pas une mesure de performance ni une validation
+avec un moteur de shaders externe.
+
+Les journaux `build/fog053-delivery.log`, `build/fog053-client.log` et
+`build/fog053-hello-client.log` contiennent les succès. Le reçu
+`build/fog053-artifact.json` confirme les 1 537 classes et ressources compilées,
+les deux packs, ainsi que la conservation des archives beta.052, des textures,
+de JEI et des données de génération.
+
+[Pack normal](../build/Sanctuary-beta.053.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.053.mrpack).
+Archives locales uniquement ; aucun déploiement dans une instance personnelle
+ni changement du canal packwiz public.
diff --git a/docs/realtime-beta037.md b/docs/realtime-beta037.md
new file mode 100644
index 0000000..7a6dc7f
--- /dev/null
+++ b/docs/realtime-beta037.md
@@ -0,0 +1,181 @@
+# TIME-037 — temps réel et suivi commun
+
+Branche `codex/realtime-tracking-beta037`, base beta.036.
+Minecraft 26.3-pre-2 et Java 25. Contrat écrit avant l’implémentation.
+
+## Demande retenue
+
+Le suivi des futurs objectifs de quête utilise le même système que les
+notifications et advancements : même position, mêmes jauges, mêmes règles
+visuelles et mêmes options. Aucun second HUD en haut à gauche. Les quêtes,
+panneaux, rubis/saphirs, récompenses et visiteurs ne sont pas créés ici.
+
+Le temps réel est activé par défaut dans les mondes concernés. En solo, le fuseau de la machine
+est automatique. Le créateur confirme une **ville de référence du fuseau**, sans
+IP ni géolocalisation, pour estimer le soleil saisonnier. En multijoueur, le
+serveur choisit son fuseau et sa région de référence dans sa configuration.
+
+## Contrat de migration et de retour
+
+L’activation concerne les mondes Sanctuary et le profil Sanctuary Test. Un monde
+Minecraft ordinaire garde son horloge native. Aucun monde personnel n’est ouvert
+par le développement ; essais uniquement dans les dossiers ignorés.
+
+Au premier lancement avec le nouveau mode, un fichier distinct
+`data/sanctuary-realtime.json` consigne un ancrage de calendrier et les paramètres
+natifs de vitesse/pause avant activation. Il est écrit atomiquement **avant**
+d’appliquer le nouveau temps. Le jour Minecraft courant est conservé comme base,
+puis avance d’un jour par jour réel ; l’heure se cale immédiatement sur le soleil
+réel estimé. Pas d’âge artificiel de plusieurs dizaines de milliers de jours.
+Un changement de région ou de mode réancre cette base sans simuler les jours manqués.
+
+L’horloge de simulation `gameTime`, les ticks planifiés, cultures, fours, fluides,
+croissance des entités, inventaires et progression ne sont ni sautés ni ralentis.
+Aucun rattrapage de simulation hors ligne. Les comportements naturellement liés
+au jour/nuit, dont les routines des villageois, suivent en revanche la journée
+solaire plus longue. Génération, chunks, structures, graines et expansions restent
+inchangés. Aucun schéma de sauvegarde existant n’est remplacé.
+
+Le fichier d’horloges natif garde une vitesse/pause native lors de l’enregistrement,
+même pendant le temps réel. Sans le mod, le monde reprend donc son rythme natif
+à partir de sa dernière heure, sans conserver une vitesse solaire très lente.
+Le mode vanilla rétablit cette même vitesse/pause, à l’heure courante ; il ne
+restaure pas un ancien `gameTime`. Les preuves d’ancrage restent dans le fichier
+séparé. Un fichier invalide ou modifié extérieurement est refusé sans écrasement.
+
+## Soleil et limites visuelles
+
+L’heure et la date viennent de l’horloge du serveur intégré ou dédié. Les jours
+allongent/raccourcissent selon la latitude de référence et la saison. Le fuseau
+Java gère les changements d’heure civile ; le soleil est calculé en instants UTC
+pour rester continu lors du passage été/hiver. Les références publiques IANA sont
+embarquées ; aucun accès réseau en jeu. Un fuseau sans ville de référence utilise
+un repère équatorial à son méridien horaire, annoncé comme approximation.
+
+Les levers, midi solaire et couchers cadrent les phases du ciel Minecraft. Les
+nuages et l’orbite restent natifs : ce n’est pas un moteur astronomique complet.
+Les journées polaires conservent un ciel de jour et les nuits polaires un ciel
+de nuit, avec soleil stationnaire dans cette première version.
+
+Le sommeil en lit conserve sa pose et son point de réapparition mais ne saute
+plus la nuit et ne change pas la météo. Les apparitions naturelles de phantoms
+liées à l’insomnie sont désactivées en temps réel, conformément à la vision.
+Les phantoms existants, œufs et familiers ne sont pas supprimés ; les autres
+fantômes prévus dans la vision restent hors de ce ticket.
+
+Les commandes administrateur permettent de consulter le mode et les heures,
+de recharger la configuration, et de revenir au temps Minecraft. Les commandes
+natives qui changent l’horloge solaire doivent signaler que le temps réel la
+contrôle ; les requêtes de lecture et les autres horloges restent disponibles.
+
+## Sources techniques
+
+Formules solaires approximatives de [NOAA](https://gml.noaa.gov/grad/solcalc/solareqns.PDF),
+références géographiques publiques de [IANA tzdb](https://data.iana.org/time-zones/tzdb/).
+Les API d’horloges, paquets, timelines et sommeil sont vérifiées dans les sources
+exactes de Minecraft 26.3-pre-2 fournies par Loom.
+
+## Utilisation
+
+En solo, aucune saisie : le fuseau de la JVM suit celui de la machine au
+lancement. Exemple Europe/Paris : référence publique Paris. Aucun paramètre
+client ne permet de modifier l’heure d’un serveur dédié.
+
+Sur serveur dédié, modifier `config/sanctuary/realtime.json` :
+
+```json
+{
+ "enabled": true,
+ "serverTimeZone": "Europe/Paris",
+ "solarRegion": "auto"
+}
+```
+
+`auto` prend la ville associée au fuseau. `solarRegion` accepte aussi un autre
+identifiant IANA de référence, par exemple `Europe/London`. Ne pas entrer de
+coordonnées privées. Par défaut dédié : UTC, repère équatorial approximatif.
+La liste embarquée est `data/sanctuary/realtime/zone-references.json` dans le JAR.
+
+- `/sanctuary time` : mode, date/heure civile, lever et coucher estimés.
+- `/sanctuary time reload` : recharge le fichier de configuration (opérateur).
+- `/sanctuary time vanilla` : reprend le rythme Minecraft dans ce monde (opérateur).
+- `/sanctuary time realtime` : reprend l’heure réelle, si la configuration
+ l’autorise (opérateur).
+
+L’option globale `enabled: false` arrête le pilotage solaire. Un monde créé
+pendant cette désactivation nécessite `/sanctuary time realtime` après
+réactivation. Une configuration invalide au lancement conserve l’horloge native ;
+un rechargement invalide laisse la dernière configuration valide en place et
+signale l’erreur. Les commandes natives `/time` de modification exigent le mode
+vanilla ; les lectures restent possibles. Le profil plat utilise aussi le temps
+réel, avec ce même retour explicite au rythme Minecraft pour les essais.
+
+## Contrat du suivi commun
+
+`ObjectiveTracker` centralise les instantanés par couple source/identifiant.
+Les advancements natifs sont déjà branchés sur ce moteur. Un futur adaptateur de
+quêtes transmettra ses instantanés validés par le serveur à
+`NotificationClient.objectives("quest", ...)`, sur le thread client, puis pourra
+utiliser `toggleObjective` pour le choix manuel. Cette API est uniquement une
+présentation : aucune validation de quête, récompense ou découverte côté client.
+
+Premier instantané silencieux, message seulement quand la progression augmente,
+barre temporaire expirante, épingle retirée à la réussite, trois épingles communes
+maximum. Mort/respawn nettoient le fil et les instantanés temporaires ; un nouvel
+instantané remet les épingles à jour. Déconnexion vide le contexte du serveur.
+Les préférences beta.036 et leurs identifiants d’advancements restent lisibles,
+sans migration forcée ; les autres sources utilisent `source|identifiant`.
+
+Le raccord au rendu traite aussi les retours anticipés de Minecraft lorsque F3
+n’a aucune ligne ; le suivi apparaît donc pendant le jeu normal.
+
+Même position, options, textures natives et évitement F3/boss/hotbar pour toutes
+les sources. Aucun panneau de quête ni nouvelle quête n’est ajouté dans le jeu.
+
+## Validation
+
+Livré localement en **beta.037**. `./gradlew check build assemblePack assembleTestPack`
+réussit avec 29 tests serveur ciblant temps réel, notifications, progression,
+mouvements, collections et recettes. Les 7 765 assertions du test solaire et du
+suivi couvrent les fuseaux Java disponibles sur les douze mois de 2026,
+les solstices nord/sud, jours polaires, année bissextile, DST, ligne de date,
+continuité à minuit, configurations, contrat atomique et objectifs mixtes.
+
+Le test serveur vérifie les paramètres natifs sauvegardés, le paquet de
+synchronisation, le temps de simulation inchangé, les permissions, le refus des
+commandes `/time` en temps réel, l’End indépendant, les phantoms naturels et
+les retours/rechargements de configuration sans perdre les modifications
+administrateur effectuées en mode vanilla.
+
+Deux parcours client natifs réussissent sur des mondes plats de développement :
+- lever, midi et coucher aux angles natifs attendus, synchronisation réelle du
+ paquet d’horloge, entrée dans un lit et sommeil profond sans saut de nuit ;
+- source de quête synthétique **uniquement dans les tests** : rendu sans F3,
+ expiration, épingle et retrait à la réussite ;
+- régression du suivi par clic/re-clic/glissement dans Advancements, conservation
+ après respawn, 36 dispositions avec F3 et son échelle indépendante, boss,
+ hotbar, écrans FR/EN, descente réelle avec un perroquet porté.
+
+Les captures ont été inspectées dans `build/realtime037-evidence/`. Logs :
+`build/realtime037-check-final.log`, `build/realtime037-client-release.log`,
+`build/realtime037-regression-client.log`. Reçu d’artefact :
+`build/realtime037-artifact.json`.
+
+Les 1 353 classes du mod et les trois classes de l’extension de test correspondent
+au build. Génération, ressources préexistantes hors langues/notices, code Demeure
+et JAR JEI sont inchangés. Les deux beta.036 gardent leurs empreintes initiales.
+Ni canal public, ni installation Prism personnelle, ni monde personnel modifié.
+
+La validation ne simule pas plusieurs jours réels d’hébergement continu : les
+frontières calendaires sont testées avec une horloge déterministe. Le soleil
+reste une approximation liée à une ville publique ; aux pôles il reste fixe
+le jour ou la nuit. Les quêtes et leurs récompenses ne sont pas livrées ici.
+
+
+
+## Artefacts vérifiés
+
+- [Sanctuary-beta.037.mrpack](../build/Sanctuary-beta.037.mrpack) — 5,014,547 octets.
+ SHA-256 : `e91d29dddf5ee26098a4de6fff85a5cfa5ce920304754c12aecd06cf8b6a9d2d`.
+- [Sanctuary-Test-beta.037.mrpack](../build/Sanctuary-Test-beta.037.mrpack) — 5,033,482 octets.
+ SHA-256 : `9b4e3c60e507e2ab9e3bf666615d833db8b0e4375454dc727aff7f5155c4b043`.
diff --git a/docs/recipe-storage-beta040.md b/docs/recipe-storage-beta040.md
new file mode 100644
index 0000000..c726314
--- /dev/null
+++ b/docs/recipe-storage-beta040.md
@@ -0,0 +1,42 @@
+# beta.040 — contrat de reprise du catalogue volumineux
+
+Le test de tous les advancements produit un catalogue de recettes d'environ
+85 Ko. Jusqu'à beta.039, l'attachement `sanctuary:recipe_knowledge` était une
+unique chaîne NBT, limitée à 65 535 octets UTF modifié. La sauvegarde native
+remplaçait une chaîne trop longue par une chaîne vide après journalisation de
+l'erreur. L'ouverture suivante pouvait donc perdre ce catalogue.
+
+## Contrat de migration
+
+- L'identifiant d'attachement reste `sanctuary:recipe_knowledge`, le contenu
+ JSON reste en schéma 1, les paquets réseau restent identiques.
+- Les anciennes chaînes NBT sont toujours lues. Une chaîne qui tient dans la
+ limite native reste écrite sous la même forme, sans migration anticipée.
+- Au-delà de cette limite, le même texte est écrit en liste NBT de chaînes de
+ 16 000 caractères au maximum, puis concaténé exactement à la lecture. Chaque
+ élément reste sous la limite UTF même avec des caractères non ASCII.
+- Le catalogue conserve sa limite existante d'un million de caractères. Les
+ listes mal formées, trop longues ou trop nombreuses sont refusées.
+- La conversion se produit seulement à la prochaine sauvegarde normale d'un
+ joueur chargé ; aucun fichier de joueur hors ligne n'est parcouru ou réécrit.
+ Aucun monde personnel n'est ouvert par l'outil de développement.
+- Le retour vers une version antérieure ne sait pas lire une liste volumineuse.
+ Conserver une sauvegarde antérieure à une rétrogradation. Les catalogues qui
+ restent sous 65 535 octets conservent leur format historique.
+- Un catalogue déjà remplacé par une chaîne vide par une ancienne sauvegarde
+ ne peut pas être restauré à partir de cette chaîne. Le correctif prévient les
+ nouvelles pertes ; il n'invente pas les découvertes disparues.
+
+La copie à la mort et la conservation au prestige restent celles de
+l'attachement existant. Aucun coût, déblocage ou filtre de recettes n'est changé.
+
+## Vérification
+
+Test serveur réussi : écriture et relecture NBT comprimé de petits catalogues historiques, d'un
+catalogue complet supérieur à 64 Ko et d'une chaîne Unicode ; refus des
+fragments invalides. Le parcours client ferme puis rouvre un monde de test
+ayant tous les advancements et compare son catalogue entier, en plus du ciel.
+
+Les contrôles passent, y compris la comparaison du catalogue entier après
+fermeture et réouverture du monde de test. Le journal client final ne contient
+plus l’erreur NBT. Reçu : `build/operator040-artifact.json`.
diff --git a/docs/recipes-jei-beta024.md b/docs/recipes-jei-beta024.md
new file mode 100644
index 0000000..d4b4b1a
--- /dev/null
+++ b/docs/recipes-jei-beta024.md
@@ -0,0 +1,108 @@
+# Recettes JEI — collections et Blocodex
+
+Ticket beta.024, suivant la finalisation cosmétique beta.023. État : livré et vérifié localement.
+
+## Décisions du créateur
+
+Reprendre JEI depuis ses sources Minecraft 26.2, puis adapter la version à
+Minecraft 26.3-pre-2 / Fabric API 0.160.0+26.3. La fabrication manuelle reste
+libre ; l’accès au catalogue et la révélation d’une recette sont distincts.
+Les avancées Minecraft et les découvertes du Blocodex débloquent des collections
+de recettes. Découvrir une planche révèle la **famille de base du bois** :
+bâtons, établi, coffre, portes et constructions simples. Cela ne révèle pas
+automatiquement piston, bouclier ou toute recette contenant une planche.
+
+Les recettes peuvent être connues avant l’achat de l’outil de consultation,
+comme l’exploration conservée avant le déblocage de la carte. La vue opérateur
+doit être explicite et permettre de vérifier également la vue d’un habitant.
+Ni consultation ni découverte de recette ne doivent attribuer de faux compteurs
+de blocs possédés, placés ou minés. Le livre de recettes natif reste masqué.
+
+## Source examinée
+
+Dépôt officiel : https://github.com/mezz/JustEnoughItems, branche `26.2`,
+commit `aae2dfcfb82e6b5bce787ba72bb2eda7339b274f`, version source `30.32.0`.
+Licence MIT, notice mezz à conserver. Copie de travail ignorée :
+`build/jei-26.2-upstream`. Le dépôt voisin `26.2` reste en lecture seule.
+L’intégration historique utilisait JEI `30.29.0.201` et un verrou global à
+30 niveaux ; ses anciens prix ne constituent pas le nouveau contrat de bêta.
+
+Le portage se reconstruit depuis les sources fixées et leurs patches. Aucun JAR 26.2 n’est déclaré
+compatible par simple changement du manifeste. Aucune publication ni mise à
+jour d’instance personnelle n’est autorisée par ce ticket.
+
+## Contrat de données beta.024
+
+Une nouvelle pièce jointe Fabric `sanctuary:recipe_knowledge`, JSON schéma 1,
+contient le reçu d’achat du catalogue (4 niveaux), les identifiants d’objets
+réellement découverts et les identifiants de recettes apprises. Elle est copiée
+à la mort et n’est pas remise à zéro par le prestige. Les anciens formats de
+progression, inventaire et Blocodex ne changent pas. Une valeur absente initialise
+un carnet vide ; une valeur invalide est conservée et bloque l’achat/la consultation.
+
+À la connexion, les blocs déjà connus, les statistiques natives d’objets et
+le livre de recettes serveur alimentent le carnet. Ensuite, les découvertes et
+les récompenses natives d’advancements l’enrichissent. Aucune possession de bloc
+n’est créée par la lecture d’une recette. Les recettes spéciales sans découverte
+native restent réservées à la vue opérateur tant qu’une collection ne les enseigne pas.
+
+Le serveur envoie le carnet personnel ; le client filtre JEI. Le catalogue coûte
+4 niveaux, indépendamment du nombre de recettes déjà apprises. La vue opérateur
+est une bascule explicite, autorisée côté serveur et révoquée à la perte des droits.
+Ce filtre est une règle d’interface : les fichiers de recettes publics du jeu ne
+sont pas des secrets. Aucune restriction de fabrication manuelle n’est ajoutée.
+
+La distribution inclut un fork MIT de JEI compilé depuis le commit fixé, avec
+ses patches conservés dans le dépôt et ses sources téléchargées dans `build/`.
+Les durées de combustible et rendements du composteur devenus contextuels en
+26.3 sont décrits comme dépendants du serveur, sans inventer de valeurs fixes.
+
+## Parcours livré
+
+**B → Découverte → Recettes**, ou **Progression → Aptitudes → Catalogue de recettes**.
+Le catalogue coûte 4 niveaux. Après achat, R consulte les recettes et U leurs
+utilisations depuis une case survolée ; le bouton + remplit la grille depuis les
+rangées débloquées, jusqu’à six. Les cases de récupération après prestige sont exclues.
+
+Les récompenses de recettes des advancements Minecraft sont reprises directement.
+Deux collections additionnelles sont définies en données : les bases du bois à la
+première découverte d’une planche, et l’alchimie après `minecraft:nether/brew_potion`.
+Leurs fichiers sont dans `data/sanctuary/sanctuary_recipe_collections/` et peuvent être
+remplacés/complétés par un datapack (`items`, `item_tags`, `advancements`, `recipes`,
+`recipe_types`). Chaque déclencheur est une alternative ; les recettes effectivement
+présentes sur le serveur sont retenues. `/reload` recharge les collections ; les
+recettes déjà apprises restent enregistrées.
+
+Les fluides du catalogue apparaissent lorsque leur seau est découvert. Les plans
+connus montrent ingrédients, résultat et poste de travail sans modifier les compteurs
+Blocodex. Les recettes spéciales sans identifiant/déblocage natif demandent encore
+leurs futures collections ; elles sont consultables en vue opérateur.
+La vue opérateur est temporaire et revient à la vue habitant au changement de serveur.
+La révocation des droits est détectée à la prochaine synchronisation (au plus une
+seconde de ticks serveur). Aucun objet n’est donné par cette bascule.
+
+Les règles et limites du port JEI sont détaillées dans
+[PROVENANCE.md](../mods/jei/PROVENANCE.md). La génération du terrain conserve le
+correctif beta.022 et ses valeurs ; ce ticket ne régénère aucun chunk.
+
+## Vérifications et artefact
+
+`./gradlew check build assemblePack :sanctuary:runClientGameTest` avec les suites
+serveur progression/inventaire/cosmétiques/familiers/construction/minage/tutoriel,
+recettes et startup, et `sanctuaryRecipesClientTests=true` : **réussi**.
+123 tests serveur, 588 288 comparaisons de densité à l’identique, parcours client
+avec catalogue verrouillé, raccourci U réel, fabrication affichée sans établi
+connu, fluides inconnus masqués, vue opérateur puis révocation, transfert réseau
+depuis la sixième rangée, véritable New Game+, mort/respawn et redémarrage.
+Le client vérifie aussi l’achat depuis un Blocodex ouvert par le menu Pause.
+Les captures du parcours ont été inspectées en GUI 2 et GUI 4.
+
+Les collections sont résolues une fois par rechargement de données ; les relevés
+sont répartis entre les ticks des habitants. Un carnet inchangé n’est ni sérialisé
+ni renvoyé périodiquement. Les données serveur restent la référence.
+
+MRpack local : `build/Sanctuary-beta.024.mrpack`.
+SHA-256 : `fc7d5b9b393a3c1d252e6a98a9b9f26b64e64024a06c876dc1943e4f42dce675`.
+Reçu détaillé : `build/jei024-artifact.json` ; journal de validation :
+`build/jei024-final-check.log`. Les exports beta.020, beta.022 et beta.023 sont
+inchangés. Aucune installation personnelle ni sauvegarde existante n’a été modifiée.
diff --git a/docs/shaders-beta040.md b/docs/shaders-beta040.md
new file mode 100644
index 0000000..2d91000
--- /dev/null
+++ b/docs/shaders-beta040.md
@@ -0,0 +1,104 @@
+# beta.040 — ciel compatible avec le rendu natif et Vanilla Light
+
+Demande du 14 septembre 2026, branche `codex/operator-inhabitant-beta040`.
+Ce ticket complète [le ciel partagé](operator-inhabitant-stars-beta040.md).
+
+La [mise à jour beta.053](progressive-fog-beta053.md) étend Vanilla Light avec
+une brume progressive dès le point de vue. Le reste de cette page décrit le
+socle graphique beta.040.
+
+## Disponible dans cette livraison
+
+**Options → Shaders → Sanctuary · Vanilla Light** active ou désactive un effet
+graphique local, activé par défaut dans les mondes Sanctuary. Le shader donne
+un peu plus de contraste et de saturation, des ombres légèrement froides et des
+lumières légèrement chaudes. Le HUD, les menus et les infobulles sont dessinés
+après cet effet et gardent leurs couleurs. Noir et blanc purs sont conservés.
+
+Le moteur de post-traitement natif de **Minecraft 26.3-pre-2** charge
+`sanctuary:vanilla_light`. Il exécute une passe couleur avec un seul échantillon
+par pixel, puis une copie vers la cible principale. Pas de calcul d'ombres,
+reflets, flou, historique temporel ou lecture de pixels par le processeur.
+Le coût dépend tout de même de la résolution et du GPU ; aucune mesure de gain
+de FPS n'est revendiquée. Désactiver l'effet retire ces deux passes.
+
+Les captures panoramiques techniques conservent leur rendu brut avant
+post-traitement, pour rester associées aux exports de profondeur et de brume.
+
+La préférence reste dans `config/sanctuary-shaders.json` (`enabled`, booléen).
+Elle ne modifie aucune sauvegarde ni lumière de gameplay. Les effets natifs de
+créature observée en spectateur sont conservés et passent après Vanilla Light.
+La présence d'Iris, Oculus ou Canvas suspend cet effet natif pour éviter de
+superposer deux traitements ; la préférence n'est pas effacée.
+
+## Les étoiles restent une mémoire du serveur
+
+Sanctuary fournit le maillage et le nombre d'indices à `SkyRenderer.renderStars`.
+Le corps natif de la méthode reste exécuté : même pipeline `RenderPipelines.STARS`,
+mêmes uniformes, étape de ciel et appels de dessin. Un registre vide produit
+zéro indice dessiné. Le buffer d'étoiles est recalculé seulement lors d'un
+changement du registre ou d'une libération des ressources graphiques.
+
+Cette intégration remplace l'annulation de la méthode et le dessin séparé.
+Les moteurs qui interceptent la passe native peuvent ainsi traiter la géométrie
+Sanctuary. Vanilla Light ne génère aucune étoile de son côté. Changer de rendu
+ne change ni les événements, ni leurs positions, ni le cycle de sept jours.
+
+## État vérifié des moteurs externes
+
+Vérification le **14 septembre 2026**, sur les versions publiées Modrinth et
+les dépôts officiels : aucun artefact Fabric Iris, Sodium ou Canvas ne déclare
+`26.3-pre-2` comme version compatible. Iris est publié pour 26.2 ; le nouveau
+Sodium 26.3 vise les release candidates et précise qu'Iris n'est pas encore
+compatible. Aucun de ces JAR n'est ajouté au pack et Minecraft reste épinglé.
+
+- [Versions Iris](https://modrinth.com/mod/iris/versions),
+ [sources et licence LGPL-3.0](https://github.com/IrisShaders/Iris).
+- [Sodium 26.3 et état d'Iris](https://github.com/CaffeineMC/sodium/releases/tag/mc26.3r1-0.9.2-beta.2).
+- [Versions Canvas](https://modrinth.com/mod/canvas/versions).
+
+**La compatibilité avec des packs Iris n'est pas validée.** Le changement de
+point d'intégration prépare cette vérification ; il ne remplace pas un essai
+avec une version exacte du moteur. Vanilla Light est pour l'instant un shader
+natif intégré, pas un chargeur de packs OptiFine/Iris.
+
+## Contrat pour le prochain pack de shaders
+
+Iris expose la géométrie native dans `gbuffers_skybasic` et reconnaît la phase
+`MC_RENDER_STAGE_STARS`. Le futur pack Sanctuary devra conserver `stars=true`,
+dessiner cette géométrie et désactiver ses étoiles procédurales éventuelles.
+Un pack qui supprime le ciel natif ou ajoute son propre semis d'étoiles exige
+une adaptation spécifique. Les réglages solaires du shader ne doivent pas
+remplacer la rotation hebdomadaire fournie par Sanctuary.
+
+Sources : [option stars](https://shaders.properties/current/reference/shadersproperties/features/),
+[étapes de rendu](https://github.com/IrisShaders/docs/blob/main/src/content/docs/current/Reference/Macros/Render_Stages.mdx),
+[association des pipelines Iris](https://github.com/IrisShaders/Iris/blob/26.1/common/src/main/java/net/irisshaders/iris/pipeline/IrisPipelines.java).
+
+Lorsque le moteur exact sera disponible : épingler les dépendances, tester ciel
+vide et ciel peuplé avec le pack, comparer les positions, changer de pack puis
+le désactiver, recharger les ressources, visiter les autres dimensions et
+vérifier les occultations par le terrain. Les ombres du shader ne doivent pas
+dessiner les étoiles dans leur passe d'ombres.
+
+## Ressources modifiables
+
+Les deux sources sont embarquées dans le mod et peuvent être remplacées par un
+resource pack avec les mêmes chemins :
+
+- `assets/sanctuary/shaders/post/vanilla_light.fsh` : traitement des couleurs ;
+- `assets/sanctuary/post_effect/vanilla_light.json` : chaîne de rendu native.
+
+Leurs identifiants restent stables. Aucun sprite ou PNG existant n'est modifié.
+Le futur resource pack commun peut reprendre ces sources ; aucune archive de
+shader externe non testée n'est présentée comme installable dans cette livraison.
+
+## Validation
+
+Le parcours client vérifie l'option native, l'application GPU puis le retrait
+de l'effet, le rechargement des ressources et le nombre réel d'indices dessiné
+par la passe native avec un ciel peuplé puis vide. Ce parcours passe sur le
+client natif : témoin `SHADER040_CLIENT_PASS` dans
+`build/operator040-client-final.log`. Captures dans
+`build/operator040-evidence/final/`, reçu dans `build/operator040-artifact.json`.
+Les contrôles ne revendiquent pas un essai avec Iris ou un autre moteur externe.
diff --git a/docs/skill-xp-bars-beta025.md b/docs/skill-xp-bars-beta025.md
new file mode 100644
index 0000000..6589820
--- /dev/null
+++ b/docs/skill-xp-bars-beta025.md
@@ -0,0 +1,67 @@
+# beta.025 — barres d’expérience des compétences
+
+Ticket sur `codex/skill-xp-bars-beta025`.
+
+## Affichage
+
+Dans **Blocodex → Progression**, les six compétences gardent chacune leur ligne :
+icône, nom, grande barre d’XP, retrait opérateur et achat. La barre utilise les
+sprites Minecraft `hud/experience_bar_background` et `hud/experience_bar_progress` :
+le resource pack peut donc modifier ensemble ce rendu et celui du HUD.
+
+Le prix du prochain achat en **niveaux** apparaît au centre, avec le nombre vert
+et le contour noir du compteur d’expérience natif. Il reste visible sans XP ;
+le bouton **+** apparaît lorsque l’achat est possible. Une barre complète n’affiche
+plus de prix ni de texte « Max ». Le bouton **−** opérateur rembourse toujours
+le prix effectivement payé pour le dernier rang.
+
+Le remplissage représente les paliers de compétence, indépendamment de l’XP
+disponible. Le palier initial occupe une part : `(rang + 1) / (plafond + 1)`.
+Ainsi toutes les barres commencent légèrement remplies, y compris Minage et
+Construction, puis avancent à chaque achat. Le survol explique ce principe et
+conserve le détail de la capacité et du prochain effet, en français et en anglais.
+
+La barre prend la largeur restante de la ligne. Les noms longs peuvent défiler
+sur une petite interface ; leur infobulle conserve le nom et les capacités.
+Le panneau des aptitudes garde son défilement indépendant.
+
+## Portée
+
+Affichage uniquement : prix, progression serveur, remboursements, prestige,
+protocole et sauvegardes restent identiques. L’inventaire garde **six rangées** ;
+un palier supplémentaire reste une idée à discuter. Aucun monde existant n’est
+modifié. JEI et l’optimisation du chargement de beta.024 sont conservés.
+
+## Vérifications et livraison
+
+`./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,inventory,cycle`
+réussit : **24 tests serveur** et les contrôles de contrat du projet passent.
+
+Le parcours graphique natif dédié réussit avec :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
+ -PsanctuarySkillsClientTests=true
+```
+
+Six barres alignées en français/anglais, GUI 1, 2 et automatique, fenêtre de
+640 × 480 et défilement jusqu’à l’Inventaire. Achat réel du dernier rang refusé
+à 47 niveaux puis accepté à 48, remboursement opérateur exact, barres pleines
+sans nouvel achat et retour entre onglets. Les dix captures ont été inspectées ;
+elles sont conservées dans `build/skills025-evidence/`.
+Le test fixe explicitement l’XP à zéro après l’admission, car les Premiers pas
+peuvent déjà en avoir donné. Le client de développement a nécessité de désactiver
+sa VSync après un blocage OpenGL au démarrage ; aucun réglage personnel modifié.
+
+L’export est contrôlé : ZIP, version interne, JAR et 1 253 classes identiques au
+build, dépendances Demeure/JEI imbriquées identiques, absence de classes de test.
+Seuls l’écran Progression et le nouveau composant de barre changent dans le code
+compilé Sanctuary par rapport à beta.024. Les autres classes, notamment les règles
+de progression, l’inventaire et le terrain, sont identiques. Le MRpack beta.024
+conserve son empreinte.
+
+[Sanctuary-beta.025.mrpack](../build/Sanctuary-beta.025.mrpack), **4 765 821 octets**.
+SHA-256 : `0601841b46da38656c00a2f88a690883b9066e886af7b0a3ba6df7677bae0ef7`.
+Reçu : `build/skills025-artifact.json`. Journaux : `build/skills025-check.log`
+et `build/skills025-client.log`. Export local ; canal packwiz et installation
+Prism personnelle non modifiés.
diff --git a/docs/sky-flight-zoom-beta046.md b/docs/sky-flight-zoom-beta046.md
new file mode 100644
index 0000000..6d1eff7
--- /dev/null
+++ b/docs/sky-flight-zoom-beta046.md
@@ -0,0 +1,100 @@
+# beta.046 — ciel dessiné, bateau à poule, zoom et identité de l’application
+
+## Gestes et règles
+
+Longue-vue en main principale ou secondaire, en première personne : un clic
+gauche sur une étoile choisit l’étoile mère, puis un clic gauche sur une autre
+étoile ajoute un trait depuis la précédente, pour trois niveaux. Aucun zoom
+n’est requis. Clic droit annule la dernière liaison de cette constellation ;
+Maj + clic gauche choisit une autre étoile mère. La suppression est gratuite,
+sans remboursement. Chacun peut retirer ses propres traits. Les étoiles qui
+portent les accomplissements restent intactes. Tous les habitants voient le dessin.
+
+La couleur est tirée de manière déterministe depuis l’identité de l’étoile mère,
+dans une palette de 64 teintes : elle reste stable après annulation, reconnexion
+et redémarrage. Les traits suivent la même rotation hebdomadaire que les étoiles.
+La géométrie GPU est conservée entre les images et passe dans le rendu natif
+des étoiles, avec au plus 64 groupes de couleur ; aucune reconstruction quand
+on tourne la caméra. Les moteurs de shaders externes ne sont pas certifiés ici.
+
+Une poule vivante embarquée avec le joueur permet de voler en bateau : Espace
+monte, relâcher descend. Le joueur pilote même si la poule est montée en premier.
+Montée et descente visent chacune cinq blocs/seconde, avec une accélération
+progressive. Au retour sur l’eau, relâcher laisse fonctionner la flottabilité.
+Les collisions et les dégâts restent natifs ; le vol remet à zéro la distance
+de chute. Sans poule ou sans pilote, la physique native reprend.
+Les paramètres serveur config/sanctuary/chicken-boat.json expriment les vitesses
+et l’accélération en blocs/tick ; ils sont synchronisés avec les clients.
+Le contrôle natif de vol du véhicule reconnaît ce cas sans activer Allow Flight
+pour les autres véhicules.
+
+L’aptitude Zoom coûte quatre niveaux. C est sa touche initiale,
+reconfigurable ; en la maintenant, la molette règle le FOV de 5° à 110°
+(Quake Pro). Relâcher restaure immédiatement le FOV normal, qui n’est jamais
+réécrit dans les options. Le panoramique natif conserve ses six faces à 90°.
+Aucun objet caméra n’est ajouté.
+
+Les dix aptitudes portent désormais les icônes PNG 9 × 9 fournies par le
+créateur, placées à gauche de leur titre. Les titres longs passent à la ligne
+dans la colonne. Les fichiers sont copiés sans retouche dans le mod et le
+resource pack, sous assets/sanctuary/textures/gui/aptitudes/. Familiarpower
+illustre le pouvoir actif ; Familiar illustre l’étude des familiers.
+
+Le titre de fenêtre devient Sanctuary - beta.046, y compris après connexion
+ou déconnexion. La fenêtre utilise l’icône officielle via le chargement SDL
+natif de Minecraft sur Windows, Linux et macOS ; les métadonnées SDL et le
+modèle d’instance Prism portent aussi le nom Sanctuary. L’icône d’une application
+tierce comme le lanceur Prism n’est pas remplacée.
+
+## Données et compatibilité
+
+Les étoiles existantes gardent leur journal intact. Un journal distinct
+data/sanctuary-constellations.json, schéma 1, conserve les ajouts payés et les
+annulations, lié à l’identifiant du ciel. Le reçu joueur additif
+sanctuary:constellation_xp_paid est sauvegardé avec l’XP, copié à la mort, et
+réconcilié avant le reset New Game+ pour éviter un deuxième débit après arrêt.
+Un fichier invalide est conservé et ses mutations sont refusées.
+
+Le zoom utilise le booléen persistant additif sanctuary:zoom_learned, absent
+donc faux avant achat, copié à la mort et conservé par le New Game+.
+Aucune réécriture des historiques de progression ni modification du terrain.
+Les nouveaux paquets ont des identifiants sanctuary:*_v1 distincts.
+Les factions et leur migration sont décrites dans
+[faction-two-slots-beta046.md](faction-two-slots-beta046.md).
+
+## Vérifications et livraison
+
+Le parcours client natif passe sur macOS : achat réel du zoom, verrouillage
+avant achat, projection mesurée à 5° et 110°, restauration au relâchement,
+clics de création et d’annulation d’une liaison, dépense de trois niveaux,
+vol continu après embarquement de la poule, montée et descente mesurées.
+Le titre de fenêtre et les métadonnées SDL sont contrôlés au menu et en jeu.
+Les captures de jeu et du défilement des dix icônes FR/EN sont conservées
+dans `build/beta046-evidence/`.
+
+Les 20 GameTests serveur ciblés passent, dont quatre nouveaux scénarios sur
+les liaisons, la récupération après arrêt, le bateau et l’achat du zoom.
+Ils vérifient les droits du propriétaire, les refus sans XP, les doublons,
+la conservation à la mort et au prestige et les fichiers invalides préservés.
+Le premier essai avait une assertion incorrecte sur l’ordre des passagers :
+Minecraft replace nativement le joueur devant la poule à l’embarquement.
+L’assertion a été corrigée sans changer la physique validée dans le client.
+
+Commandes reproductibles avec Java 25 :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryQuickTests=true -PsanctuarySkyFlightClientTests=true -PsanctuaryClientNoVsync=true
+./gradlew check build assemblePack assembleTestPack -PsanctuaryFocusedTests=cycle,operator,skyflight
+```
+
+`sanctuaryClientNoVsync` contourne un blocage de présentation SDL/OpenGL du
+client automatisé. Ce mixin existe uniquement dans les tests, sans changer
+les réglages du pack livré. La validation graphique Windows/Linux, le Dock
+macOS et les moteurs de shaders tiers restent à effectuer.
+
+`check build assemblePack assembleTestPack` passe avec la version finale.
+Les deux MRpacks sont vérifiés contre les 1 503 classes compilées, les ressources
+et les JAR imbriqués ; les dix icônes correspondent exactement aux originaux.
+Les exports et leurs empreintes sont consignés dans [Distribution](packwiz.md).
+Logs : `build/beta046-delivery.log` et `build/beta046-client.log`.
+Reçu : `build/beta046-artifact.json`.
diff --git a/docs/sky-spacing-beta056.md b/docs/sky-spacing-beta056.md
new file mode 100644
index 0000000..714973e
--- /dev/null
+++ b/docs/sky-spacing-beta056.md
@@ -0,0 +1,59 @@
+# beta.056 — étoiles dispersées et saturation par défaut
+
+Branche `codex/sky-spacing-beta056`. Les étoiles d'un même accomplissement
+se répartissent sur toute la sphère céleste, au lieu de former un petit amas.
+Chaque direction est tirée uniformément en surface, pôles compris. Le placement
+écarte les étoiles d'un même événement de 30° avec un maximum de 32 essais
+par étoile ; le meilleur essai est conservé si le seuil n'est pas atteint.
+Les événements indépendants peuvent naturellement être voisins : plus le
+serveur accumule d'habitants et d'accomplissements, plus le ciel devient dense.
+Le nombre d'étoiles gagnées et leur taille ne changent pas.
+
+La position dépend uniquement de la graine de l'événement et de l'indice de
+l'étoile. Ajouter des habitants ou des événements, tourner la caméra ou se
+reconnecter ne redistribue pas les étoiles. Le maillage et l'index de visée
+restent mis en cache ; aucun nouveau calcul périodique n'est ajouté.
+
+## Reprise du ciel existant
+
+Cette version change une fois la disposition visuelle des souvenirs existants.
+Les fichiers `sanctuary-stars.json` et `sanctuary-constellations.json` restent
+en schéma 1, sans réécriture ni migration de données. Les références
+`(seed, index)`, les noms, origines, couleurs et liaisons gardent leur identité.
+Les constellations suivent ces nouvelles positions : leurs formes changent.
+Affichage, sélection à la longue-vue et lignes utilisent la même géométrie.
+Aucun monde personnel ni chunk n'est modifié pendant le développement.
+
+## Saturation
+
+Vanilla Light adopte **142 %** par défaut, dans la préférence cliente et
+l'uniforme du shader. Une configuration sans saturation reçoit 142 % ; une
+valeur déjà enregistrée reste respectée, y compris 100 % ou 0 %.
+Le curseur reste disponible dans Options → Shaders, de 0 à 200 %.
+Les explications françaises et anglaises indiquent la nouvelle valeur.
+
+## Vérifications
+
+`Sky056ClientChecks` réussit sur Minecraft 26.3-pre-2 / Java 25 :
+10 000 positions déterministes, références stables par indice, séparation des
+étoiles d'un même événement, couverture des secteurs et des pôles. Le catalogue
+natif injecté pour trois habitants produit 724 étoiles (242 pour un seul).
+Maillage conservé pendant les rotations de caméra ; tracé payé et inspection
+à la longue-vue d'étoiles bonus ; relecture intacte des deux journaux en schéma 1
+et rechargement des ressources. Préférence absente/ancienne/explicite, persistance
+de 142 %, écran et aide FR/EN vérifiés. Captures relues : `build/sky056-evidence/`.
+
+`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
+réussit. Le test natif tourne séparément avec `-PsanctuaryClientTests=true
+-PsanctuarySky056ClientTests=true -PsanctuaryQuickTests=true`, dans un monde plat
+de développement, sans serveur dédié. Aucun EULA supplémentaire accepté.
+Logs : `build/sky056-check.log`, `build/sky056-client.log`.
+
+Le reçu `build/sky056-artifact.json` vérifie les 1 588 classes, les ressources,
+les deux exports, la conservation des archives beta.055 et de JEI. Seules deux
+classes changent : géométrie des étoiles et préférences du shader. Les nouvelles
+ressources sont la valeur uniforme et les deux aides localisées.
+Aucune publication du canal ni installation personnelle.
+
+[Pack normal](../build/Sanctuary-beta.056.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.056.mrpack).
diff --git a/docs/spawn-eggs-audit-beta044.md b/docs/spawn-eggs-audit-beta044.md
new file mode 100644
index 0000000..a75bd9b
--- /dev/null
+++ b/docs/spawn-eggs-audit-beta044.md
@@ -0,0 +1,132 @@
+# beta.044 — pouvoirs de familiers et icône officielle
+
+## Résultat attendu
+
+Vérifier les 88 œufs dans un contexte où leur actif peut réellement fonctionner,
+corriger les écarts avec les descriptions et employer les attaques natives des
+mobs pour les explosions, incendies et effets de statut. Le créateur a demandé
+explicitement des destructions réelles, en remplacement des limitations de la
+proposition v2. Le Slime crée un bloc temporaire de **5 secondes**.
+
+Branche : codex/spawn-egg-audit-beta044. Minecraft **26.3-pre-2**.
+[Catalogue des 88 passifs, actifs et recharges](spawn-eggs-catalogue-beta044.md).
+
+## Corrections
+
+- Slime : véritable bloc sous les pieds, collision et rebond natifs ; le joueur
+ est posé dessus. Refus si la case est occupée ou si le plafond empêche ce geste.
+ Le terrain existant n'est pas remplacé.
+- Creeper : mèche de 1,5 s puis explosion native de puissance 3, avec destruction.
+- Blaze : trois petites boules de feu natives qui brûlent les cibles et peuvent
+ allumer le terrain.
+- Ghast : boule de feu native, explosion et incendies.
+- Wither : trois crânes natifs, explosions, Wither selon la difficulté et soin
+ natif sur une victime tuée par l'impact direct.
+- Dragon : boule de souffle et véritable nuage de dégâts instantanés II.
+- Breeze : véritable charge de vent, poussée et interactions natives ; elle
+ conserve aussi l'action de déplacement des brumes proches.
+- Evoker : les crocs invoqués mordent réellement ; suppression des dégâts
+ artificiels qui accompagnaient auparavant des crocs purement visuels.
+- Shulker : projectile guidé et Lévitation, à la place de l'appui virtuel.
+- Warden : onde sonique qui traverse les murs, armures et boucliers, avec les
+ 10 PV et le recul de cette version native.
+- Gardien : charge de quatre secondes et dégâts magiques/physiques natifs.
+- Lama et Golem de neige : projectiles natifs. Les boules de neige infligent
+ notamment leurs dégâts aux Blazes, sans ralentissement artificiel.
+- Bogged, Parched et Stray : Poison, Faiblesse et Lenteur sur les vraies flèches.
+- Araignée venimeuse, Wither squelette et Poisson-globe : effets natifs de
+ Poison/Wither au contact, avec leurs immunités.
+- Golem de cuivre : un conteneur visible ne se masque plus lui-même pendant
+ la recherche de destination. Une paroi intermédiaire continue à empêcher
+ le transfert.
+- Vex : le minage spectral dépend de Mining, sans exiger aussi Building.
+- Piglin zombifié : le secours sans cible ne réclame plus un lingot d'or.
+- Les projectiles ignorent les familiers invulnérables qui pourraient gêner
+ leur départ.
+
+Les FR/EN sont synchronisés avec ces gestes. Les durées d'annonce, achats et
+recharges restent gérés par le serveur. Une blessure peut interrompre une
+préparation ; un projectile déjà lancé poursuit ensuite sa vie native.
+
+## Règles de monde et sauvegarde
+
+Les explosions Creeper/Ghast/Wither utilisent l'interaction native MOB :
+la règle mob_griefing décide des dégâts au terrain. Le Ghast lui associe les
+incendies. Les petites boules du Blaze ont un tireur joueur et suivent donc
+le comportement natif des projectiles de joueur pour le feu. Les probabilités
+de propagation du feu, résistances des blocs, immunités, difficulté et règles
+PvP restent celles du moteur. Aucun plafond de dégâts de l'ancien projectile
+Sanctuary n'est ajouté à ces attaques natives.
+
+Les capacités utilitaires de soin, de déplacement, d'exploration et de travail
+gardent leurs contrats communs. Ce ticket ne remplace pas chaque capacité
+utilitaire par une attaque et ne redéfinit pas les rangs ou Anomaly.
+
+Ajout de l'identifiant stable **sanctuary:slime_springboard**, sans BlockItem,
+sans butin et immobile pour les pistons. Sa texture utilise le modèle vanilla
+du slime. Son expiration utilise les ticks planifiés natifs, déjà sauvegardés
+avec les chunks : 100 ticks de jeu ; en chunk déchargé, elle est exécutée à la
+reprise de la simulation. Aucun format de sauvegarde Sanctuary n'est modifié,
+aucun monde existant n'est ouvert ou converti, aucune génération n'est changée.
+Client et serveur doivent employer beta.044 ensemble.
+
+## Commandes Sanctuary après activation du LAN
+
+Le signalement précis /sanctuary time vanilla est reproduit dans le chat :
+« Incorrect argument for command at position 10: sanctuary ». L'hôte possède
+bien les droits OWNERS. C'est le dispatcher local de /sanctuary intro qui
+capture la racine et bloque les sous-commandes serveur.
+
+Le replay passe désormais par une vraie commande serveur et un message de
+prévisualisation en jeu, distinct du handshake de première admission.
+La racine commune est publique ; chaque branche administrative garde son
+contrôle opérateur. Un habitant ordinaire peut rejouer l'intro, mais ne peut
+pas modifier l'heure, inspecter le census ou gérer les expansions.
+Aucun invité n'est promu et Allow Commands reste l'autorité en solo/LAN.
+
+## Icône
+
+Variante finale fournie : sanctuary_icon-export.png, **bleu en haut**.
+Copie exacte dans assets/sanctuary/icon.png et packwiz/icon.png.
+SHA-256 : 33adb8f689d55b7bf8d0824b02942ea3823f128b27fab458713ab9582061a1a0.
+L'icône accompagne le mod, le MRpack et le modèle d'instance Prism.
+
+## Vérifications
+
+Livraison vérifiée avec check build assemblePack assembleTestPack,
+sélection eggaudit, companions, refonte, headblocks, operator :
+**69 tests serveur réussis**, dont les **88 contextes positifs** dans le test
+de catalogue. Le bloc de Slime expire, le Vex termine son minage, le Golem de
+cuivre transfère le vrai stock, les projectiles ont leurs impacts natifs et la
+règle de destruction du terrain est respectée.
+
+Deux parcours client réussis : menus des blocs portés et vrai shift-click ;
+LAN ouvert sans commandes, activation tardive, saisie exacte de
+/sanctuary time vanilla dans le chat, révocation des droits, puis replay
+d'introduction par un habitant ordinaire. Le contrôle visuel du coffre confirme
+trois rangées de conteneur et six rangées de joueur.
+
+Le défaut LAN est reproduit avant le correctif dans
+build/egg044-lan-baseline.log et corrigé dans build/egg044-lan-fixed.log.
+Autres traces : build/egg044-client1.log et build/egg044-delivery.log.
+Reçu d'intégrité : build/egg044-artifact.json.
+Les tests positifs des 88 œufs préparent leurs ressources, cibles, environnement,
+outils et compétences, puis exigent une activation réussie. Des assertions
+supplémentaires vérifient notamment les stocks, consommables, échanges, air,
+chutes, bloc de Slime et impacts natifs.
+
+Ces tests et la relecture des hooks ne remplacent pas une séance d'équilibrage
+des 88 pouvoirs avec plusieurs vrais joueurs, ni un contrôle visuel exhaustif
+des 88 familiers.
+
+## Artefacts locaux
+
+[Pack normal](../build/Sanctuary-beta.044.mrpack), 5250933 octets.
+SHA-256 : a76cabcadb85c3c0439961b8a175ee8be28fcf246970a3e57ca6475da0c169f0.
+
+[Monde plat rapide](../build/Sanctuary-Test-beta.044.mrpack), 5269868 octets.
+SHA-256 : 06f4893e4e4d629a9c8791e59000678c768ad134e32f7346c103edd85e304ed2.
+
+Archives, manifestes, icône, 1 470 classes compilées et dépendances imbriquées
+comparés au build. Les anciennes archives beta.043 restent inchangées.
+Aucun déploiement du canal public, d'instance personnelle ou de monde existant.
diff --git a/docs/spawn-eggs-catalogue-beta044.md b/docs/spawn-eggs-catalogue-beta044.md
new file mode 100644
index 0000000..8be104f
--- /dev/null
+++ b/docs/spawn-eggs-catalogue-beta044.md
@@ -0,0 +1,109 @@
+# Familiers — catalogue beta.044
+
+Ce catalogue remplace les descriptions beta.032 pour cette livraison.
+Les passifs et les recharges restent ceux de la refonte, avec les attaques natives
+corrigées et le véritable bloc de Slime temporaire. Les attaques natives conservent
+leurs dégâts et leurs effets de terrain ; elles ne passent plus par le plafond
+des anciens projectiles de familiers. Soins, déplacements et aides utilitaires
+conservent leurs règles communes.
+
+Œuf équipé et stabilisé pendant 10 s, Lien actif acheté, puis **G**.
+L'Étude des familiers affiche ces explications dans les infobulles.
+Les rangs I–III et Anomaly restent hors de cette livraison.
+
+Chaque ligne a un contexte positif d'activation dans Companion044GameTests.
+Cela vérifie la préparation et l'exécution, avec assertions supplémentaires sur
+les stocks et plusieurs impacts. Cela ne remplace pas un essai visuel et
+d'équilibrage de chaque combinaison en multijoueur.
+[Contrat et vérifications](spawn-eggs-audit-beta044.md).
+
+| Œuf | Passif | Actif | Recharge |
+|---|---|---|---:|
+| `bat` | L'obscurité est légèrement éclaircie, même en mouvement. Cécité et Ténèbres restent actives. | Repère 3 présences à 12 blocs pendant 6 s ; partage avec 4 personnes au plus dans un rayon de 16 blocs. | 15 s |
+| `cat` | Réduit les dégâts de chute de 20 %. | Fait reculer jusqu'à 4 petits monstres ordinaires à 5 blocs pendant 2 s. Les boss et joueurs résistent. | 20 s |
+| `chicken` | Tolère un bloc de chute supplémentaire. | Plane pendant 3 s sans remonter. Efface la distance de chute accumulée à l'activation. | 15 s |
+| `cod` | Économise 10 % de l'air normalement perdu. | Rend jusqu'à 30 points d'air au plongeur visé à 6 blocs, ou à soi. Budget commun : un tiers de la capacité en 30 s. | 20 s |
+| `cow` | Le lait bu conserve les effets bénéfiques. | Consomme 1 seau de lait pour retirer Poison et Faim de 3 personnes à 3 blocs. Rend le seau vide. | 30 s |
+| `donkey` | Réduit l'épuisement du sprint de 10 %. | Range jusqu'à 18 piles hors hotbar dans un coffre, tonneau ou shulker à portée normale. Seulement les types déjà présents. | 10 s |
+| `fox` | Déplacement accroupi 10 % plus rapide sur tous les sols. | Esquive au sol de 3 blocs et ramasse un objet à la fin du trajet. Sans invulnérabilité. | 10 s |
+| `frog` | Réduit de 10 % la gêne du déplacement dans l'eau peu profonde. | À 6 blocs, attire un objet, un petit monstre ou un allié qui maintient Maj. Le déplacement allié utilise son budget aérien. | 10 s |
+| `horse` | Réduit l'épuisement du sprint de 10 %. | Ruée dirigée de 4 blocs à pied ou sur la monture contrôlée. Garde les collisions et les pénalités de portage. | 25 s |
+| `llama` | Les laisses tenues tolèrent 2 blocs supplémentaires. | Projette un véritable crachat de lama : 1 PV de dégâts natifs, avec sa trajectoire et ses collisions. Aucun objet consommé. | 12 s |
+| `mule` | Réduit le recul de 20 % lorsque tu portes ou es porté. | Livre jusqu'à 9 piles du type tenu en main gauche à une monture possédée ou un allié consentant visé à 3 blocs. Hotbars préservées. | 10 s |
+| `pig` | Les aliments apportent 10 % de saturation supplémentaire. | Repère jusqu'à 3 récoltes, bois ou fleurs déjà découverts à 16 blocs pendant 10 s. Repères partagés, aucun objet créé. | 15 s |
+| `rabbit` | Contrôle de direction en l'air amélioré de 25 %, sans vitesse ni hauteur ajoutée au saut normal. | Un bond par vol : jusqu'à 3 blocs vers l'avant, ou 2 vers le haut en visant haut. Appuyer à nouveau après réception ; budget aérien commun. | 8 s |
+| `salmon` | Les courants poussent 25 % moins fort. | Impulsion aquatique dirigée de 3 blocs, puis sillage de 5 s pour les nageurs qui suivent en maintenant Maj. Nage requise. | 12 s |
+| `sheep` | Réduit les dégâts de chute de 15 %. | Consomme 1 laine pour poser à 6 blocs un coussin de 2 × 2 pendant 8 s. Une chute amortie par personne, 4 personnes au total : moitié des dégâts, jusqu'à 4 PV. | 30 s |
+| `skeleton` | Réduit de 15 % la dispersion des flèches en mouvement lent. | La prochaine flèche tirée sous 8 s marque sa victime pendant 6 s pour 4 observateurs proches au moment du tir. Arc et munition réels. | 15 s |
+| `slime` | Réduit les dégâts de chute de 15 % et la gêne du miel de 15 %. | Fait apparaître un vrai bloc de slime sous tes pieds et te pose dessus. Rebond natif ; le bloc disparaît après 5 s et ne donne aucun objet. Il faut une case libre et assez de place au-dessus. | 15 s |
+| `spider` | Monte les échelles et lianes 15 % plus vite. | Accroche une paroi à 2 blocs pendant 4 s et grimpe jusqu'à 2 blocs. La paroi doit rester présente. | 15 s |
+| `squid` | L'eau proche est plus lisible. | Pose à 4 blocs un nuage d'encre de rayon 2 pendant 4 s. Gêne l'acquisition des monstres ordinaires ; le vent le déplace, sans arrêter les projectiles. | 25 s |
+| `tadpole` | Reprend son air 20 % plus vite à la surface. | Repère une berge ou une sortie libre proche, dans un rayon de 16 blocs, pendant 10 s. Partage le repère sans téléporter. | 12 s |
+| `tropical_fish` | Souligne les objets visibles au sol sous l'eau à 6 blocs. | Pose une balise colorée à 8 blocs pour 20 s, partagée avec 4 personnes à 16 blocs. | 10 s |
+| `zombie` | Réduit le recul de 20 % sous 30 % de vie. | Consomme 1 point de faim pour récupérer 1 PV en 5 s. Un coup reçu ou porté interrompt le soin ; budget commun de soin. | 30 s |
+| `armadillo` | Accroupi, réduit de 15 % les dégâts des projectiles, sans attente. | Garde de 3 s, réduction jusqu'à 40 %. Ralentit et empêche d'attaquer ; budget commun de garde de 6 s sur 20 s. | 25 s |
+| `bee` | Clic droit avec une houe sur une culture mûre : récolte native et replante avec une graine de l'inventaire débloqué. L'outil s'use. | Consomme une poudre d'os par plante visée à portée normale, jusqu'à 4 plantes selon le rang de construction. Aucun rendement ajouté. | 20 s |
+| `camel` | Réduit l'épuisement du sprint de 15 % sur tous les sols. | Enjambée de 5 blocs à pied ou sur la monture contrôlée ; franchit un obstacle bas avec collisions conservées. | 15 s |
+| `cave_spider` | Les poisons reçus durent 20 % moins longtemps. | Prépare le prochain vrai coup de mêlée sous 6 s : Poison natif pendant 7 s en Normal ou 15 s en Difficile. Aucun poison en Facile ; les immunités natives restent actives. | 25 s |
+| `copper_golem` | Le conteneur autorisé visé indique ses cases libres et les piles correspondant à la main gauche. | Répartit le type tenu en main gauche entre 4 conteneurs à portée normale, 9 piles par conteneur au plus, selon les types présents. Hotbar préservée. | 15 s |
+| `dolphin` | Améliore de 25 % le contrôle de direction dans l'eau, sans vitesse gratuite. | Sillage de 5 s : nage 20 % plus rapide pour toi et les alliés qui suivent en maintenant Maj à 3 blocs. Nage et faim suffisante requises ; 4 personnes au total. | 25 s |
+| `drowned` | Réduit de 10 % la pénalité de minage sous l'eau. | Ancrage de rayon 3 pendant 6 s : maintenir Maj sur un support réel pour ne plus dériver. 4 personnes consentantes au total. | 20 s |
+| `glow_squid` | Souligne doucement les surfaces sombres proches, sur terre et sous l'eau, sans changer la lumière du monde. | Balise lumineuse de 20 s à 8 blocs ; peut suivre l'objet visé. Visible pour 4 personnes proches. | 15 s |
+| `goat` | Réduit de 20 % la glissade supplémentaire sur glace. | Charge de 3 blocs : jusqu'à 2 PV et déplacement d'un monstre ordinaire. Aucun recul imposé aux joueurs. | 15 s |
+| `husk` | L'effet Faim reçu dure 20 % moins longtemps. | Pose à 4 blocs une poussière mobile de rayon 2 pendant 4 s. Ralentit jusqu'à 4 ennemis de 20 %, avec résistance aux contrôles successifs. | 25 s |
+| `ocelot` | Déplacement accroupi 10 % plus rapide sur tous les sols. | Pas latéral de 3 blocs, sans invulnérabilité ni attaque gratuite. | 10 s |
+| `panda` | Réduit le recul de 20 % au sol. | Roulade de 3 blocs avec une brève protection de 25 %. Peut déplacer les petits monstres proches, sans dégâts de terrain. | 20 s |
+| `parrot` | Localise un danger connu proche par un son et un repère, au plus toutes les 5 s. | Place un leurre sonore à 8 blocs pendant 4 s, partagé comme repère. Détourne jusqu'à 3 monstres ordinaires. | 20 s |
+| `pufferfish` | Après un contact hostile, éclat visuel et recul réduit de 20 % pendant 2 s. | Pendant 5 s, le prochain attaquant de mêlée reçoit le Poison natif pendant 4 s. Une seule riposte ; les immunités de la cible s'appliquent. | 25 s |
+| `polar_bear` | Réduit le recul de 20 % à proximité d'un allié consentant. | Protection de rayon 3 pendant 4 s : dégâts et recul réduits de 25 % pour 4 personnes au total. | 30 s |
+| `snow_golem` | Réduit de 20 % la glissade supplémentaire sur glace ; bref appui sur neige poudreuse. | Consomme 3 boules de neige pour tirer une rafale de vrais projectiles. Dégâts natifs contre les Blazes ; aucun ralentissement ajouté. | 20 s |
+| `strider` | Les brûlures durent 20 % moins longtemps, sans immunité. | Traverse une surface de lave pendant 3 s sur 5 blocs au plus. Il faut avancer ; rester sur place met fin au pouvoir. | 35 s |
+| `turtle` | Économise 10 % de l'air perdu, même en mouvement. | Garde directionnelle mobile de 4 s, sur terre ou dans l'eau. Jusqu'à 40 % de réduction devant 4 personnes ; budget de garde partagé. | 30 s |
+| `villager` | Mémorise les offres et stocks personnellement consultés. | Sélectionne puis effectue jusqu'à 3 échanges identiques avec un marchand accessible. Paiement, stocks et prix natifs. | 15 s |
+| `wolf` | Marque brièvement l'ennemi qui vient de te blesser. | Marque une menace à 12 blocs pendant 8 s ; appelle tes animaux dressés et crée un point de regroupement volontaire de 4 s. Maj pour bondir, budget aérien commun. | 25 s |
+| `bogged` | Le déplacement sur boue est moins entravé. | La prochaine flèche tirée sous 8 s porte le Poison natif pendant 5 s. Arc et munition réels ; les immunités de la cible s'appliquent. | 25 s |
+| `parched` | L'effet Faiblesse reçu dure 20 % moins longtemps. | La prochaine flèche tirée sous 8 s porte la Faiblesse native pendant 30 s. Arc et munition réels. | 25 s |
+| `allay` | Ramasse à 3 blocs les objets correspondant à la main gauche, dans les cases débloquées. | Rassemble jusqu'à 16 piles visibles à 8 blocs vers un point visé à 4 blocs pendant 3 s. Pas de transfert automatique dans les conteneurs. | 12 s |
+| `axolotl` | Mange 10 % plus vite, sur terre et dans l'eau. | Consomme 1 point de faim pour rendre jusqu'à 20 % de la vie maximale en 4 s à soi ou un allié visé à 6 blocs. Un coup reçu ou porté interrompt ; budget de soin partagé. | 30 s |
+| `blaze` | Les brûlures durent 20 % moins longtemps. | Après 0,5 s, tire 3 petites boules de feu natives : dégâts, brûlures et vrais incendies sur les blocs touchés. Aucune munition consommée. | 25 s |
+| `breeze` | Contrôle de direction en l'air amélioré de 25 %, sans remontée. | Tire une vraie charge de vent de Breeze : impact, souffle et interactions natives avec les blocs. La rafale déplace aussi les brumes de familiers proches. | 15 s |
+| `camel_husk` | Réduit l'épuisement terrestre du sprint de 15 %, de jour comme de nuit. | Ruée de 6 blocs qui laisse un écran de poussière de rayon 2 pendant 4 s. Garde les collisions et le budget aérien. | 25 s |
+| `creeper` | Localise les amorçages explosifs proches à 8 blocs. | Après une mèche de 1,5 s, déclenche une explosion de Creeper de puissance 3 : dégâts et destruction réelle du terrain selon les règles du serveur. Un coup reçu pendant la mèche interrompt l'amorçage. | 25 s |
+| `endermite` | Réduit les petits dégâts de perle de 20 %. | Téléportation jusqu'à 3 blocs vers une arrivée libre sur support réel. Peut traverser les murs autorisés ; budget aérien commun. | 15 s |
+| `guardian` | Réduit de 15 % la dispersion des flèches en mouvement lent. | Canalise 4 s un rayon vers l'ennemi visé à 12 blocs. Inflige les dégâts magiques puis physiques natifs du Gardien. Garder la cible visible ; un coup reçu interrompt. | 30 s |
+| `hoglin` | Réduit le recul de mêlée de 20 %. | Charge annoncée de 4 blocs : jusqu'à 4 PV et poussée d'un monstre devant soi, sans traverser les murs. | 25 s |
+| `magma_cube` | Les brûlures durent 20 % moins longtemps. | Bond de 3 blocs puis réception brûlante : rayon 2, 3 ennemis, jusqu'à 3 PV chacun. Aucun feu de terrain. | 25 s |
+| `mooshroom` | Consomme la soupe aux champignons 10 % plus vite. | Distribue la soupe tenue à 4 personnes à 4 blocs, une portion réelle par personne. Rend les bols ; faim et saturation natives, sans soin gratuit. | 30 s |
+| `nautilus` | Économise 10 % de l'air perdu, même en mouvement. | Cloche d'air aquatique à 6 blocs, rayon 2, durée 6 s, 4 personnes au total. Suspend la perte d'air tant que le budget du bénéficiaire le permet. | 45 s |
+| `phantom` | Contrôle aérien amélioré de 25 % et dégâts de chute réduits de 20 %, même le jour. | Descente planée de 6 s, sans remontée ni nouveau bond. Efface la distance dangereuse à l'activation. | 30 s |
+| `piglin` | Mémorise les objets réellement reçus par troc. | Consomme 2 lingots d'or auprès d'un Piglin accessible et reçoit 2 tirages natifs complets. Aucun choix améliorant le hasard. | 30 s |
+| `pillager` | Arbalète rechargée 10 % plus vite ; meilleure stabilité en mouvement lent. | Prépare sous 8 s un tir de couverture réel. À l'impact, ralentit 3 monstres dans un rayon de 2 blocs de 20 % pendant 3 s. | 25 s |
+| `silverfish` | L'inspection d'une paroi proche distingue pierre et infestation après 2 s. | Sonde les 2 blocs derrière la face visée et partage un repère de cavité pendant 8 s. Aucun minerai révélé. | 20 s |
+| `sulfur_cube` | Poison et Faiblesse durent 10 % moins longtemps. | Pose à 6 blocs une zone acide de rayon 2 pendant 4 s : 4 ennemis au total, jusqu'à 3 PV par cible. Sans usure d'armure spéciale ni terrain détruit. | 30 s |
+| `trader_llama` | Affiche les places libres des montures possédées proches. | Rassemble 3 montures possédées à 12 blocs autour d'un point praticable visé. Navigation native. | 20 s |
+| `wandering_trader` | Mémorise les offres et lieux des marchands personnellement rencontrés. | Partage jusqu'à 4 références d'approvisionnement déjà observées avec 4 personnes proches. Aucun achat à distance. | 20 s |
+| `zombie_horse` | Réduit de 20 % les dégâts de chute de la monture contrôlée. | Pendant 6 s, monture et cavalier résistent 20 % mieux au recul ; dans l'eau, la monture gagne 15 % de vitesse. Aucune condition d'heure. | 25 s |
+| `zombie_villager` | Indique le temps restant d'une conversion observée. | Veille de 8 s sur une conversion engagée : retire 20 s une fois, ingrédients natifs requis. Sinon, secours payé en faim à un allié blessé. | 35 s |
+| `zombified_piglin` | Localise une montée de colère proche. | Consomme 1 lingot pour un répit de 2 s auprès de 3 Piglins zombifiés à 5 blocs. Sans cible adaptée : résistance au recul de 25 % pendant 4 s, sans lingot consommé. | 30 s |
+| `stray` | Visée plus stable en mouvement lent ; glissade réduite en bandant l'arc. | La prochaine flèche tirée sous 8 s porte la Lenteur native pendant 30 s. Arc et munition réels. | 25 s |
+| `vindicator` | Travaille le bois à la hache environ 10 % plus vite. | Prépare un vrai coup de hache sous 6 s : arc frontal sur 2 ennemis supplémentaires à 3 blocs, moitié des dégâts de l'arme, jusqu'à 4 PV chacun. | 25 s |
+| `zoglin` | Réduit le recul de 15 % sous la moitié de vie. | Charge annoncée de 5 blocs à travers 3 ennemis ordinaires, jusqu'à 4 PV chacun. Garde les murs et le budget de déplacement. | 30 s |
+| `creaking` | Après 3 s immobile hors attaque, réduit les dégâts de 15 %. | Pose à 6 blocs des racines de rayon 2 retenant 4 monstres au plus pendant 3 s. Puis 6 s de résistance ; joueurs et boss immunisés. | 45 s |
+| `elder_guardian` | Réduit de 15 % la pénalité de minage aquatique. | Sanctuaire de rayon 3 pendant 6 s, sur terre et sous l'eau : dégâts et recul réduits de 25 % pour 4 personnes au total. | 60 s |
+| `enderman` | Indique la case de placement du bloc tenu à portée normale. | Ouvre pendant 8 s un passage à entrée fixe vers une arrivée libre à 8 blocs. Maintenir Maj dans l'entrée ; 4 voyageurs, droits et budget de chacun vérifiés. | 45 s |
+| `evoker` | Faiblesse et Lenteur durent 15 % moins longtemps. | Après 1 s, invoque une ligne de 8 vrais crocs d'Evoker au sol. Chaque morsure inflige 6 PV magiques selon les règles natives. La ligne s'arrête devant un mur. | 45 s |
+| `ghast` | Contrôle de direction en chute amélioré de 25 %. | Après 0,5 s, tire une vraie boule de feu de Ghast. Son explosion détruit le terrain et peut allumer des incendies lorsque les dégâts des mobs au terrain sont activés. | 45 s |
+| `happy_ghast` | Réduit les dégâts de chute de 20 %. | Appui suspendu à 4 blocs, rayon 1,5 pendant 6 s. Maintenir Maj pour stabiliser sa descente, 4 personnes. Ne réarme pas les bonds. | 60 s |
+| `iron_golem` | Réduit le recul de 20 %. | Interposition mobile de 4 s devant 4 personnes : impacts frontaux réduits jusqu'à 40 %. Attaquer interrompt ; garde commune de 6 s sur 20 s, puis 25 %. | 30 s |
+| `piglin_brute` | Réduit le recul de mêlée de 20 %, sans armure d'or requise. | Avec une vraie arme, parade frontale de 1 s jusqu'à 40 %, puis riposte de 2 PV au plus sous 3 s. Budget de garde partagé. | 25 s |
+| `ravager` | Réduit le recul de 25 % au sol. | Large charge annoncée de 6 blocs, 3 ennemis, jusqu'à 5 PV chacun. Repousse les monstres et garde le terrain intact. | 45 s |
+| `shulker` | Réduit les dégâts de chute de 20 % ; Lévitation dure 20 % moins longtemps. | Tire un projectile de Shulker guidé vers l'ennemi visé à 16 blocs. Son impact inflige les dégâts natifs et 10 s de Lévitation. | 45 s |
+| `skeleton_horse` | Petite économie d'air de 15 % à cheval. | Traverse une surface d'eau pendant 6 s sur 10 blocs au plus, à pied ou sur une monture réelle. Ne permet pas de s'arrêter sur l'eau. | 40 s |
+| `sniffer` | Indique les besoins des cultures connues observées : lumière, eau ou sol. | Repère jusqu'à 4 ressources naturelles déjà connues à 24 blocs pendant 12 s. Inclut les sites d'archéologie connus ; seulement dans les chunks chargés. | 30 s |
+| `vex` | Prévisualise le placement du bloc tenu ; Lenteur dure 20 % moins longtemps. | Une action de placement ou de minage avec les vrais blocs et outils, à portée normale +2. Maj pour miner ; rang de construction ou minage acquis requis. | 15 s |
+| `witch` | Les potions bénéfiques bues durent 10 % plus longtemps, sans niveau ni soin instantané renforcé. | Consomme la potion bénéfique tenue et rend le flacon. Brume mobile à 4 blocs, rayon 1,5, pendant 6 s : une dose native, une personne consentante. | 30 s |
+| `wither_skeleton` | L'effet Wither reçu dure 20 % moins longtemps. | Le prochain vrai coup de mêlée sous 6 s inflige l'effet Wither natif pendant 10 s. Les immunités de la cible s'appliquent. | 30 s |
+| `zombie_nautilus` | Réduit le recul de 25 % dans l'eau. | Ouvre 6 s un point d'extraction aquatique de rayon 2. Maintenir Maj pour rejoindre une eau libre plus haute à 6 blocs ; 4 personnes, budget de mouvement personnel. | 45 s |
+| `ender_dragon` | Contrôle aérien amélioré de 25 % ; impacts contre une paroi et projectiles en vol réduits de 20 %. | Après 1,5 s, projette une boule de souffle draconique. À l'impact, elle forme un véritable nuage de dégâts instantanés II pendant 30 s, qui s'élargit de 3 à 7 blocs de rayon. | 90 s |
+| `warden` | Localise les vibrations de créatures en mouvement à 8 blocs, sans révélation de l'Atlas. | Après 1,7 s, frappe la créature visée à 15 blocs avec l'onde sonique native : 10 PV, recul et passage à travers les murs, armures et boucliers. Un coup reçu interrompt la préparation. | 90 s |
+| `wither` | Réduit le recul de 25 % sous 30 % de vie. | Après 1,5 s, tire 3 crânes de Wither natifs : dégâts, explosions et effet Wither selon la difficulté. Les victimes tuées par l'impact direct rendent de la vie au tireur, comme pour le Wither. | 100 s |
diff --git a/docs/spawn-eggs-refonte-v2.md b/docs/spawn-eggs-refonte-v2.md
new file mode 100644
index 0000000..a620646
--- /dev/null
+++ b/docs/spawn-eggs-refonte-v2.md
@@ -0,0 +1,582 @@
+# Spawn eggs — retrouver des pouvoirs désirables, seul et en groupe
+
+**Proposition v2 du 14 septembre 2026 — document de conception conservé.**
+
+La demande ultérieure du créateur étend l’implémentation aux **88 œufs**.
+Le [ticket beta.032](spawn-eggs-v2-beta032.md) suit cette réalisation et le
+[catalogue chiffré](spawn-eggs-v2-catalogue-beta032.md) fixe les valeurs des
+76 espèces qui n’avaient encore que des orientations. Les sections ci-dessous
+conservent la proposition initiale ; les limites livrées figurent dans le ticket.
+Ticket documentaire local **FAM-DESIGN-02**, branche de travail créée
+`codex/spawn-eggs-refonte-v2`. Référence de travail : sources locales beta.029,
+Minecraft fixé par le dépôt à **26.3-pre-2**.
+
+La demande est de retrouver le plaisir des anciens œufs de la 26.2 : des
+pouvoirs que l’on a envie d’utiliser souvent, sans rendre les potions, les
+équipements et la progression superflus. Elle inclut les combinaisons entre
+joueurs : deux pouvoirs doivent pouvoir produire ensemble une solution que
+personne n’avait prévue en écrivant leurs fiches.
+
+**Je propose de donner à chaque œuf une manière de jouer : un passif perceptible
+dans une activité courante, un geste actif assez disponible, et des effets sur
+le monde avec lesquels les autres joueurs peuvent interagir.** On garde les
+88 espèces et leurs raretés actuelles. On revoit d’abord leurs usages ; les
+chiffres viennent ensuite.
+
+Cette v2 propose une nouvelle direction après la
+[v1](companion-powers-balance-proposal.md). La v1 décrit la base de beta.019 ;
+les changements de [beta.020](familiar-interactions-beta020.md) restent la
+référence pour les interactions actuelles. L’état livré est documenté séparément dans le ticket beta.032.
+Cette proposition n’autorise pas une mise à jour de Prism ; le contrat de migration
+additive des budgets figure dans le ticket d’implémentation.
+
+## 1. Le problème à résoudre
+
+Le retour du joueur porte sur la fréquence et le plaisir d’utilisation. Les
+sources permettent de comprendre ce ressenti, mais ne fournissent pas de
+mesures de parties permettant de classer objectivement les 88 œufs.
+
+| Exemple observé dans les définitions actuelles | Conséquence probable en partie |
+|---|---|
+| Lapin : +10 % de vitesse accroupie ; bond, recharge de 25 s. | Le passif accompagne peu le déplacement ordinaire, et le petit geste arrive rarement. |
+| Chat : réduction des dégâts de chute ; actif limité aux Creepers et Phantoms, 45 s. | Un compagnon d’exploration peut passer un combat entier sans actif utilisable. |
+| Mouton : ralentissement du gel ; laine consommée pour amortir un atterrissage, 45 s. | Deux usages très circonstanciels pour un animal que l’on voudrait aimer au quotidien. |
+| Squelette : précision ; prochaine flèche qui marque sa cible pour le tireur, 35 s. | Un tir réussi produit surtout une information sur une cible que l’on venait de viser. |
+| Poisson tropical : contraste du récif ; recherche de variantes connues. | Une bonne idée de collection devient la totalité du rôle du familier. |
+
+Le catalogue contient aussi de bonnes bases : déplacement, rassemblement
+d’objets, interposition, bulles d’air, construction. Le problème n’est donc pas
+que tous les effets sont mauvais. Trop d’œufs ont une identité construite
+autour d’une exception, et trop de recharges longues protègent des effets modestes.
+
+Dans la 26.2, le lapin accordait **Vitesse II et Sauts améliorés II**, entretenus
+en continu, avec un bond toutes les **4 secondes**. Cela explique la sensation
+de transformation immédiate évoquée par le joueur. Ces noms de niveaux ne
+signifient pas littéralement « toutes les valeurs sont multipliées par deux ».
+En revanche, des effets d’alchimie continus fournis par un œuf concurrencent
+directement la préparation et la consommation de potions.
+
+Deux dimensions doivent être séparées : **à quelle fréquence le pouvoir sert**
+et **quelle part du jeu il remplace**. Un petit bond fréquent peut être plaisant
+sans donner une vitesse permanente extraordinaire. Une immunité permanente
+peut au contraire supprimer une activité entière, même sans bouton à presser.
+
+## 2. Le contrat de plaisir d’un œuf
+
+Un œuf doit pouvoir se présenter ainsi : « je le prends pour faire ceci, et
+j’utilise son pouvoir quand cela arrive ». Cette phrase doit fonctionner sans
+énumérer trois conditions rares.
+
+- **Un passif que l’on ressent.** Il améliore un geste courant de son activité :
+ se réceptionner, viser, ramasser, transporter, manger, préparer un chantier.
+ On préfère un comportement lisible à plusieurs microbonus invisibles.
+- **Un actif utile sur demande.** Le joueur choisit sa cible, sa direction ou
+ le moment. Un pouvoir ordinaire doit pouvoir servir plusieurs fois pendant
+ une traversée, un combat ou une tâche, selon sa nature.
+- **Au moins deux situations d’usage.** Par exemple : le bond sert à franchir
+ un obstacle et à se replacer ; la rafale dégage un monstre et transporte un
+ nuage ; le coussin sécurise un chantier et une évacuation.
+- **Une contribution possible à plusieurs.** Elle peut passer par une cible,
+ un objet, une trajectoire ou un lieu. Elle n’exige pas forcément un buff allié.
+- **Une valeur en solo.** Le joueur peut exploiter lui-même ce qu’il prépare,
+ avec ses outils, ses armes ou les possibilités du terrain.
+
+Un familier agricole peut rester spécialisé en agriculture. Il doit alors
+servir pendant une séance ordinaire de culture, sans attendre une plante ou
+un événement rarissime. L’objectif n’est pas que chaque œuf serve partout.
+
+Les cinq raretés restent **commun, peu commun, rare, extraordinaire, légendaire**.
+Un légendaire change l’échelle ou la mise en scène d’une intervention ; il ne
+remplace pas automatiquement un commun. La taille visuelle du familier,
+son nom et les futurs rangs ne multiplient pas sa puissance.
+
+## 3. Familiers, potions et équipement : garder trois raisons de préparer sa sortie
+
+| Système | Ce qu’il doit apporter | Exemple de complémentarité |
+|---|---|---|
+| Familier | Un geste ou une possibilité réutilisable, bornée dans le temps ou l’espace. | Le lapin corrige un franchissement ; la poule permet une réception contrôlée. |
+| Potion | Un avantage soutenu ou un secours consommable, choisi pour une séquence entière. | Vitesse pour la traversée ; saut pour un terrain accidenté ; respiration pour travailler sous l’eau. |
+| Équipement et enchantements | La capacité durable à pratiquer une activité. | Outil adapté, protection, mobilité et efficacité pendant toute la sortie. |
+| Compétences Sanctuary | Les capacités du personnage et les actions acquises. | Vie, faim, air, inventaire, Nage, minage et construction groupés. |
+
+Les œufs ordinaires n’accordent plus gratuitement et en continu les effets
+complets de Vitesse, Saut, Force, Régénération, Résistance au feu ou Respiration
+aquatique. Un petit avantage peut recouvrir une partie de ces usages, mais
+l’alchimie doit rester sensiblement avantageuse quand on compare une activité
+complète. Une recharge, à elle seule, ne prouve pas cet équilibre.
+
+**Règle proposée pour un même bonus numérique :** parmi les contributions de
+familiers, prendre la plus forte. Si une potion fournit le même type de bonus,
+prendre la plus forte des deux contributions, sans addition ni multiplication.
+La potion continue de s’écouler normalement ; le familier ne l’efface jamais.
+Les règles natives de l’équipement et des potions restent intactes entre elles.
+Le soin instantané et les impulsions de déplacement suivent leurs règles
+spécifiques ci-dessous, pas cette règle de vitesse.
+
+Cela ne bloque pas les effets différents : **une potion de vitesse et le
+contrôle aérien du lapin** peuvent être utiles ensemble. L’objectif est que le
+familier aide à employer la potion, et que la potion améliore encore la sortie.
+
+### Le lapin comme premier cas concret
+
+Proposition de départ : un meilleur contrôle de direction en l’air, sans hausse
+de vitesse maximale ni de hauteur du saut ordinaire ; un bond dirigé de
+**3 blocs horizontalement ou 2 blocs verticalement**, toutes les **8 secondes**.
+Ces deux maxima décrivent des trajectoires alternatives, pas un déplacement
+diagonal qui cumule les deux. Pas de second bond dans le même vol.
+
+Le lapin devient agréable dans le relief, en construction et pour esquiver.
+Il ne suffit pas de tenir le bouton pour courir plus vite : le bond exige un
+nouvel appui volontaire et conserve sa récupération. Son trajet propre reste
+borné même sous potion ; il ne réduit pas les possibilités natives de la potion
+lors des sauts ordinaires. La réception redevient ensuite celle du jeu normal.
+
+Le test décisif compare **sans œuf, lapin seul, potion seule, lapin + potion**,
+à progression et équipement identiques. Sur terrain plat, viser au plus **8 %**
+de temps gagné avec le lapin seul en usage répété. Ce seuil est une cible de
+prototype à mesurer, pas un résultat. Si le bond devient le moyen optimal de
+parcourir toutes les routes, ajuster sa trajectoire ou son rythme avant de
+supprimer son intérêt dans le relief.
+
+## 4. Des interactions communes pour que les joueurs inventent les combinaisons
+
+**Aucun bonus ne se déclenche parce que le groupe possède deux espèces précises.**
+Une rafale doit pouvoir déplacer un nuage parce que c’est un nuage mobile ;
+une attaque de zone doit profiter d’un regroupement parce que les cibles sont
+réunies. Le code futur ne devrait pas demander « y a-t-il un Breeze et une
+Sorcière dans l’équipe ? ».
+
+Voici les propriétés communes proposées. Ce sont des règles à construire et
+à éprouver ; la beta actuelle ne possède pas encore ce contrat général.
+
+| Ce qu’un pouvoir produit | Ce que les autres peuvent en faire | Limite structurante |
+|---|---|---|
+| Une impulsion, une attraction ou une poussée | Déplacer une cible, une pile au sol ou un effet explicitement mobile. | Collision, portée et résistance de la cible ; aucune énergie accumulée sans plafond. |
+| Une zone mobile : encre, poussière, brume de potion | La déplacer avec une force, y entrer ou en sortir, la dissiper. | Déplacer conserve sa durée restante, sa quantité et son propriétaire. |
+| Un appui ou une protection temporaire | Tirer, canaliser, réceptionner ou traverser pendant la fenêtre ouverte. | Durée et capacité finies ; l’appui magique ne réarme pas un autre pouvoir aérien. |
+| Un repère partagé | Orienter un tir manuel, prévenir un danger, guider une équipe. | Aucun tir automatique, bonus de dégâts universel ou découverte gratuite dans l’Atlas. |
+| Un projectile matériel compatible | Modifier son angle avant l’impact, ouvrir ou protéger une ligne de tir. | Une seule déviation de familier par projectile au prototype ; pas de duplication ni de gain de dégâts. |
+| Un transfert d’objets existants | Faire une chaîne de collecte, de livraison et de rangement. | Même stock avant/après, inventaires accessibles, aucune capacité supplémentaire. |
+
+Une zone posée au sol comme les crocs de l’Évocateur reste ancrée. Une brume
+peut dériver. Un rayon instantané ne devient pas un projectile déviable.
+Ces différences doivent se comprendre visuellement. Les projectiles et effets
+vanilla compatibles seront recensés dans le ticket technique ; on ne prétend
+pas que tout Minecraft est déjà manipulable de cette façon.
+
+Déplacer un effet lui conserve aussi son attribution offensive, ses droits
+d’accès et la mémoire des bénéficiaires déjà servis. Traverser une brume,
+sortir, puis la recevoir à nouveau ne donne pas une seconde dose.
+
+### Des exemples de résultats, pas des recettes de combos
+
+| Situation | Combinaison possible | Pourquoi elle fonctionne, et ce qui peut la faire échouer |
+|---|---|---|
+| Un groupe de monstres disperse les joueurs. | Breeze rabat des cibles vers un angle ; Creeper prépare sa détonation à cet endroit. | La géométrie améliore l’attaque de zone. Une rafale mal orientée peut aussi écarter les cibles ; aucun multiplicateur secret. |
+| Un allié blessé reste de l’autre côté d’un couloir dangereux. | Sorcière pose une brume de potion consommée ; Breeze la pousse jusqu’à lui. | La dose réelle voyage. Un obstacle bloque la brume ; durée et nombre de doses n’augmentent pas. |
+| Il faut descendre d’un chantier avec du matériel. | Mouton prépare une réception ; Lapin ajuste le départ ; Poule permet à son porteur de corriger la descente. | Chacun intervient sur une phase différente. Un joueur sans ces œufs peut aussi viser le coussin. Aucun appui ne crée un vol renouvelable. |
+| Un rayon demande de rester exposé. | Gardien vise ; Golem de fer s’interpose ; Squelette renseigne sur la cible. | Protection, temps de préparation et information se complètent. La ligne de vue et l’esquive restent nécessaires pour le rayon. |
+| Une récolte encombre le terrain. | Abeille facilite les gestes sur les cultures ; Allay rassemble les vrais objets ; Âne les range. | Le groupe gagne sur les manipulations. Les quantités ne sont pas multipliées et les outils restent nécessaires. |
+| L’équipe doit extraire un plongeur. | Dauphin ouvre un passage rapide ; Nautile crée une halte d’air ; Tortue couvre la retraite. | Mobilité, air et protection servent à des moments différents. Le trajet long demande toujours une préparation aquatique. |
+| Une salle dangereuse est encore mal comprise. | Chauve-souris partage des présences ; Araignée gagne une position ; Poulpe couvre une approche. | Information, angle et dissimulation produisent un plan. Le sonar n’accorde ni autorisation d’accès ni attaque à travers la roche. |
+| Un petit camp doit être installé sous pression. | Shulker offre un appui ; Vex place les vrais blocs ; Mule livre le matériel. | La construction stabilise une situation temporaire. Sans stock, portée achetée et droits de construction, l’appui expire sans solution durable. |
+
+Une bonne surprise peut aussi employer **un seul œuf et un objet ordinaire** :
+une rafale dirige une pile vers un système de collecte déjà construit ; une
+langue récupère un outil tombé ; un abri permet à un joueur de boire sa potion.
+Ces usages comptent autant que les compositions à trois familiers.
+
+On conserve une combinaison plus efficace que prévu si elle exige des choix,
+de la coordination ou une préparation réelle. On corrige les boucles qui
+créent des ressources, retirent toute possibilité de réponse ou rendent une
+capacité permanente. **« Très fort ensemble » n’est pas, à lui seul, un bug.**
+
+## 5. Limiter les boucles sans étouffer la coopération
+
+Les valeurs de cette section sont des hypothèses de prototype. Elles
+remplaceraient certaines règles de la v1 après essais, notamment le verrou de
+soin lié à la recharge du premier pouvoir reçu.
+
+### Rythme et changement d’œuf
+
+Un seul œuf équipé. Garder les **10 secondes de stabilisation** et la recharge
+commune au joueur déjà présentes ; pendant la stabilisation, aucun passif ni
+actif du nouvel œuf. Déséquiper annule ses améliorations temporaires. Les
+échéances continuent après échange, mort, reconnexion et prestige.
+
+Les gestes simples visent **8–20 s**, les interventions ordinaires **20–40 s**,
+les protections ou déplacements de groupe importants **45–75 s**, les grands
+moments **90–120 s**. Ce rythme dépend de l’impact réel, pas seulement de la
+rareté. Une intervention spectaculaire mais sans avantage majeur peut revenir
+plus vite. Les chiffres des douze prototypes ci-dessous priment.
+
+Pas de mana ni de coût de faim uniforme. Les gestes courants sont gratuits ;
+les potions, matériaux, munitions et soins consomment leur ressource explicite.
+Une action sans cible ou sans arrivée valide explique le refus, sans coût.
+Une attaque réellement lancée mais esquivée reste dépensée.
+
+### Soin, air et protection
+
+Une récupération globale trop longue peut rendre un petit soin nuisible :
+il empêche ensuite un allié de mieux aider. Proposer plutôt des **quantités
+maximales reçues sur une fenêtre glissante**, visibles au joueur.
+
+- **Soin et absorption produits par des familiers :** budget commun par
+ bénéficiaire de **20 % de sa vie maximale sur 20 s**. Une absorption réserve
+ sa part à l’octroi, même si elle expire sans être utilisée. Les deux effets
+ ne permettent donc pas de contourner le plafond en alternant. À 3 cœurs,
+ le budget vaut 1,2 PV ; à 10 cœurs, 4 PV. Le serveur peut conserver ces
+ fractions, sans arrondi favorable répété.
+- **Air produit ou économisé par un actif :** au plus **un tiers de la capacité
+ personnelle sur 30 s**, partagé entre tous les familiers. Compter aussi les
+ unités non consommées pendant une bulle. Une nouvelle cloche ne remet pas
+ le budget à zéro. Les petits passifs respiratoires ne rendent pas d’air.
+- **Réduction des dégâts par familiers :** retenir la plus forte et plafonner
+ à **25 %** hors garde ponctuelle. Une garde peut atteindre **40 %** pendant
+ au plus **6 s reçues sur 20 s**, tous protecteurs confondus ; ensuite elle
+ ne donne plus que 25 % jusqu’à récupération. Ce délai est indépendant du
+ soin : se protéger doit permettre de recevoir un secours. Les amortissements
+ d’un seul atterrissage ont leur limite propre et n’ouvrent pas cette garde.
+- Une potion **réellement consommée** reste une ressource d’alchimie et suit
+ ses règles natives. La déplacer ne la multiplie pas. Tout supplément créé
+ par l’œuf compte dans le budget de familier correspondant.
+
+Un petit soin n’efface donc pas la possibilité d’un complément ; plusieurs
+soigneurs peuvent servir davantage de personnes. Ils ne rendent pas le même
+personnage invulnérable. Les interfaces parlent de « récupération » et de
+quantité encore recevable ; elles n’affichent pas une comptabilité permanente.
+
+Un amortissement de chute concerne un événement ponctuel, pas une réduction
+de tous les coups. Il ne consomme pas le soin disponible : un joueur qui a
+survécu à une mauvaise réception doit encore pouvoir être soigné. Les
+amortissements prennent le plus fort et un même bénéficiaire ne reçoit qu’un
+amortissement actif de familier par atterrissage.
+
+### Déplacement, contrôles et taille du groupe
+
+- Le lapin, la rafale et les appuis temporaires peuvent servir dans une même
+ traversée. Pour le prototype, tous les déplacements magiques successifs
+ d’un bénéficiaire partagent une enveloppe de **8 blocs horizontaux et
+ 4 blocs de gain vertical** avant son prochain appui durable. Ce sont des
+ distances cumulées dues aux pouvoirs, pas la distance totale parcourue.
+ Dépasser le reliquat réduit la prochaine impulsion et sa prévisualisation.
+- Un appui durable est une surface réelle autorisée, y compris un bloc
+ légitimement construit ; un autre joueur, un familier ou une plateforme
+ magique ne réinitialise pas cette enveloppe. Pour les pouvoirs montés,
+ la monture doit elle-même retrouver une surface réelle ; monter dessus en
+ plein vol ne suffit pas. Le mouvement natif conserve ses règles, mais ne
+ rend pas de charge magique pendant le même vol. Les pénalités normales de
+ portage et de piles de joueurs restent applicables.
+- Sur un monstre ordinaire, jusqu’à **3 s de contrôle dur cumulé**, puis
+ **6 s de résistance aux contrôles de familiers** après la fin du contrôle.
+ Les ralentissements de familiers prennent le plus fort, sans multiplication.
+ Boss et créatures non déplaçables gardent leurs immunités propres.
+- En PvP, conserver la limite actuelle proposée de **2 PV additionnels par
+ activation et par cible**, tous impacts compris. Pas de contrôle dur,
+ ralentissement au plus 20 % pendant 2 s, puis 6 s de récupération. Les
+ poussées offensives restent désactivées au premier prototype.
+- Un actif collectif ordinaire sert au plus **4 bénéficiaires, propriétaire
+ compris**, sur toute sa durée. Sortir puis entrer ne libère pas une place.
+ Viser un allié le rend prioritaire ; ensuite propriétaire puis alliés les
+ plus proches. Les effets de même nature prennent le plus fort. Le nombre
+ final des grands actifs doit être fixé dans leur ticket, sans cible illimitée.
+
+Les aides suivent le réglage personnel existant. Pour être déplacé, il faut
+en plus entrer volontairement dans le passage ou accepter l’interaction ;
+accepter un soin n’autorise pas un allié à vous lancer dans le vide.
+La composition de faction ne remplace pas ce choix.
+
+Ces limites doivent rester en arrière-plan. Si elles réclament plus
+d’explications que le geste lui-même ou empêchent un secours évident, le
+prototype doit les simplifier. Une combinaison de protection et de soin est
+souhaitable ; une rotation qui supprime durablement tout danger ne l’est pas.
+
+### Objets, progression et monde
+
+Le familier visible reste invulnérable, sans collision et sans production ni
+combat autonome. Un bouclier est un effet limité du pouvoir, jamais un animal
+invincible utilisé comme mur permanent.
+
+Les nouvelles propositions agricoles et logistiques économisent des gestes,
+pas des ressources. Plantation et construction prélèvent le stock réel ; les
+outils requis s’usent. Une action groupée reste bornée par les achats du joueur
+qui l’exécute, sans second groupe ajouté au vein mining/building. Un actif peut
+être utile dès le rang initial sur une seule cible.
+
+Les bonus de rendement hérités de la v1 sont à retirer ou remplacer par du
+confort lors du ticket de l’espèce concernée ; ils restent actuels d’ici là.
+Aucun nouveau multiplicateur d’XP, de minerai, de monnaie ou de butin n’est
+proposé. Aucun bonus de capacité, de Nage ou de cases d’inventaire gratuites.
+
+Les perceptions et téléportations peuvent continuer à traverser les parois
+selon beta.020, avec arrivée libre et permissions applicables. On ne réintroduit
+pas silencieusement les anciennes interdictions de la v1. Détecter ne débloque
+pas l’Atlas ; toutes les recherches restent dans les chunks chargés.
+
+## 6. Douze prototypes assez précis pour commencer les essais
+
+Ces fiches sont des **valeurs de départ proposées**, pas douze changements
+déjà programmés. Un cœur = 2 PV. Portées mesurées depuis le joueur ; durées
+à cadence normale du serveur. Les budgets de la section 5 s’appliquent aussi
+à ces fiches. Aucun œuf n’ajoute de touche : **G**, configurable, et le regard.
+
+| Œuf | Passif proposé | Actif à prototyper | Recharge et coût |
+|---|---|---|---|
+| Lapin | +25 % de correction directionnelle en l’air, sans vitesse ni hauteur maximales ajoutées. | Bond : jusqu’à 3 blocs horizontaux ou 2 verticaux ; un seul par vol. | **8 s**, gratuit. |
+| Poule | Réception : +1 bloc de chute tolérée, comme aujourd’hui. | Battement : freine la descente pendant **3 s**, efface la chute accumulée au déclenchement selon beta.020 ; aucune montée. | **15 s**, gratuit ; ne réarme pas le bond. |
+| Mouton | Pas feutrés : −15 % de dégâts de chute, applicable au quotidien. | Coussin : place une zone de réception de **2 × 2 blocs** sur un sol visé à **6 blocs**, durant **8 s** ; réduit de moitié un atterrissage, au plus **4 PV** par personne, quatre personnes distinctes. Chaque personne en bénéficie une fois ; pas de cumul avec un autre amortissement. | **30 s**, **1 laine**, non récupérable. |
+| Squelette | Main stable : conserver le gain actuel de précision, à vérifier en mouvement réel. | Tir repère : la prochaine flèche sous **8 s** partage la position de sa cible pendant **6 s** avec les alliés à **16 blocs** du tireur au moment du tir ; quatre lecteurs maximum. Dégâts natifs, sans amplification. | **15 s**, flèche et usure normales. |
+| Âne | −10 % d’épuisement du sprint, comme aujourd’hui. | Déchargement : transfère jusqu’à **18 piles** vers un conteneur autorisé à portée native, uniquement les types déjà présents. Hotbar et équipement exclus. Retirer la restriction au seul coffre lorsque le conteneur permet un transfert sûr. | **10 s**, aucun objet créé. |
+| Allay | Aimant de proximité à **3 blocs** pour les types explicitement choisis ; cases ouvertes seulement. | Rassemblement : attire au plus **16 piles au sol**, visibles à **8 blocs**, vers le point de dépôt visé à **4 blocs** ; aucun transfert automatique dans un conteneur. | **12 s**, gratuit ; objets réservés exclus. |
+| Breeze | +25 % de contrôle de direction en chute ; ne ralentit pas la gravité. | Rafale conique de **6 blocs** : pousse au plus **4 monstres ordinaires** de **2 blocs**, déplace les objets et zones mobiles compatibles. Viser vers le bas depuis un appui donne au porteur une impulsion dans l’enveloppe aérienne. Une seule modalité par activation. | **15 s**, gratuit ; pas de dégâts. |
+| Creeper | Alerte directionnelle à **8 blocs** lorsqu’un monstre proche amorce une attaque explosive. | Détonation : après **0,8 s** d’annonce, **4 PV** par cible dans **3 blocs**, au plus **4 ennemis**, sans dégât au terrain. Interrompue si le porteur subit des dégâts pendant l’annonce ; pas de remboursement. | **25 s**, gratuit ; plafond PvP applicable. |
+| Axolotl | Une nourriture consommée normalement bénéficie de la même réduction de temps de consommation sur terre et dans l’eau : essai à **10 %**. | Secours : sur soi ou l’allié visé à **6 blocs**, restaure jusqu’à **20 % de sa vie maximale sur 4 s**, sous budget. Les dégâts reçus ou une attaque du bénéficiaire interrompent le soin. | **30 s**, **1 point de nourriture** du lanceur ; refus si aucun soin possible. |
+| Nautile | L’air diminue **10 %** moins vite, sans condition d’immobilité. | Cloche : sphère de **2 blocs de rayon**, centrée sur le point d’eau visé à **6 blocs**, durant **6 s**, quatre bénéficiaires distincts. Suspend la consommation d’air seulement tant que chacun possède son budget d’air. Ne crée pas de bloc d’air. | **45 s**, gratuit. |
+| Golem de fer | −20 % de recul subi, sans condition de première attaque. | Interposition : devant soi, pendant **4 s**, protège jusqu’à **4 personnes, porteur compris**, derrière le plan. Réduit de **40 %** les impacts venant de face, y compris projectiles, tant que leur récupération de garde le permet. Le porteur peut marcher, attaquer termine la garde. | **30 s**, gratuit ; aucun bloc ni mur invincible. |
+| Sorcière | +10 % de durée des effets bénéfiques d’une potion bue normalement ; aucun niveau augmenté ni amplification du soin instantané. | Brume : consomme la potion buvable sélectionnée et crée une zone mobile de **1,5 bloc de rayon**, à **4 blocs** au plus, durant **6 s**. Une seule personne consentante reçoit une seule dose aux effets natifs de la potion consommée ; ensuite la brume disparaît. Le propriétaire peut la prendre. | **30 s**, **1 potion**, fiole vide rendue. Potions bénéfiques uniquement au prototype ; aucune copie de dose. |
+
+La brume à dose unique permet déjà le transport coopératif d’une potion, mais
+les potions jetables gardent leur intérêt de portée et de zone sans cette
+recharge. Une éventuelle version à plusieurs doses devra **consommer autant
+de potions**, et non transformer gratuitement une potion en quatre.
+
+Le Creeper a un passif encore étroit : son actif généraliste doit porter son
+usage courant. C’est un point d’attention du prototype. Si l’alerte n’est pas
+ressentie pendant les essais, élargir la détection aux dangers imminents lisibles
+avant d’augmenter ses dégâts.
+
+## 7. Direction proposée pour les 88 œufs
+
+Ce catalogue couvre une fois chaque identifiant de `CompanionType`. Il conserve
+les **22 communs, 22 peu communs, 25 rares, 16 extraordinaires et 3 légendaires**.
+Les 25 anciennes variantes ItsAlive sont hors périmètre du pack actuel.
+
+**Les douze fiches précédentes précisent leur ligne ci-dessous. Les 76 autres
+lignes sont des concepts**, à chiffrer et tester avant implémentation. Une
+mention « bref » ou « quelques » ne constitue donc pas un contrat technique.
+Elles n’autorisent ni buff permanent gratuit ni exception aux règles communes.
+Les anciennes particularités peuvent subsister comme détails secondaires,
+mais ne doivent plus porter seules l’intérêt de l’œuf.
+
+### Communs — des gestes qui servent dès le début
+
+| Œuf | Passif envisagé | Actif et ouverture vers les autres |
+|---|---|---|
+| Chauve-souris (`bat`) | Adaptation visuelle douce dans l’obscurité, sans immobilité exigée ; conserve le besoin d’éclairage. | Écho partagé de présences proches : reconnaître une salle ou retrouver le groupe. |
+| Chat (`cat`) | Réceptions moins douloureuses. | Feulement sur les petits monstres ordinaires : ouvre un passage ou libère brièvement un allié ; Creepers et Phantoms gardent une réaction distinctive. |
+| Poule (`chicken`) | Un bloc de chute tolérée en plus. | Battement contrôlé pour choisir sa réception, notamment sur un appui préparé par un autre joueur. |
+| Morue (`cod`) | Petite économie d’air. | Bulle de secours sur soi ou un plongeur proche ; sert pendant toute activité aquatique. |
+| Vache (`cow`) | Le lait bu conserve les effets bénéfiques, comme aujourd’hui. | Décontamination partagée avec du vrai lait : retire un effet négatif pris en charge, sans produire de seau. |
+| Âne (`donkey`) | Sprint moins épuisant. | Déchargement rapide du stock vers un conteneur : termine les chaînes de collecte du groupe. |
+| Renard (`fox`) | Meilleur contrôle directionnel accroupi, sans voisinage de végétation requis. | Esquive au sol avec récupération d’un objet sur le trajet : fuite, ravitaillement, récupération d’outil. |
+| Grenouille (`frog`) | Entrer et sortir d’une berge gêne moins le mouvement, sans débloquer Nage. | Langue sur un objet, un petit monstre ou un allié consentant : récupération et repositionnement. |
+| Cheval (`horse`) | Moins d’épuisement lors des déplacements terrestres, à pied ou monté. | Ruée terrestre dirigée ; facilite une traversée ou une prise de position, sans Vitesse permanente. |
+| Lama (`llama`) | Laisses plus tolérantes, avec alerte avant rupture. | Crachat qui gêne brièvement une attaque de monstre : protéger une récolte, un troupeau ou une incantation. |
+| Mule (`mule`) | Meilleure stabilité sous charge, y compris pendant le portage. | Livraison d’une sélection de piles à une monture ou un joueur consentant : ravitailler un chantier sans ouvrir une seconde réserve. |
+| Cochon (`pig`) | Petit bonus de saturation des nourritures ordinaires, sans génération de faim. | Fouille des ressources de surface connues proches, puis repères partageables : nourriture, matériaux et cultures. |
+| Lapin (`rabbit`) | Contrôle aérien amélioré. | Bond fréquent pour franchir et se replacer ; exploite les passages et réceptions préparés. |
+| Saumon (`salmon`) | Courants moins gênants. | Impulsion aquatique directionnelle avec sillage bref pour les nageurs volontaires ; montée ou traversée. |
+| Mouton (`sheep`) | Réceptions amorties. | Coussin consommant une laine : point d’arrivée commun sur un chantier ou une fuite. |
+| Squelette (`skeleton`) | Précision conservée en déplacement lent. | Tir repère partagé : donne au groupe une information gagnée par un vrai tir. |
+| Slime (`slime`) | Chutes légèrement amorties, adhérences moins pénalisantes. | Rebond au sol orientable : déplacer son porteur ou offrir un tremplin à usage borné. |
+| Araignée (`spider`) | Échelles et lianes plus agréables à parcourir. | Accroche courte à une paroi : observer, construire ou préparer l’arrivée d’autres joueurs ; pas d’escalade illimitée. |
+| Poulpe (`squid`) | Vision sous-marine plus lisible. | Nuage d’encre mobile sur terre comme dans l’eau : couper brièvement l’acquisition visuelle de monstres ordinaires, sans annuler un projectile déjà parti. |
+| Têtard (`tadpole`) | Reprise d’air plus rapide à la surface. | Marque une issue proche réellement praticable, berge ou passage terrestre : guide de retour local pour le groupe. |
+| Poisson tropical (`tropical_fish`) | Objets au sol plus lisibles sous l’eau, au-delà des seuls coraux. | Balise colorée placée localement, visible dans la pénombre et l’eau : organiser une plongée ou un chantier. |
+| Zombie (`zombie`) | Recul réduit lorsque la vie baisse. | Second souffle hors échange de coups, payé en nourriture : tenir jusqu’au retour ou au soin d’un allié. |
+
+### Peu communs — des spécialités assez larges pour une séance complète
+
+| Œuf | Passif envisagé | Actif et ouverture vers les autres |
+|---|---|---|
+| Tatou (`armadillo`) | Garde accroupie efficace sans attente de cinq secondes. | Mise en boule courte : encaisser un passage dangereux et offrir un point de regroupement. |
+| Abeille (`bee`) | Récolter/replanter une culture mûre en un geste quand graine et outil sont disponibles, au rang acheté. | Pollinisation dirigée avec poudre d’os réelle sur le groupe autorisé ; alimente une chaîne de récolte sans rendement ajouté. |
+| Dromadaire (`camel`) | Sprint prolongé moins épuisant sur tous les sols. | Longue enjambée terrestre sur obstacle bas ; passager réellement monté inclus. |
+| Araignée venimeuse (`cave_spider`) | Les poisons reçus durent moins longtemps. | Attaque enduite d’un poison bref ; le contrôle d’un allié aide à placer le coup, sans bonus spécial de combinaison. |
+| Golem de cuivre (`copper_golem`) | Aperçu des cases disponibles et des filtres du conteneur visé autorisé. | Répartit une sélection réelle entre quelques conteneurs proches selon leurs types déjà présents : débouché des chaînes logistiques. |
+| Dauphin (`dolphin`) | Meilleur contrôle directionnel dans l’eau, sans vitesse de nage gratuite. | Sillage de nage pour le porteur et ceux qui le suivent ; exige Nage pour la vitesse rapide. |
+| Noyé (`drowned`) | Travail sous l’eau un peu moins pénalisé. | Ancrage volontaire au support pour soi et proches : miner, poser ou viser sans dériver. |
+| Poulpe luisant (`glow_squid`) | Meilleure lecture locale des surfaces sombres, sans lumière de monde. | Balise lumineuse temporaire partageable, pouvant suivre un objet balisé ; exploration, récupération, chantier. |
+| Chèvre (`goat`) | Meilleure stabilité sur les corniches et après réception. | Charge courte qui décale une cible ou permet de rejoindre un appui. |
+| Momifié (`husk`) | Faim négative plus courte. | Écran de poussière mobile utilisable sur tout sol : séparer des monstres et couvrir un repli. |
+| Ocelot (`ocelot`) | Déplacement accroupi plus réactif sur tous les terrains. | Pas latéral rapide sans invulnérabilité : quitter une ligne de tir ou gagner un angle. |
+| Panda (`panda`) | Recul moins gênant lorsque l’on reste au sol. | Roulade large qui ouvre de l’espace sans briser le terrain ; repositionnement et dégagement. |
+| Perroquet (`parrot`) | Signaux proches de danger plus faciles à localiser. | Leurres sonores à un point visé : détourner une approche de monstre ou signaler une route aux alliés. |
+| Poisson-globe (`pufferfish`) | Riposte visuelle et réduction brève du recul après un contact hostile. | Défense épineuse courte : décourage les attaques rapprochées pendant qu’un allié agit ; aucun renvoi récursif. |
+| Ours blanc (`polar_bear`) | Recul réduit lors de la protection d’un proche. | Rugissement protecteur autour d’une position : couvrir un soin, une consommation ou un départ. |
+| Golem de neige (`snow_golem`) | Déplacement plus sûr sur les sols glissants ; jamais un prérequis pour traverser. | Rafale de neige brève sur une zone : ralentit une approche et rend les trajectoires visibles, sans blocs permanents. |
+| Arpenteur (`strider`) | Brûlures plus courtes, sans immunité. | Franchissement bref d’une surface de lave, sans arrêt ni renouvellement sur place ; équipement et potions restent nécessaires pour y travailler. |
+| Tortue (`turtle`) | Petite économie d’air en mouvement aussi. | Garde directionnelle mobile pour une retraite terrestre ou aquatique ; ne nécessite plus d’être immobile au fond de l’eau. |
+| Villageois (`villager`) | Derniers devis et stocks connus plus faciles à consulter. | Lot d’échanges sélectionnés avec un marchand accessible : paiement, stocks et prix normaux ; gain de manipulation. |
+| Loup (`wolf`) | Cible qui menace le porteur plus facile à suivre visuellement. | Appel de meute : marque cette menace et ouvre un bref déplacement de regroupement volontaire ; soutien au contact et repli. |
+| Embourbé (`bogged`) | Déplacement moins entravé par les sols meubles. | Flèche créant une petite zone de ralentissement temporaire au point d’impact : organise les approches. |
+| Desséché (`parched`) | Effets de faiblesse plus courts. | Flèche de poussière qui gêne brièvement les tirs des monstres ordinaires : protège une ligne d’avancée. |
+
+### Rares — des outils avec lesquels construire un plan
+
+| Œuf | Passif envisagé | Actif et ouverture vers les autres |
+|---|---|---|
+| Allay (`allay`) | Ramassage de proximité sur les types choisis. | Rassemble les piles visibles vers un point : récolte, récupération et logistique. |
+| Axolotl (`axolotl`) | Consommation de nourriture un peu plus rapide, sur terre et dans l’eau. | Secours ciblé payé en nourriture ; un allié peut protéger la courte récupération. |
+| Blaze (`blaze`) | Brûlures plus courtes. | Projectile enflammé annoncé, sans incendie propagé ; profite d’un angle ouvert ou d’une trajectoire préparée. |
+| Breeze (`breeze`) | Contrôle de direction en chute. | Rafale sur cibles, objets et zones mobiles ; ouvre des interactions avec toutes les sources compatibles. |
+| Dromadaire momifié (`camel_husk`) | Endurance terrestre sans condition nocturne. | Ruée laissant un écran de poussière temporaire : ouvre puis couvre le passage du groupe. |
+| Creeper (`creeper`) | Alerte aux amorçages explosifs proches. | Détonation annoncée autour du porteur : profite de la protection et du placement des autres. |
+| Endermite (`endermite`) | Petits dégâts de perle réduits. | Téléportation courte vers une place libre : repli, changement d’angle et accès selon les droits existants. |
+| Gardien (`guardian`) | Visée plus stable pendant la dérive, sur terre comme dans l’eau. | Rayon canalisé en ligne de vue : protection et contrôle alliés permettent de tenir sa visée. |
+| Hoglin (`hoglin`) | Recul de mêlée réduit. | Charge qui pousse une cible devant soi, sans traverser les murs ; amène un ennemi dans une zone préparée. |
+| Cube de magma (`magma_cube`) | Brûlures plus courtes. | Bond et réception brûlante temporaire sans feu de terrain : changement de position et contrôle d’un point. |
+| Mooshroom (`mooshroom`) | Consommation de soupes plus commode, bol vide rangé si possible. | Distribue de vraies soupes sélectionnées aux proches consentants, une portion consommée par personne ; pause collective. |
+| Nautile (`nautilus`) | Petite économie d’air constante. | Cloche d’air temporaire : halte de travail ou de secours pour plusieurs plongeurs. |
+| Phantom (`phantom`) | Correction aérienne améliorée aussi le jour. | Descente planée plus longue que la Poule, avec un virage ample ; trajet entre points préparés, sans remontée. |
+| Piglin (`piglin`) | Mémoire lisible des trocs déjà obtenus. | Suite de trocs payés auprès d’un Piglin accessible ; moins de manipulations, aucune amélioration du tirage. |
+| Pillard (`pillager`) | Maniement de l’arbalète plus stable en déplacement lent. | Prépare un tir de couverture avec une munition réelle : tient une ligne pendant le passage d’un allié, sans tir supplémentaire gratuit. |
+| Poisson d’argent (`silverfish`) | Retour visuel sur les parois proches déjà inspectées. | Écho de cavité avec repère partagé : choisir un accès sans révéler du minerai ni ouvrir un tunnel. |
+| Cube de soufre (`sulfur_cube`) | Altérations de poison/faiblesse plus courtes. | Zone acide temporaire bornée en dégâts, sans usure d’armure ni destruction de blocs ; profite du placement des cibles. |
+| Lama de marchand (`trader_llama`) | Laisses et convois plus faciles à surveiller. | Rassemblement des animaux possédés proches autour d’un point praticable ; départ de caravane et mise à l’abri. |
+| Marchand ambulant (`wandering_trader`) | Carnet des offres et lieux personnellement rencontrés. | Prépare et partage une liste d’approvisionnement depuis les informations réellement connues ; achats toujours auprès du marchand accessible. |
+| Cheval-zombie (`zombie_horse`) | Réceptions de la monture moins douloureuses. | Endurance brève de la monture réellement chevauchée, utilisable à tout moment du jour ; escorte et retour. |
+| Zombie-villageois (`zombie_villager`) | Lecture claire de la conversion ou de l'altération de la cible observée. | Veille de secours interrompue par les coups : stabilise un allié ou aide une conversion déjà engagée ; ses ingrédients restent requis. |
+| Piglin zombifié (`zombified_piglin`) | Alerte lisible lors d’une montée de colère proche. | Offrande réelle créant un répit local et bref auprès des créatures concernées ; ailleurs, protection courte contre le recul pour préparer la fuite. |
+| Vagabond (`stray`) | Visée plus stable après immobilisation volontaire. | Flèche de givre ralentissant brièvement la cible : ouvre un tir ou un dégagement, sans chaîne de gel. |
+| Vindicateur (`vindicator`) | Gestes de hache plus commodes sur le bois, dans le rang acheté. | Coup préparé à la hache avec arc de frappe lisible : exploite les ennemis rapprochés, sans arme gratuite. |
+| Zoglin (`zoglin`) | Stabilité accrue lorsque la vie baisse. | Charge traversante sur quelques ennemis ordinaires : sépare une mêlée pour ouvrir un corridor. |
+
+### Extraordinaires — une intervention qui change brièvement la situation
+
+| Œuf | Passif envisagé | Actif et ouverture vers les autres |
+|---|---|---|
+| Grinceur (`creaking`) | Protection modérée après immobilisation courte, sans annuler le danger. | Racines temporaires retenant quelques monstres : préparer une attaque ou protéger une évacuation. |
+| Grand gardien (`elder_guardian`) | Travail aquatique moins pénalisé. | Sanctuaire temporaire combinant stabilité et protection, sur terre ou dans l’eau ; ni air infini ni Force de conduit permanente. |
+| Enderman (`enderman`) | Manipulation d’un bloc sélectionné plus lisible : aperçu de placement, sans portée gratuite permanente. | Passage court à entrée et sortie fixes pour quelques alliés volontaires ; chacun doit être autorisé à l’arrivée, enveloppe de déplacement conservée. |
+| Évocateur (`evoker`) | Altérations entravant les gestes plus courtes. | Ligne de crocs ancrée au sol et annoncée : profite d’une immobilisation ou d’un corridor, sans franchir une paroi. |
+| Ghast (`ghast`) | Contrôle de direction pendant la chute. | Projectile explosif lent et visible, sans destruction du terrain : peut être préparé ou dévié selon le contrat des projectiles. |
+| Ghast joyeux (`happy_ghast`) | Réceptions de transport plus douces. | Appui aérien temporaire pour un petit groupe : observer ou préparer une vraie arrivée ; ne relance pas les capacités aériennes. |
+| Golem de fer (`iron_golem`) | Recul réduit. | Interposition mobile devant les alliés : donne du temps pour tirer, boire ou secourir. |
+| Piglin barbare (`piglin_brute`) | Stabilité au corps à corps sans imposer une pièce d’or. | Parade volontaire à fenêtre courte, puis déplacement de riposte ; exige une vraie arme. |
+| Ravageur (`ravager`) | Forte stabilité au sol. | Large charge annoncée qui dégage un passage ; coordination avec les zones alliées, sans dégâts au terrain. |
+| Shulker (`shulker`) | Réception et stabilisation près d’un support plus faciles. | Appui suspendu bref, utilisable par d’autres pour tirer ou construire ; pas d’escalier magique infini. |
+| Cheval-squelette (`skeleton_horse`) | Meilleur contrôle de la monture aux transitions terre/eau. | Traversée brève à la surface de l’eau, porteur ou monture : ouvre un trajet, sans remplacer bateau ni longue plongée. |
+| Renifleur (`sniffer`) | Ressources de surface connues plus faciles à distinguer à proximité. | Recherche de piste partagée vers une ressource naturelle ou un indice déjà connu dans les chunks chargés ; l’archéologie devient une spécialité supplémentaire. |
+| Vex (`vex`) | Gestes de placement près des angles plus faciles à prévisualiser. | Main distante pour une action de construction réelle et limitée ; exploite l’appui ou les matériaux apportés par le groupe. |
+| Sorcière (`witch`) | Petite prolongation des potions bénéfiques bues, sans puissance ajoutée. | Transporte une vraie dose sous forme de brume mobile : soin ou préparation à distance avec une ressource consommée. |
+| Wither squelette (`wither_skeleton`) | Déclin reçu moins long. | Coup chargé appliquant une blessure brève aux monstres compatibles : ouverture offensive après placement, sans affaiblissement permanent de boss. |
+| Nautile-zombie (`zombie_nautilus`) | Stabilité aquatique, même lorsque l’air n’est pas critique. | Extraction d’un plongeur volontaire vers un point d’eau sûr proche : consomme son budget de déplacement et l’aide d’air éventuelle. |
+
+### Légendaires — spectaculaires, sans devenir l’équipement obligatoire
+
+| Œuf | Passif envisagé | Actif et ouverture vers les autres |
+|---|---|---|
+| Ender Dragon (`ender_dragon`) | Contrôle de descente et réception contre les parois améliorés. | Grand souffle directionnel annoncé, en zone temporaire ; les alliés organisent les cibles et les voies de sortie. Aucun vol permanent ni Résistance II. |
+| Warden (`warden`) | Perception locale des vibrations significatives, sans omniscience. | Onde frontale annoncée sur quelques cibles compatibles : exploite une ligne préparée ; les perceptions ne donnent pas de dégâts à travers une paroi. |
+| Wither (`wither`) | Stabilité lorsque la vie devient basse. | Salve lente annoncée qui impose un déplacement aux ennemis : zones et déviations offrent des usages variés, sans destruction du terrain. |
+
+## 8. Lire le pouvoir et découvrir les synergies
+
+Conserver les noms cryptiques et **Étude des familiers** de beta.020. Sans
+l’aptitude, le joueur peut expérimenter ; avec elle, il doit comprendre les
+effets et leurs interactions sans consulter ce document.
+
+L’infobulle étudiée donne : passif, geste actif, portée/durée/recharge, coût,
+puis une phrase de comportement partagé lorsqu’elle est pertinente.
+Les raisons de refus et le consentement restent compréhensibles même sans
+l’aptitude. Les nouveaux textes devront exister en **FR et EN**.
+
+Exemples de formulations proposées :
+
+| FR | EN |
+|---|---|
+| « Rafale — Repousse les petites créatures, les objets et les nuages mobiles. » | “Gust — Pushes small creatures, items and movable clouds.” |
+| « Brume — Transporte une dose de la potion sélectionnée. Le vent peut la déplacer. » | “Mist — Carries one dose of the selected potion. Wind can move it.” |
+| « Coussin — Prépare une réception pour vous et vos alliés. Consomme 1 laine. » | “Cushion — Prepares a landing for you and your allies. Consumes 1 wool.” |
+| « Récupération en cours : ce soin ne peut rendre que {amount} PV. » | “Recovering: this heal can restore only {amount} HP.” |
+| « Arrivée bloquée. Aucun pouvoir dépensé. » | “Destination blocked. Ability not spent.” |
+
+L’aperçu doit montrer le point d’arrivée ou la zone et le coût réel, pas
+proposer un menu de composition d’équipe. Le carnet peut garder les expériences
+du joueur si un ticket ultérieur le prévoit ; aucune encyclopédie de recettes
+de combos n’est nécessaire pour livrer les premiers pouvoirs.
+
+## 9. Comment savoir si la refonte marche
+
+Tester des **séances de 20 minutes**, en solo puis à **2–4 joueurs**, sur des
+mondes de développement neufs. Essayer séparément les capacités initiales
+3–3–3 et un personnage avancé, puis le retour après prestige. Fixer terrain,
+équipement et objectifs entre comparaisons ; noter graine et coordonnées.
+
+| Séance | Ce que l’on observe | Critère de décision proposé |
+|---|---|---|
+| Route plate puis relief, avec/sans potion | Temps, nombre de bonds, chutes, attention demandée. | Le lapin aide dans le relief ; la potion reste un gain net sur le trajet long. Aucun bonus n’efface la potion active. |
+| Combat ordinaire sans espèce obligatoire | Activations utiles, coups évités, dégâts, moments d’attente. | Un actif courant offre au moins deux occasions utiles dans une rencontre de 60–90 s ; il ne se joue pas par pression automatique à chaque recharge. |
+| Chantier avec une descente et un ravitaillement | Objets transportés, manipulations, accidents, matériaux consommés. | Plusieurs rôles améliorent le travail ; retirer un œuf ne bloque pas la construction normale. |
+| Plongée courte puis travail prolongé | Air économisé, fenêtres de travail, besoins en potions. | Une cloche permet un secours ; les rotations de cloches ne donnent pas une respiration illimitée. |
+| Duos puis trio : rafale, brume, attaque de zone | Réussites, ratés, lisibilité, pertes de doses. | On comprend l’interaction sans bonus de paire ; déplacer un effet ne renouvelle ni durée ni ressources. |
+| Exploration libre avec composition non suggérée | Solutions inventées par les joueurs. | Rechercher au moins une utilisation utile absente de la fiche initiale, puis expliquer par quelle règle commune elle fonctionne. |
+| Groupe nombreux, jusqu’à 30 joueurs en charge de test | Contrôles, soin, air, quantité d’effets et temps serveur. | Pas d’immortalité, de contrôle infini, de recharge réinitialisée, ni de coût de recherche proportionnel à du terrain non chargé. |
+
+Relever les activations **qui ont réellement servi**, pas seulement les touches
+pressées, et les raisons de refus. Pour un œuf lié à l’activité de la séance,
+viser un actif utile au moins **5 fois en 20 minutes** hors grands pouvoirs.
+Une fonction logistique peut servir moins souvent si le gain de manipulation
+est clair ; une fréquence forcée n’est pas une qualité en soi.
+
+Après chaque essai, demander au joueur de reformuler ce que son œuf lui a
+permis de faire et pourquoi il le reprendrait. Si la réponse décrit seulement
+un bonus invisible ou un cas improbable, revoir le geste. Si tous les joueurs
+choisissent le même œuf quelle que soit l’activité, rechercher la cause plutôt
+que nerfer tous ses chiffres indistinctement.
+
+Les essais coopératifs comprennent aussi : deux œufs identiques, deux soins
+différents, une personne refusant les déplacements, une sortie/entrée répétée
+dans une zone, changement d’œuf en plein effet, mort et reconnexion. Tester
+les potions natives et les capacités de faction pour distinguer les sources
+d’effets et éviter de multiplier involontairement les mêmes bonus.
+
+## 10. Découpage en résultats vérifiables
+
+L’ordre recommandé commence par les gestes fréquents et une interaction
+coopérative réelle. Réécrire 88 descriptions d’un coup ne suffirait pas à
+valider le plaisir du système.
+
+| Ticket local proposé | Résultat attendu | Vérification qui permet de le clôturer |
+|---|---|---|
+| **FAM-CORE-01** | Budget des effets reçus, déplacements consentis et règles de cumul communes. | Transactions, récupération après reconnexion/prestige, aucune ressource ou impulsion recréée. Contrat de données explicite avant ajout d’état persistant. |
+| **FAM-MOVE-02** | Lapin, Poule et Mouton utilisables dans une même traversée. | Route/relief avec et sans potions ; réception de groupe ; aucun vol renouvelable. |
+| **FAM-GROUP-03** | Rafale, brume de potion et détonation réagissent aux mêmes règles de position. | Brume transportée avec dose conservée ; regroupement puis explosion ; une combinaison libre documentée. |
+| **FAM-SUPPORT-04** | Golem de fer, Squelette, Axolotl et Nautile ouvrent des actions aux autres. | Tir sous protection, soin interrompu, marque partagée, secours aquatique ; plafonds par bénéficiaire vérifiés. |
+| **FAM-WORK-05** | Allay et Âne forment une chaîne de collecte/rangement avec les outils existants. | Quantités conservées, protections et cases verrouillées respectées, moins de manipulations mesurées. |
+| **FAM-CATALOG-06** | Reprendre les 76 autres concepts par petits lots liés à une activité. | Chaque espèce reçoit valeurs, FR/EN, comportement vérifié et un essai réel ; sa ligne conceptuelle ne vaut jamais livraison. |
+| **FAM-ACQUIRE-07** | Donner un parcours d’obtention en survie aux premiers œufs éprouvés. | Œuf accessible et utilisable dans une vraie partie ; décisions sur les récompenses prises dans ce ticket. |
+
+L’obtention en survie est encore un chantier distinct dans les documents
+actuels. La meilleure refonte des pouvoirs restera un essai tant que les
+joueurs ne pourront pas obtenir ces œufs. Pour les prototypes, utiliser les
+moyens opérateur uniquement dans les mondes de développement ; ne pas présenter
+cela comme un parcours de survie livré. Rangs I–III et œufs Anomaly attendent
+une base agréable et stable, sans nouveau système de puissance ajouté ici.
+
+Chaque livraison de code devra suivre les règles du dépôt : branche dédiée,
+`./gradlew check build`, puis `./gradlew assemblePack` si la distribution change,
+incrément beta.xxx et documentation correspondante. Les formats de sauvegarde,
+identifiants, noms et tailles d’œufs existants restent préservés ; tout ajout de
+budget persistant exige son contrat de migration avant implémentation.
+Un retour à une ancienne version ne doit pas être promis sans ce contrat.
+
+## Sources et état de cette passe
+
+- [README du dépôt](../README.md), état local jusqu’à beta.029 ;
+ [vision](vision.md), notamment progression et familiers.
+- [Inventaire historique 26.2](companion-powers-inventory.md) et
+ [code historique des pouvoirs](../../26.2/sanctuary/src/main/java/fr/koka99cab/sanctuary26/sanctuary/gameplay/SanctuaryCompanionPowers.java),
+ consulté en lecture seule : définition du lapin et entretien des effets.
+- [Proposition v1](companion-powers-balance-proposal.md),
+ [livraison beta.019](companion-powers-beta019.md),
+ [interactions beta.020](familiar-interactions-beta020.md).
+- Sources locales : [catalogue](../mods/sanctuary/src/main/java/fr/koka/sanctuary/companion/CompanionType.java),
+ [passifs](../mods/sanctuary/src/main/java/fr/koka/sanctuary/companion/CompanionPassives.java),
+ [actifs](../mods/sanctuary/src/main/java/fr/koka/sanctuary/companion/CompanionActions.java).
+
+Vérification de cette passe : cohérence documentaire, liens locaux et couverture
+des 88 identifiants. **Aucun essai d’équilibrage exécuté, aucun code de gameplay
+modifié, aucun binaire livré.** Les chiffres v2 restent à éprouver ; le document
+sépare les douze prototypes chiffrés des 76 orientations de catalogue.
diff --git a/docs/spawn-eggs-v2-beta032.md b/docs/spawn-eggs-v2-beta032.md
new file mode 100644
index 0000000..7aa9221
--- /dev/null
+++ b/docs/spawn-eggs-v2-beta032.md
@@ -0,0 +1,113 @@
+# FAM-CORE-01 à FAM-CATALOG-06 — refonte des 88 familiers
+
+Le contrat des attaques et du Slime est remplacé en beta.044 : [audit](spawn-eggs-audit-beta044.md).
+
+Branche `codex/spawn-eggs-v2-prototypes`. Les 88 œufs du document
+[de conception](spawn-eggs-refonte-v2.md) sont inclus selon la réponse du créateur.
+Le [catalogue chiffré](spawn-eggs-v2-catalogue-beta032.md) fixe les valeurs manquantes.
+L’acquisition, les rangs et Anomaly restent des tickets suivants. État : livré localement dans beta.032, prêt pour les essais en partie.
+
+## Contrat de migration additive
+
+La demande d'implémentation porte sur les pouvoirs. Aucun terrain ni fichier de
+monde existant n'est ouvert ou réécrit par les outils de développement.
+À l'utilisation du mod, l'attachement joueur `sanctuary:companion_powers` reste
+strictement au schéma 1 : achats, consentement, notes et échéances sont conservés.
+Les anciennes échéances soin/air sont honorées jusqu'à leur expiration.
+
+Un nouvel attachement indépendant `sanctuary:companion_budgets_v2` (schéma 1)
+conserve les dépenses récentes par bénéficiaire et l'enveloppe de déplacement.
+Il se crée paresseusement. Mort, prestige, reconnexion et changement d'œuf ne
+remettent pas ce budget à zéro. Un état invalide bloque les pouvoirs concernés,
+sans effacer les octets d'origine. Seul le bouton opérateur de préparation peut
+réinitialiser explicitement ces budgets pour les essais.
+
+Les fenêtres utilisent l'horloge serveur réelle comme la recharge existante.
+Les dépenses sont regroupées par quarts de seconde, expirant à la fin du
+quart : la borne est conservatrice, avec au plus 250 ms de délai supplémentaire.
+Les soins sont débités à leur livraison effective ; les interruptions ne
+facturent pas les soins non reçus. Les points d'air réellement économisés ou
+rendus sont débités. Les plafonds suivent la capacité personnelle actuelle.
+Un retour de l'horloge ne raccourcit pas les dépenses encore enregistrées.
+
+Les configurations existantes restent schéma 1. Les anciennes valeurs de
+recharge **encore identiques à leurs valeurs par défaut** sont remplacées par
+les nouvelles ; les personnalisations, raretés et désactivations restent intactes.
+Une copie `.pre-v2` précède cette écriture atomique. Un marqueur de migration
+empêche de répéter l'opération : une valeur personnalisée ultérieure, même égale
+à l'ancien défaut, reste respectée. Un fichier invalide reste intact.
+
+Un retour vers un ancien binaire ne connaît pas les nouveaux budgets : sauvegarder
+le monde avant mise à jour et restaurer cette sauvegarde pour un retour arrière.
+Ne pas compter sur un aller-retour de binaires pour conserver ces nouveaux champs.
+
+
+## Résultat jouable
+
+Les 88 passifs, actifs, recharges et descriptions FR/EN sont repris dans le
+catalogue chiffré. Les familles utilisent les mêmes règles de soin, d’air, de
+garde et de déplacement par bénéficiaire, même en combinant plusieurs œufs.
+Les variantes natives et certains petits effets secondaires historiques restent
+compatibles ; le tableau décrit le pouvoir principal de chaque espèce.
+
+Le coussin de laine apparaît au sol ; les brumes et poussières se déplacent
+avec le souffle du Breeze sans renouveler leur durée ou leur contenu. La brume
+de sorcière consomme une vraie potion et donne une dose native à une seule
+personne. Les appuis et passages demandent de maintenir Maj pour accepter le
+déplacement. Les potions, armes, outils, graines et stocks utilisés restent réels.
+
+Les gestes de visée principaux montrent un aperçu de zone, le coût et les refus
+serveur aux opérateurs et aux joueurs ayant étudié les familiers. Les effets
+s’arrêtent au retrait de l’œuf, sauf la potion réelle déjà reçue. La touche G
+agit une fois par pression ; maintenir la touche ne relance pas automatiquement.
+
+Pour essayer : équiper un œuf dans la case Compagnon, acheter le Lien actif,
+attendre les 10 secondes de stabilisation, puis utiliser G. L’Étude des familiers
+révèle les explications ; l’outillage opérateur permet de préparer les essais.
+Les recharges affichées suivent la configuration reçue du serveur.
+
+Client et serveur doivent tous deux utiliser beta.032 : une capacité réseau v2
+explicite évite qu’un ancien client rejoigne avec des règles incompatibles.
+La génération, les identifiants des œufs et les autres formats restent inchangés.
+
+## Vérifications et limites
+
+`./gradlew check build assemblePack` avec la sélection native
+`refonte,companions,familiar,carry,inventory,cycle` réussit : **85/85 tests serveur**,
+dont 20 cas de refonte. Le contrôle couvre notamment la sauvegarde/relecture
+native, la mort, le vrai New Game+, les délais hérités, la migration de configuration,
+les budgets fractionnaires, les doses de potion, les 4 bénéficiaires distincts,
+les gardes avec Résistance native, la résistance aux contrôles, l’abeille avec
+sa graine et sa houe, le souffle d’un autre joueur et les transferts sans pertes.
+Les 88 chemins d’action sont vérifiés : une action préparée ou un refus contextuel.
+Cela ne constitue pas un essai positif exhaustif des 88 usages dans toutes les situations.
+
+Le parcours `:sanctuary:runClientGameTest` avec `sanctuaryClientTests=true` et
+`sanctuaryRefonteClientTests=true` réussit dans une nouvelle partie native :
+touche G, réseau, coussin payé en laine, brume payée par une potion complète,
+flacon rendu, bond du lapin sous Vitesse native et résolution des textes des
+88 espèces en FR et EN. Les captures finales ont été relues dans
+`build/refonte032-evidence/`. Le catalogue FR et les 88 recharges correspondent
+aux ressources et constantes du binaire.
+
+Une simulation pure de 30 bénéficiaires pendant 60 secondes de jeu valide les
+plafonds et la taille bornée des journaux (129 ms dans cette exécution). Elle
+ne simule pas 30 clients graphiques. Les essais ne remplacent pas une séance
+d’équilibrage de 20 minutes en solo puis à 2–4 joueurs.
+L’acquisition des œufs en survie, les rangs I–III et Anomaly restent à concevoir.
+Aucun test de FPS avec 30 clients connectés n’est revendiqué.
+
+
+## Artefact local
+
+[Sanctuary-beta.032.mrpack](../build/Sanctuary-beta.032.mrpack), 4871119 octets.
+
+SHA-256 : `5ca10e29b07a68926f518d71072d8d56b61e26c0182fadc2c508ef1fc79bcac5`.
+Reçu : `build/refonte032-artifact.json`. Logs : `build/refonte032-check-final.log`
+et `build/refonte032-client-final.log`.
+
+Archive ZIP, versions internes, JAR embarqué et **1 303 classes compilées**
+vérifiés. Comparaison avec beta.031 : seules les classes des familiers et leur
+admission réseau changent ; autres ressources, classes Demeure et JAR JEI conservés.
+L’ancien MRpack beta.031 conserve son empreinte. Aucun déploiement, canal packwiz
+publié ou instance Prism personnelle modifié.
diff --git a/docs/spawn-eggs-v2-catalogue-beta032.md b/docs/spawn-eggs-v2-catalogue-beta032.md
new file mode 100644
index 0000000..355648a
--- /dev/null
+++ b/docs/spawn-eggs-v2-catalogue-beta032.md
@@ -0,0 +1,128 @@
+# Refonte v2 — catalogue chiffré beta.032
+
+Archive beta.032 : [catalogue actuel beta.044](spawn-eggs-catalogue-beta044.md).
+
+Les 88 espèces sont incluses à la demande du créateur. Les douze prototypes
+reprennent les valeurs du document v2 ; les autres reçoivent ici leurs valeurs
+de premier essai. Ces nombres sont des choix de développement, pas des résultats
+d'équilibrage mesurés en partie.
+
+Les rangs I–III, Anomaly et l'obtention en survie restent hors de cette livraison.
+Les anciens œufs, leur nom, leur taille, leur rareté et les achats restent valides.
+
+**Gestes communs :** œuf équipé, stabilisation de 10 s, **G** pour l'actif après
+achat du Lien actif. **Étude des familiers** révèle les explications dans
+l'infobulle. Maintenir Maj dans une zone de déplacement accepte ce mouvement ;
+le consentement général aux soins n'autorise pas à déplacer quelqu'un.
+
+**Plafonds :** soins de familiers 20 % de la vie maximale / 20 s ; air actif
+généré ou économisé un tiers de la capacité / 30 s ; garde 40 % pendant
+6 s reçues / 20 s, puis 25 %. Un déplacement magique partage 8 blocs horizontaux
+et 4 ascendants jusqu'à un véritable appui. Quatre bénéficiaires par zone pour
+toute sa durée, propriétaire compris. Une brume transporte **une dose consommée
+pour une seule personne**. Les projectiles natifs gardent leurs dégâts natifs ;
+les ajouts de familiers sont plafonnés à 2 PV par activation/cible en PvP.
+
+Les baisses de rang et changements d'œuf n'effacent pas les dépenses récentes.
+Les zones sont temporaires ; les effets de la dose de potion déjà reçue restent
+natifs même si la sorcière est déséquipée. La durée/quantité d'une zone ne se
+renouvelle pas lorsqu'une rafale la déplace.
+
+| Œuf | Passif | Actif | Recharge |
+|---|---|---|---:|
+| `bat` | L'obscurité est légèrement éclaircie, même en mouvement. Cécité et Ténèbres restent actives. | Repère 3 présences à 12 blocs pendant 6 s ; partage avec 4 personnes au plus dans un rayon de 16 blocs. | 15 s |
+| `cat` | Réduit les dégâts de chute de 20 %. | Fait reculer jusqu'à 4 petits monstres ordinaires à 5 blocs pendant 2 s. Les boss et joueurs résistent. | 20 s |
+| `chicken` | Tolère un bloc de chute supplémentaire. | Plane pendant 3 s sans remonter. Efface la distance de chute accumulée à l'activation. | 15 s |
+| `cod` | Économise 10 % de l'air normalement perdu. | Rend jusqu'à 30 points d'air au plongeur visé à 6 blocs, ou à soi. Budget commun : un tiers de la capacité en 30 s. | 20 s |
+| `cow` | Le lait bu conserve les effets bénéfiques. | Consomme 1 seau de lait pour retirer Poison et Faim de 3 personnes à 3 blocs. Rend le seau vide. | 30 s |
+| `donkey` | Réduit l'épuisement du sprint de 10 %. | Range jusqu'à 18 piles hors hotbar dans un coffre, tonneau ou shulker à portée normale. Seulement les types déjà présents. | 10 s |
+| `fox` | Déplacement accroupi 10 % plus rapide sur tous les sols. | Esquive au sol de 3 blocs et ramasse un objet à la fin du trajet. Sans invulnérabilité. | 10 s |
+| `frog` | Réduit de 10 % la gêne du déplacement dans l'eau peu profonde. | À 6 blocs, attire un objet, un petit monstre ou un allié qui maintient Maj. Le déplacement allié utilise son budget aérien. | 10 s |
+| `horse` | Réduit l'épuisement du sprint de 10 %. | Ruée dirigée de 4 blocs à pied ou sur la monture contrôlée. Garde les collisions et les pénalités de portage. | 25 s |
+| `llama` | Les laisses tenues tolèrent 2 blocs supplémentaires. | Crachat visible à 10 blocs : 1 PV et ralentissement de 15 % pendant 2 s. Aucun objet consommé. | 12 s |
+| `mule` | Réduit le recul de 20 % lorsque tu portes ou es porté. | Livre jusqu'à 9 piles du type tenu en main gauche à une monture possédée ou un allié consentant visé à 3 blocs. Hotbars préservées. | 10 s |
+| `pig` | Les aliments apportent 10 % de saturation supplémentaire. | Repère jusqu'à 3 récoltes, bois ou fleurs déjà découverts à 16 blocs pendant 10 s. Repères partagés, aucun objet créé. | 15 s |
+| `rabbit` | Contrôle de direction en l'air amélioré de 25 %, sans vitesse ni hauteur ajoutée au saut normal. | Un bond par vol : jusqu'à 3 blocs vers l'avant, ou 2 vers le haut en visant haut. Appuyer à nouveau après réception ; budget aérien commun. | 8 s |
+| `salmon` | Les courants poussent 25 % moins fort. | Impulsion aquatique dirigée de 3 blocs, puis sillage de 5 s pour les nageurs qui suivent en maintenant Maj. Nage requise. | 12 s |
+| `sheep` | Réduit les dégâts de chute de 15 %. | Consomme 1 laine pour poser à 6 blocs un coussin de 2 × 2 pendant 8 s. Une chute amortie par personne, 4 personnes au total : moitié des dégâts, jusqu'à 4 PV. | 30 s |
+| `skeleton` | Réduit de 15 % la dispersion des flèches en mouvement lent. | La prochaine flèche tirée sous 8 s marque sa victime pendant 6 s pour 4 observateurs proches au moment du tir. Arc et munition réels. | 15 s |
+| `slime` | Réduit les dégâts de chute de 15 % et la gêne du miel de 15 %. | Pose un tremplin à 4 blocs pendant 5 s. Maintenir Maj dessus déclenche un rebond, une fois par personne ; 4 personnes, budget aérien commun. | 15 s |
+| `spider` | Monte les échelles et lianes 15 % plus vite. | Accroche une paroi à 2 blocs pendant 4 s et grimpe jusqu'à 2 blocs. La paroi doit rester présente. | 15 s |
+| `squid` | L'eau proche est plus lisible. | Pose à 4 blocs un nuage d'encre de rayon 2 pendant 4 s. Gêne l'acquisition des monstres ordinaires ; le vent le déplace, sans arrêter les projectiles. | 25 s |
+| `tadpole` | Reprend son air 20 % plus vite à la surface. | Repère une berge ou une sortie libre proche, dans un rayon de 16 blocs, pendant 10 s. Partage le repère sans téléporter. | 12 s |
+| `tropical_fish` | Souligne les objets visibles au sol sous l'eau à 6 blocs. | Pose une balise colorée à 8 blocs pour 20 s, partagée avec 4 personnes à 16 blocs. | 10 s |
+| `zombie` | Réduit le recul de 20 % sous 30 % de vie. | Consomme 1 point de faim pour récupérer 1 PV en 5 s. Un coup reçu ou porté interrompt le soin ; budget commun de soin. | 30 s |
+| `armadillo` | Accroupi, réduit de 15 % les dégâts des projectiles, sans attente. | Garde de 3 s, réduction jusqu'à 40 %. Ralentit et empêche d'attaquer ; budget commun de garde de 6 s sur 20 s. | 25 s |
+| `bee` | Clic droit avec une houe sur une culture mûre : récolte native et replante avec une graine de l'inventaire débloqué. L'outil s'use. | Consomme une poudre d'os par plante visée à portée normale, jusqu'à 4 plantes selon le rang de construction. Aucun rendement ajouté. | 20 s |
+| `camel` | Réduit l'épuisement du sprint de 15 % sur tous les sols. | Enjambée de 5 blocs à pied ou sur la monture contrôlée ; franchit un obstacle bas avec collisions conservées. | 15 s |
+| `cave_spider` | Les poisons reçus durent 20 % moins longtemps. | Prépare sous 6 s un vrai coup de mêlée qui inflige jusqu'à 2 PV de poison en 4 s. Ne tue pas par le poison. | 25 s |
+| `copper_golem` | Le conteneur autorisé visé indique ses cases libres et les piles correspondant à la main gauche. | Répartit le type tenu en main gauche entre 4 conteneurs à portée normale, 9 piles par conteneur au plus, selon les types présents. Hotbar préservée. | 15 s |
+| `dolphin` | Améliore de 25 % le contrôle de direction dans l'eau, sans vitesse gratuite. | Sillage de 5 s : nage 20 % plus rapide pour toi et les alliés qui suivent en maintenant Maj à 3 blocs. Nage et faim suffisante requises ; 4 personnes au total. | 25 s |
+| `drowned` | Réduit de 10 % la pénalité de minage sous l'eau. | Ancrage de rayon 3 pendant 6 s : maintenir Maj sur un support réel pour ne plus dériver. 4 personnes consentantes au total. | 20 s |
+| `glow_squid` | Souligne doucement les surfaces sombres proches, sur terre et sous l'eau, sans changer la lumière du monde. | Balise lumineuse de 20 s à 8 blocs ; peut suivre l'objet visé. Visible pour 4 personnes proches. | 15 s |
+| `goat` | Réduit de 20 % la glissade supplémentaire sur glace. | Charge de 3 blocs : jusqu'à 2 PV et déplacement d'un monstre ordinaire. Aucun recul imposé aux joueurs. | 15 s |
+| `husk` | L'effet Faim reçu dure 20 % moins longtemps. | Pose à 4 blocs une poussière mobile de rayon 2 pendant 4 s. Ralentit jusqu'à 4 ennemis de 20 %, avec résistance aux contrôles successifs. | 25 s |
+| `ocelot` | Déplacement accroupi 10 % plus rapide sur tous les sols. | Pas latéral de 3 blocs, sans invulnérabilité ni attaque gratuite. | 10 s |
+| `panda` | Réduit le recul de 20 % au sol. | Roulade de 3 blocs avec une brève protection de 25 %. Peut déplacer les petits monstres proches, sans dégâts de terrain. | 20 s |
+| `parrot` | Localise un danger connu proche par un son et un repère, au plus toutes les 5 s. | Place un leurre sonore à 8 blocs pendant 4 s, partagé comme repère. Détourne jusqu'à 3 monstres ordinaires. | 20 s |
+| `pufferfish` | Après un contact hostile, éclat visuel et recul réduit de 20 % pendant 2 s. | Pendant 5 s, le prochain attaquant de mêlée reçoit jusqu'à 2 PV de poison sur 4 s. Une riposte, sans boucle de renvoi. | 25 s |
+| `polar_bear` | Réduit le recul de 20 % à proximité d'un allié consentant. | Protection de rayon 3 pendant 4 s : dégâts et recul réduits de 25 % pour 4 personnes au total. | 30 s |
+| `snow_golem` | Réduit de 20 % la glissade supplémentaire sur glace ; bref appui sur neige poudreuse. | Consomme 3 boules de neige pour une rafale visible à 12 blocs. Ralentit de 20 % pendant 2 s ; dégâts natifs contre les Blazes. | 20 s |
+| `strider` | Les brûlures durent 20 % moins longtemps, sans immunité. | Traverse une surface de lave pendant 3 s sur 5 blocs au plus. Il faut avancer ; rester sur place met fin au pouvoir. | 35 s |
+| `turtle` | Économise 10 % de l'air perdu, même en mouvement. | Garde directionnelle mobile de 4 s, sur terre ou dans l'eau. Jusqu'à 40 % de réduction devant 4 personnes ; budget de garde partagé. | 30 s |
+| `villager` | Mémorise les offres et stocks personnellement consultés. | Sélectionne puis effectue jusqu'à 3 échanges identiques avec un marchand accessible. Paiement, stocks et prix natifs. | 15 s |
+| `wolf` | Marque brièvement l'ennemi qui vient de te blesser. | Marque une menace à 12 blocs pendant 8 s ; appelle tes animaux dressés et crée un point de regroupement volontaire de 4 s. Maj pour bondir, budget aérien commun. | 25 s |
+| `bogged` | Le déplacement sur boue est moins entravé. | La prochaine flèche tirée sous 8 s crée à l'impact une zone de poussière de rayon 1,5 pendant 3 s ; ralentissement de 20 %. Arc et munition réels. | 25 s |
+| `parched` | L'effet Faiblesse reçu dure 20 % moins longtemps. | La prochaine flèche tirée sous 8 s gêne l'acquisition de cible d'un monstre ordinaire pendant 2 s. Joueurs et boss résistent. | 25 s |
+| `allay` | Ramasse à 3 blocs les objets correspondant à la main gauche, dans les cases débloquées. | Rassemble jusqu'à 16 piles visibles à 8 blocs vers un point visé à 4 blocs pendant 3 s. Pas de transfert automatique dans les conteneurs. | 12 s |
+| `axolotl` | Mange 10 % plus vite, sur terre et dans l'eau. | Consomme 1 point de faim pour rendre jusqu'à 20 % de la vie maximale en 4 s à soi ou un allié visé à 6 blocs. Un coup reçu ou porté interrompt ; budget de soin partagé. | 30 s |
+| `blaze` | Les brûlures durent 20 % moins longtemps. | Après 0,5 s d'annonce, lance un projectile enflammé à 16 blocs infligeant jusqu'à 4 PV. Aucun incendie de terrain ni munition consommée. | 25 s |
+| `breeze` | Contrôle de direction en l'air amélioré de 25 %, sans remontée. | Rafale en cône de 6 blocs : pousse 4 monstres ordinaires sur 2 blocs, ainsi que les objets et nuages mobiles. Dévie une fois les projectiles matériels compatibles. Viser le sol pour se propulser. | 15 s |
+| `camel_husk` | Réduit l'épuisement terrestre du sprint de 15 %, de jour comme de nuit. | Ruée de 6 blocs qui laisse un écran de poussière de rayon 2 pendant 4 s. Garde les collisions et le budget aérien. | 25 s |
+| `creeper` | Localise les amorçages explosifs proches à 8 blocs. | Annonce de 0,8 s puis détonation : 4 PV, rayon 3, 4 ennemis au plus, sans détruire le terrain. Être blessé interrompt, sans remboursement. | 25 s |
+| `endermite` | Réduit les petits dégâts de perle de 20 %. | Téléportation jusqu'à 3 blocs vers une arrivée libre sur support réel. Peut traverser les murs autorisés ; budget aérien commun. | 15 s |
+| `guardian` | Réduit de 15 % la dispersion des flèches en mouvement lent. | Canalise 1,5 s un rayon à 12 blocs, jusqu'à 4 PV. Garder la cible visible ; un coup reçu interrompt. | 30 s |
+| `hoglin` | Réduit le recul de mêlée de 20 %. | Charge annoncée de 4 blocs : jusqu'à 4 PV et poussée d'un monstre devant soi, sans traverser les murs. | 25 s |
+| `magma_cube` | Les brûlures durent 20 % moins longtemps. | Bond de 3 blocs puis réception brûlante : rayon 2, 3 ennemis, jusqu'à 3 PV chacun. Aucun feu de terrain. | 25 s |
+| `mooshroom` | Consomme la soupe aux champignons 10 % plus vite. | Distribue la soupe tenue à 4 personnes à 4 blocs, une portion réelle par personne. Rend les bols ; faim et saturation natives, sans soin gratuit. | 30 s |
+| `nautilus` | Économise 10 % de l'air perdu, même en mouvement. | Cloche d'air aquatique à 6 blocs, rayon 2, durée 6 s, 4 personnes au total. Suspend la perte d'air tant que le budget du bénéficiaire le permet. | 45 s |
+| `phantom` | Contrôle aérien amélioré de 25 % et dégâts de chute réduits de 20 %, même le jour. | Descente planée de 6 s, sans remontée ni nouveau bond. Efface la distance dangereuse à l'activation. | 30 s |
+| `piglin` | Mémorise les objets réellement reçus par troc. | Consomme 2 lingots d'or auprès d'un Piglin accessible et reçoit 2 tirages natifs complets. Aucun choix améliorant le hasard. | 30 s |
+| `pillager` | Arbalète rechargée 10 % plus vite ; meilleure stabilité en mouvement lent. | Prépare sous 8 s un tir de couverture réel. À l'impact, ralentit 3 monstres dans un rayon de 2 blocs de 20 % pendant 3 s. | 25 s |
+| `silverfish` | L'inspection d'une paroi proche distingue pierre et infestation après 2 s. | Sonde les 2 blocs derrière la face visée et partage un repère de cavité pendant 8 s. Aucun minerai révélé. | 20 s |
+| `sulfur_cube` | Poison et Faiblesse durent 10 % moins longtemps. | Pose à 6 blocs une zone acide de rayon 2 pendant 4 s : 4 ennemis au total, jusqu'à 3 PV par cible. Sans usure d'armure spéciale ni terrain détruit. | 30 s |
+| `trader_llama` | Affiche les places libres des montures possédées proches. | Rassemble 3 montures possédées à 12 blocs autour d'un point praticable visé. Navigation native. | 20 s |
+| `wandering_trader` | Mémorise les offres et lieux des marchands personnellement rencontrés. | Partage jusqu'à 4 références d'approvisionnement déjà observées avec 4 personnes proches. Aucun achat à distance. | 20 s |
+| `zombie_horse` | Réduit de 20 % les dégâts de chute de la monture contrôlée. | Pendant 6 s, monture et cavalier résistent 20 % mieux au recul ; dans l'eau, la monture gagne 15 % de vitesse. Aucune condition d'heure. | 25 s |
+| `zombie_villager` | Indique le temps restant d'une conversion observée. | Veille de 8 s sur une conversion engagée : retire 20 s une fois, ingrédients natifs requis. Sinon, secours payé en faim à un allié blessé. | 35 s |
+| `zombified_piglin` | Localise une montée de colère proche. | Consomme 1 lingot pour un répit de 2 s auprès de 3 Piglins zombifiés à 5 blocs. Sans cible adaptée : résistance au recul de 25 % pendant 4 s, sans lingot consommé. | 30 s |
+| `stray` | Visée plus stable en mouvement lent ; glissade réduite en bandant l'arc. | La prochaine flèche sous 8 s ralentit de 20 % pendant 3 s. En PvP : 2 s au plus puis 6 s de résistance. | 25 s |
+| `vindicator` | Travaille le bois à la hache environ 10 % plus vite. | Prépare un vrai coup de hache sous 6 s : arc frontal sur 2 ennemis supplémentaires à 3 blocs, moitié des dégâts de l'arme, jusqu'à 4 PV chacun. | 25 s |
+| `zoglin` | Réduit le recul de 15 % sous la moitié de vie. | Charge annoncée de 5 blocs à travers 3 ennemis ordinaires, jusqu'à 4 PV chacun. Garde les murs et le budget de déplacement. | 30 s |
+| `creaking` | Après 3 s immobile hors attaque, réduit les dégâts de 15 %. | Pose à 6 blocs des racines de rayon 2 retenant 4 monstres au plus pendant 3 s. Puis 6 s de résistance ; joueurs et boss immunisés. | 45 s |
+| `elder_guardian` | Réduit de 15 % la pénalité de minage aquatique. | Sanctuaire de rayon 3 pendant 6 s, sur terre et sous l'eau : dégâts et recul réduits de 25 % pour 4 personnes au total. | 60 s |
+| `enderman` | Indique la case de placement du bloc tenu à portée normale. | Ouvre pendant 8 s un passage à entrée fixe vers une arrivée libre à 8 blocs. Maintenir Maj dans l'entrée ; 4 voyageurs, droits et budget de chacun vérifiés. | 45 s |
+| `evoker` | Faiblesse et Lenteur durent 15 % moins longtemps. | Après 1 s d'annonce, ligne de crocs ancrée au sol sur 8 blocs : 3 cibles, jusqu'à 5 PV. Ne traverse pas une paroi. | 45 s |
+| `ghast` | Contrôle de direction en chute amélioré de 25 %. | Projectile explosif lent à 24 blocs, jusqu'à 5 PV dans un rayon de 2. Terrain intact ; une déviation de familier, sans gain de puissance. | 45 s |
+| `happy_ghast` | Réduit les dégâts de chute de 20 %. | Appui suspendu à 4 blocs, rayon 1,5 pendant 6 s. Maintenir Maj pour stabiliser sa descente, 4 personnes. Ne réarme pas les bonds. | 60 s |
+| `iron_golem` | Réduit le recul de 20 %. | Interposition mobile de 4 s devant 4 personnes : impacts frontaux réduits jusqu'à 40 %. Attaquer interrompt ; garde commune de 6 s sur 20 s, puis 25 %. | 30 s |
+| `piglin_brute` | Réduit le recul de mêlée de 20 %, sans armure d'or requise. | Avec une vraie arme, parade frontale de 1 s jusqu'à 40 %, puis riposte de 2 PV au plus sous 3 s. Budget de garde partagé. | 25 s |
+| `ravager` | Réduit le recul de 25 % au sol. | Large charge annoncée de 6 blocs, 3 ennemis, jusqu'à 5 PV chacun. Repousse les monstres et garde le terrain intact. | 45 s |
+| `shulker` | Réduit les dégâts de chute de 20 % ; Lévitation dure 20 % moins longtemps. | Appui suspendu à 4 blocs, rayon 1 pendant 4 s. Maintenir Maj pour se stabiliser ; 4 personnes, aucun bond réarmé. | 45 s |
+| `skeleton_horse` | Petite économie d'air de 15 % à cheval. | Traverse une surface d'eau pendant 6 s sur 10 blocs au plus, à pied ou sur une monture réelle. Ne permet pas de s'arrêter sur l'eau. | 40 s |
+| `sniffer` | Indique les besoins des cultures connues observées : lumière, eau ou sol. | Repère jusqu'à 4 ressources naturelles déjà connues à 24 blocs pendant 12 s. Inclut les sites d'archéologie connus ; seulement dans les chunks chargés. | 30 s |
+| `vex` | Prévisualise le placement du bloc tenu ; Lenteur dure 20 % moins longtemps. | Une action de placement ou de minage avec les vrais blocs et outils, à portée normale +2. Maj pour miner ; rang de construction ou minage acquis requis. | 15 s |
+| `witch` | Les potions bénéfiques bues durent 10 % plus longtemps, sans niveau ni soin instantané renforcé. | Consomme la potion bénéfique tenue et rend le flacon. Brume mobile à 4 blocs, rayon 1,5, pendant 6 s : une dose native, une personne consentante. | 30 s |
+| `wither_skeleton` | L'effet Wither reçu dure 20 % moins longtemps. | Prépare un vrai coup sous 6 s : jusqu'à 3 PV de déclin sur 4 s et soins réduits de 20 %. Boss exclus ; limites PvP communes. | 30 s |
+| `zombie_nautilus` | Réduit le recul de 25 % dans l'eau. | Ouvre 6 s un point d'extraction aquatique de rayon 2. Maintenir Maj pour rejoindre une eau libre plus haute à 6 blocs ; 4 personnes, budget de mouvement personnel. | 45 s |
+| `ender_dragon` | Contrôle aérien amélioré de 25 % ; impacts contre une paroi et projectiles en vol réduits de 20 %. | Annonce de 1,5 s puis souffle devant soi pendant 5 s : rayon 3 à 4 blocs, 4 ennemis, jusqu'à 8 PV chacun. Terrain intact, aucun vol accordé. | 90 s |
+| `warden` | Localise les vibrations de créatures en mouvement à 8 blocs, sans révélation de l'Atlas. | Onde frontale annoncée 2 s, portée 12 : 3 cibles visibles, jusqu'à 10 PV. Ne traverse pas les murs ; dissipe les projectiles hostiles sur la ligne. | 90 s |
+| `wither` | Réduit le recul de 25 % sous 30 % de vie. | Après 1,5 s d'annonce, salve de 3 crânes lents à 16 blocs. Budget total de 12 PV par ennemi, 2 en PvP ; terrain intact. Vol de vie limité par le budget commun de soin. | 100 s |
+
+## Vérification de la livraison
+
+Implémentation locale beta.032 : build, 85 tests serveur, parcours client natif
+et export MRpack vérifiés. Les 88 recharges et textes du catalogue correspondent
+aux ressources du binaire. Le [ticket de livraison](spawn-eggs-v2-beta032.md)
+détaille les contrôles et la migration. Les essais de plaisir et d’équilibre
+sur une séance de 20 minutes restent à faire avec les joueurs.
diff --git a/docs/spyglass-stars-beta047.md b/docs/spyglass-stars-beta047.md
new file mode 100644
index 0000000..aa1f2b6
--- /dev/null
+++ b/docs/spyglass-stars-beta047.md
@@ -0,0 +1,43 @@
+# beta.047 — lecture du ciel à la longue-vue
+
+Branche codex/spyglass-star-info-beta047.
+
+Regarder une étoile dans la longue-vue affiche son nom dérivé de son souvenir,
+son origine et sa date UTC, ainsi que les constellations auxquelles elle appartient.
+Les constellations portent le nom de leur étoile mère et indiquent leur auteur.
+Plusieurs appartenances se parcourent à la molette. Aucun achat supplémentaire.
+
+Clic droit permet toujours d’observer. Maj + clic droit annule désormais le
+dernier trait ; le clic gauche continue de dessiner sans avoir à zoomer.
+Observer ne dépense pas d’XP et ne crée ni ne supprime de souvenir.
+
+Les données viennent des journaux serveur existants. Aucune migration de sauvegarde,
+aucune modification des positions des étoiles ou des anciens dessins. Un paquet
+additif de consultation transmet uniquement les informations de l’étoile visée,
+avec limitation de fréquence et cache client. Le ciblage utilise les positions
+mises en cache, pas un recalcul du ciel à chaque image.
+
+Les boutons actifs affichent leur libellé en jaune vif au survol. Les boutons
+désactivés gardent leur style natif.
+
+Vérifié sur Minecraft 26.3-pre-2 : `check build assemblePack assembleTestPack`,
+12 GameTests ciblés, parcours client natif FR/EN (survol actif/inactif, retour de
+couleur, observation sans suppression, noms traduits, deux appartenances,
+pagination, retrait Maj + clic droit, maintien de l'XP et effacement du panneau).
+Le ciblage normalise le rayon natif pour rester précis au fort zoom.
+
+Les lectures nécessitent la longue-vue réellement utilisée. Elles sont limitées
+à dix requêtes par seconde côté serveur ; le client émet au plus cinq requêtes
+par seconde et conserve 64 fiches. Les index sont invalidés lorsque les journaux
+changent. Les constellations ont un nom dérivé de leur étoile mère ; leur
+renommage n'est pas ajouté dans ce ticket.
+
+[Pack normal](../build/Sanctuary-beta.047.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.047.mrpack).
+Reçu d'intégrité : `build/star047-artifact.json`. Les 1 514 classes, ressources,
+icônes et dépendances imbriquées ont été comparées au build. Les données de
+terrain et les archives beta.046 restent inchangées. Aucun déploiement personnel
+ou public, aucune acceptation d'EULA de serveur dédié.
+
+Captures : `build/star047-evidence/0000_sanctuary-beta047-hover-yellow.png`,
+`0001_sanctuary-beta047-star-en.png`, `0002_sanctuary-beta047-star-fr-page-2.png`.
diff --git a/docs/starter-name-beta052.md b/docs/starter-name-beta052.md
new file mode 100644
index 0000000..36a4976
--- /dev/null
+++ b/docs/starter-name-beta052.md
@@ -0,0 +1,51 @@
+# beta.052 — nom du familier de départ
+
+Branche `codex/starter-name-beta052`. Contrat avant modification : Hello World
+ajoute un champ « Nom du familier » sous les trois œufs centrés. Le libellé reste
+aligné aux autres champs. Le nom est facultatif, limité à 32 caractères, sans
+codes de mise en forme ni caractères de contrôle. Un champ vide conserve le nom
+natif de l'espèce. Le serveur valide puis enregistre ensemble l'espèce et le nom
+avant l'introduction. L'œuf reçoit le composant natif CUSTOM_NAME : le familier,
+sa fiche et les renommages ultérieurs utilisent le fonctionnement déjà en place.
+
+## Contrat de migration
+
+Le registre `data/sanctuary-starters.json` passe du schéma 1 au schéma 2 : chaque
+offre gagne `name`, vide pour les anciennes entrées. La lecture ne réécrit rien.
+À la première modification, l'ancien fichier valide est conservé à l'identique
+dans `sanctuary-starters.json.schema1.bak` avant l'écriture atomique du schéma 2.
+Un fichier invalide ou une sauvegarde incompatible bloque l'écriture, sans remise
+à zéro. Les choix, UUID, graines et reçus de remise restent identiques ; aucun
+cadeau ni renommage rétroactif. Pour revenir à beta.051, conserver le registre 2
+puis restaurer la sauvegarde 1 ; les choix faits depuis la migration seraient
+alors perdus. Aucun monde personnel n'est ouvert ou migré pendant ces travaux.
+
+Un nouveau message de configuration `sanctuary:starter_named_create_v1`
+transporte le nom ; les anciens messages gardent leur format. Les clients
+anciens peuvent encore créer un familier sans nom personnalisé sur ce serveur.
+La saisie reste lors d'un redimensionnement ou d'un refus de couleur ; après
+validation, reconnexion et introduction interrompue conservent le choix nommé.
+
+## Vérifications
+
+`./gradlew check build assemblePack assembleTestPack` avec
+`-PsanctuaryFocusedTests=starter052,starter -PsanctuaryQuickTests=true` : succès,
+**8 tests serveur**. Migration 1 → 2 et sauvegarde originale vérifiées, refus des
+noms invalides sans mutation, choix définitif idempotent, sérialisation du
+message, nom natif sur l'œuf et l'entité, renommage ultérieur préservé. Les tests
+du tirage, de la remise unique, de la mort et du prestige restent réussis.
+
+`runClientGameTest` avec `-PsanctuaryClientTests=true
+-PsanctuaryStarterNameClientTests=true -PsanctuaryQuickTests=true` : succès.
+Hello World à froid sans joueur ni monde, FR/EN, interfaces 854 × 480 et
+427 × 240, saisie conservée lors du redimensionnement et d'un refus de couleur,
+admission réelle sur serveur intégré, nom dans la fiche habitant, sauvegarde et
+reconnexion sans nouvel œuf. Captures relues : `build/starter052-evidence/`.
+
+Journaux : `build/starter052-delivery.log`, `build/starter052-client.log`.
+Reçu : `build/starter052-artifact.json` ; 1 535 classes et ressources conformes
+au build. Archives beta.051, textures, JEI et données de génération préservés.
+[Pack normal](../build/Sanctuary-beta.052.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.052.mrpack).
+Connexion à plusieurs clients distants restant à vérifier. Aucun déploiement
+sur le canal public ou l'instance personnelle.
diff --git a/docs/steve-anomalies.md b/docs/steve-anomalies.md
new file mode 100644
index 0000000..5dd5750
--- /dev/null
+++ b/docs/steve-anomalies.md
@@ -0,0 +1,250 @@
+# ANO-01 — Les doubles de Steve
+
+**Conception ajoutée le 15 septembre 2026. Non implémentée.** Les trois doubles
+sont des anomalies de Sanctuary. Leurs comportements sont distincts : Herobrine
+harcèle les explorateurs et disparaît lorsqu'il est vu ; le mineur fantôme
+creuse des galeries ; le Steve bugué reste comme une preuve matérielle de
+l'existence de Steve.
+
+Branche de conception réservée : `codex/steve-anomalies-design`.
+Ce dossier prolonge [BR-01](backrooms-implementation.md) et la
+[cosmologie](cosmologie.md). Il prépare trois lots vérifiables ; il ne déclenche
+pas leur implémentation et ne consomme pas de numéro `beta.xxx`.
+
+## 1. Règles données par le créateur
+
+| Double | Présence | Apparence et comportement retenus |
+| --- | --- | --- |
+| **Herobrine** | Caché dans les Backrooms | Frappe le joueur, place des blocs, l'enferme, avance vers lui ; peut se placer derrière lui et attendre qu'il se retourne. Disparaît au moment où le joueur le voit. |
+| **Mineur fantôme** | **Overworld et Backrooms**, confirmé pendant la conception | Creuse des galeries surtout dans les chunks les moins marqués par Demeure. De face comme de dos, on voit l'arrière de sa tête. |
+| **Steve bugué** | Apparition **extrêmement rare** ; lieux d'apparition à préciser | Steve en T-pose, avec des UV mal placés, donc des fragments de texture au mauvais endroit sur son corps. Une fois trouvé, il reste : on peut le mettre dans un bateau et le déplacer. Il constitue une preuve que Steve existe. |
+
+La description du mineur retient la correction orale finale : arrière de la
+tête visible de face et de dos. Elle ne demande pas deux visages frontaux.
+
+Les trois anomalies sont liées à Steve dans la fiction. Leur origine exacte,
+leurs relations entre elles et ce que Steve en sait restent à raconter ; ce
+dossier ne leur attribue ni machine créatrice ni explication définitive.
+
+## 2. Place dans les prochains développements
+
+| Lot proposé | Résultat vérifiable | Dépendances | Branche prévue |
+| --- | --- | --- | --- |
+| **ANO-01A — Herobrine** | Une rencontre dans les Backrooms comporte une action physique puis une disparition lorsque l'apparition est vue. | BR-01 jouable, donc suivi du ballast livré auparavant ; contrat de modification des blocs. | `codex/herobrine-backrooms` |
+| **ANO-01B — Mineur fantôme** | Le mineur choisit préférentiellement les zones peu demeurées et y laisse des galeries persistantes. | Demeure et contrat de creusement ; BR-01 pour la partie Backrooms. Un prototype Overworld peut être vérifié séparément. | `codex/ghost-miner` |
+| **ANO-01C — Steve bugué** | Un individu rare est découvert, embarqué, déplacé puis retrouvé après redémarrage. | Entités persistantes, rendu et bateaux ; BR-01 seulement si les lieux d'apparition retenus l'exigent. | `codex/glitched-steve` |
+
+BR-01 garde son résultat initial : générer les lieux et permettre l'aller-retour.
+L'absence des doubles ne bloque pas la validation de ce socle. Leur activation
+vient ensuite, avec leurs propres critères et notes de livraison.
+
+## 3. ANO-01A — Herobrine dans les Backrooms
+
+### Rencontre attendue
+
+Herobrine peut se manifester à travers plusieurs gestes : frapper, avancer,
+poser des blocs pour entraver ou enfermer, se tenir derrière un joueur et
+attendre son regard. Chaque rencontre peut en employer un ou plusieurs ; tous
+les gestes doivent pouvoir être éprouvés pendant le développement.
+
+Exemple de mise en scène proposé : le joueur poursuit son exploration après un
+coup ou découvre un passage récemment fermé ; Herobrine attend derrière lui.
+Quand le joueur se retourne et le voit, l'apparition disparaît. Les dégâts et
+les changements de blocs sont de vrais événements du monde.
+
+### Contrat technique proposé
+
+- Une machine d'états serveur décrit l'apparition, sa cible, son action, sa phase
+ d'attente du regard et sa disparition. Les intervalles et budgets de rencontres
+ sont réglables ; ils ne sont pas fixés arbitrairement dans cette conception.
+- Définir « vu » par un observateur actif, une direction de vue et une ligne de
+ visibilité non obstruée. Un regard à travers un mur ne déclenche pas la
+ disparition. Vérifier le comportement avec les caméras et zooms pris en charge.
+- En multijoueur, proposition de départ : le premier observateur admissible qui
+ voit l'apparition déclenche sa disparition pour tous. Le client peut signaler
+ une observation ; le serveur en valide les conditions et décide du résultat.
+- La disparition interrompt les attaques et placements encore en attente. Une
+ attaque déjà résolue n'est pas appliquée une seconde fois à la reprise.
+- Les blocs placés et les effets accomplis suivent leur propre persistance.
+ La disparition ne restaure pas automatiquement une ancienne copie du terrain.
+- L'enfermement doit pouvoir se produire réellement. Fixer avant le code les
+ blocs autorisés, leur provenance/drops, la quantité par rencontre et les moyens
+ d'en sortir. Aucun écrasement de conteneur ou placement dans le corps d'un
+ joueur n'est nécessaire pour obtenir cet effet.
+- Proposer des rencontres hors du volume d'arrivée protégé des salles personnelles,
+ en cohérence avec BR-01. La portée exacte de cette protection face à Herobrine
+ doit être inscrite dans le contrat du lot avant son activation.
+
+La cadence, les dégâts, le caractère éventuellement mortel, les sons, la
+distance d'approche et la possibilité de combattre Herobrine restent à régler.
+Le comportement retenu ne doit pas être réduit à un effet visuel sans coup
+ni modification réelle de l'environnement.
+
+### Critères d'acceptation futurs
+
+- [ ] Démontrer chaque geste demandé : coup, marche/approche, placement,
+ enfermement et attente derrière le joueur.
+- [ ] Lors du retournement, l'apparition est perceptible puis disparaît dès
+ l'observation reconnue, sans attaque différée après sa disparition.
+- [ ] Observer derrière un mur n'a pas le même résultat qu'une vue dégagée ;
+ deux clients partagent un résultat cohérent.
+- [ ] Les blocs réellement placés survivent conformément au contrat, sans
+ effacer des modifications ultérieures des joueurs lors de la fin de rencontre.
+- [ ] Annulation, déconnexion, déchargement et redémarrage ne rejouent ni coup
+ ni placement ; les limites de rencontres restent effectives.
+
+## 4. ANO-01B — Le mineur fantôme
+
+### Apparence et comportement
+
+Le modèle montre l'arrière de la tête sur ses faces avant et arrière. Les côtés,
+l'animation de creusement et les accessoires restent à concevoir. Ce défaut
+visuel appartient au modèle et doit être cohérent pour deux observateurs placés
+de part et d'autre ; il ne résulte pas d'une tête tournée artificiellement pour
+chaque caméra.
+
+Le mineur avance en creusant des galeries dans l'Overworld et les Backrooms.
+Il laisse une transformation physique du terrain. L'apparition ne dépend pas
+du fait qu'un joueur soit en train de regarder le creusement.
+
+### Choisir les chunks les moins demeurés
+
+Le mod autonome [Demeure](demeure-beta006.md) fournit déjà des influences par
+dimension et chunk. Il conserve notamment des scores de joueurs, d'animaux,
+de villageois, de monstres et de nature. Un pourcentage dominant ou l'étiquette
+« abandonné » ne mesure pas à lui seul la quantité d'habitation humaine.
+
+Proposition pour le premier prototype :
+
+1. Lire les empreintes des chunks candidats dans un voisinage borné, déjà
+ disponible ; ne pas parcourir ni charger toute la carte pour chercher un
+ minimum mondial.
+2. Comparer la **somme des scores effectifs des sujets joueurs**, à date commune,
+ et favoriser les scores faibles. Conserver « surtout » comme une préférence
+ pondérée, dont la force sera réglée pendant les essais.
+3. Distinguer une zone sans empreinte enregistrée d'un service indisponible.
+ Documenter la couverture historique et la politique des zones non relevées ;
+ une panne ne vaut jamais un score nul. Un monde ancien sans suivi n'est pas
+ réputé vierge par défaut.
+4. Réévaluer le trajet aux limites de chunks et avant chaque courte séquence
+ de creusement, pour tenir compte d'un secteur qui devient habité.
+5. Garder la politique de ciblage dans Sanctuary. Demeure reste responsable de
+ ses mesures et ne reçoit pas de règle propre au mineur.
+
+### Creuser et conserver les traces
+
+- Traiter le creusement comme l'action d'une entité dans le monde chargé,
+ avec cadence, portée et volume bornés ; aucune régénération de chunk.
+- Définir matériaux creusables, drops, traitement des minerais, eau/lave,
+ conteneurs, constructions et protections avant activation. Les permissions
+ explicites et les exclusions de terrain sont vérifiées indépendamment du score
+ Demeure : une faible empreinte ne constitue pas un droit de démolition.
+- Réserver des séquences courtes et reprendre depuis l'état sauvegardé. Recharger
+ le mineur ne doit pas creuser de nouveau une ancienne séquence ni redonner ses
+ éventuels drops. Aucun rattrapage de creusement hors ligne n'est proposé.
+- Les galeries sont sauvegardées comme modifications normales du monde. Dans
+ les Backrooms, le générateur ne les rebouche pas en rechargeant son ancien plan.
+- Attribuer l'action au mineur. Ne pas créditer un joueur de minage, d'XP ou de
+ Demeure à sa place. Si le ballast reçoit un événement, son origine « anomalie »
+ et la règle de contribution doivent venir du contrat économique.
+
+La fréquence d'apparition, la forme et la longueur des galeries, les interactions
+avec le mineur et la persistance de l'entité elle-même restent à concevoir.
+Ses galeries, une fois creusées, sont persistantes.
+
+### Critères d'acceptation futurs
+
+- [ ] Le rendu montre l'arrière de la tête de face et de dos, y compris pour
+ deux joueurs simultanés et pendant le creusement.
+- [ ] La sélection contrôlée favorise les scores humains faibles ; tester
+ scores égaux, activité répartie entre plusieurs joueurs, influence animale,
+ service indisponible et zones sans historique.
+- [ ] Un parcours est vérifié dans l'Overworld puis dans les Backrooms. Un score
+ associé à une dimension ne contamine pas le classement de l'autre.
+- [ ] Les galeries existent réellement, respectent les contrats de terrain et
+ subsistent après déchargement/redémarrage sans doublon de drops.
+- [ ] Une nouvelle habitation influe sur les choix suivants. Le mineur ne crée
+ pas d'activité personnelle artificielle et n'ouvre pas une boucle de ballast.
+
+## 5. ANO-01C — Le Steve bugué, preuve transportable
+
+### Identité visuelle et découverte
+
+Le Steve bugué est **extrêmement rare**, en T-pose avec un placement volontairement
+incorrect des UV : les UV indiquent quelle portion de texture couvre chaque
+face du modèle. Cette mauvaise correspondance doit être visible et rester
+identique après rechargement ; elle fait partie de l'apparence de l'individu.
+
+Une fois découvert, il demeure dans le monde et peut être montré aux autres.
+Le trouver ne se résout pas par sa disparition ou une récompense qui le remplace.
+Il peut monter dans un bateau et être déplacé avec celui-ci. Son rôle narratif
+est de fournir une preuve durable que Steve existe.
+
+### Contrat proposé de persistance et de transport
+
+- Donner à l'individu une identité persistante. Une fois découvert, le soustraire
+ au despawn naturel lié à la distance, au temps ou au départ de son découvreur.
+- Sauvegarder position, dimension, variante visuelle, état de découverte et lien
+ de passager. Le changement de chunk, le déchargement du bateau et le redémarrage
+ conservent le même individu, sans le réinvoquer à son ancien emplacement.
+- Vérifier embarquement, déplacement et débarquement avec les bateaux du pack.
+ Conserver la signature en T-pose une fois embarqué et adapter le placement pour
+ un résultat lisible. Un bateau cassé libère l'individu sans le supprimer.
+- Le départ ou le changement de nom du découvreur n'efface pas l'entité. Deux
+ joueurs peuvent constater et déplacer le même individu selon les interactions
+ de transport ordinaires, sans exclusivité de propriété ajoutée implicitement.
+- Les tentatives d'apparition rare doivent avoir une identité et une fréquence
+ maîtrisées : recharger un chunk ne relance pas une loterie illimitée et ne
+ recopie pas un individu déjà découvert.
+
+**Persistance ne signifie pas invulnérabilité.** Résistance aux dégâts, mort,
+chute dans le vide, nombre d'individus possibles et éventuels passages entre
+dimensions restent à fixer avant livraison. Les lieux d'apparition et la
+probabilité exacte restent également ouverts ; seule l'extrême rareté est
+décidée. Aucun chiffre ni dimension ne sont présentés ici comme approuvés.
+
+### Critères d'acceptation futurs
+
+- [ ] T-pose et UV déplacés sont identifiables et stables à pied et en bateau.
+- [ ] Un individu découvert est encore présent après éloignement, départ de
+ tous les joueurs, déchargement et redémarrage.
+- [ ] Embarquement, transport sur plusieurs chunks, débarquement et destruction
+ du bateau conservent l'identité et produisent exactement un individu.
+- [ ] Deux clients constatent la même présence et le même déplacement ; changer
+ de découvreur ou quitter le serveur ne provoque pas de disparition.
+- [ ] Les essais démontrent le mécanisme de rareté et l'absence de nouveau tirage
+ induit par une simple reconnexion ou un rechargement répété.
+
+## 6. Terrain, sauvegardes et livraison
+
+Les choix proposés sur les blocs, les dégâts et les protections complètent les
+idées du créateur ; leurs valeurs doivent être établies dans chaque lot avant
+le code qui les applique. Les coups, poses et creusements restent des actions
+réelles. La conservation des secteurs de BR-01 interdit leur régénération
+silencieuse, tout en permettant ces modifications de gameplay sauvegardées.
+
+Développer d'abord sur des mondes neufs ignorés. Avant activation sur un monde
+existant, documenter le contrat d'adoption : périmètre des mutations autorisées,
+couverture Demeure, identités des anomalies, actions en cours et devenir des
+entités lors d'une désactivation. Aucun monde personnel n'est modifié par ce
+dossier. Le stockage autonome de Demeure reste sous sa responsabilité.
+
+Pour chaque livraison de code :
+
+- vérifier les API de la version Minecraft exacte, les identifiants
+ `sanctuary:*` retenus et les modèles/textures effectivement utilisés ;
+- ajouter les libellés FR/EN utiles et préserver l'absence de coordonnées des
+ Backrooms dans les messages et interfaces ordinaires ;
+- effectuer les tests serveur ciblés, des essais de rendu à deux clients et les
+ reprises de sauvegarde ; archiver graines, positions opérateur et paramètres ;
+- lancer `./gradlew check build`, puis `./gradlew assemblePack` si la distribution
+ change ; synchroniser le prochain `beta.xxx` selon [la convention](versioning.md) ;
+- indiquer ce qui est réellement livré, ses preuves et les limites restantes.
+
+## 7. Références du socle
+
+- [BR-01 — réseau, salles personnelles et retour](backrooms-implementation.md).
+- [Demeure — portée des empreintes](demeure-beta006.md).
+- [Service Demeure](../mods/demeure/src/main/java/fr/koka/demeure/DemeureService.java)
+ et [scores par chunk](../mods/demeure/src/main/java/fr/koka/demeure/FootprintStore.java).
+- [Cosmologie](cosmologie.md) et [vision](vision.md).
diff --git a/docs/strength-carry-beta055.md b/docs/strength-carry-beta055.md
new file mode 100644
index 0000000..93f4c17
--- /dev/null
+++ b/docs/strength-carry-beta055.md
@@ -0,0 +1,48 @@
+# beta.055 — Force et joueurs portés
+
+Branche `codex/strength-carry-beta055`. L'effet natif Force réduit le poids
+ressenti des joueurs au-dessus du porteur, à tous les étages de sa pile.
+Force I divise cette charge par deux, Force II par trois, et ainsi de suite.
+La vitesse vaut `max(0,25 ; 0,75^(adultes + joueurs / (1 + niveau de Force)))`.
+Sans Force, le dénominateur vaut un et le comportement existant est conservé.
+
+Avec un joueur : 75 % de vitesse normale sans Force, environ 86,60 % avec
+Force I, 90,86 % avec Force II. Avec deux joueurs : 56,25 %, 75 % et 82,55 %.
+Force facilite le portage sans rendre un porteur plus rapide qu'à vide.
+Les autres modificateurs de mouvement, dont Vitesse et Lenteur, se composent
+normalement avec cette pénalité.
+
+Seule la Force du porteur concerné compte. La Force d'un passager ne se
+transmet pas à celui qui le porte. Les animaux adultes gardent leur poids,
+les bébés restent exempts, et la masse physique des bateaux reste inchangée.
+Les règles de collisions, chute et démontage ne changent pas.
+
+Le recalcul reste côté serveur, au prochain tick après ajout, changement ou
+expiration de Force. Le modificateur temporaire existant est synchronisé par
+Minecraft ; il est retiré dès que le porteur n'a plus de charge.
+Aucune nouvelle interface, texture, donnée persistante ou migration de monde.
+
+## Vérifications
+
+`Strength055ClientChecks` passe sur Minecraft 26.3-pre-2 : Force I, II et
+amplificateur 255 ; un puis deux joueurs portés ; effet personnel du passager ;
+expiration native ; composition avec Vitesse I ; synchronisation effective
+avec le client ; animaux adultes/bébés ; suppression du modificateur au
+démontage. Un seul modificateur temporaire reste présent pendant les changements
+successifs d'effets. Le déplacement à vide ne reçoit aucun bonus de portage.
+Journal : `build/strength055-client.log` (marqueur `STRENGTH055_PASS`).
+Les tests utilisent uniquement un monde plat de développement dans un client
+intégré, sans serveur dédié ni modification d'un monde personnel.
+
+`./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest`
+réussit. Le parcours natif a été exécuté séparément avec
+`-PsanctuaryClientTests=true -PsanctuaryStrengthCarryClientTests=true
+-PsanctuaryQuickTests=true`. Aucun EULA de serveur dédié n'a été accepté.
+
+Les deux archives beta.055 sont vérifiées contre les classes compilées. Seule
+`CarryService.class` diffère de beta.054 dans le code du mod principal ;
+les autres ressources, les traductions, JEI et les anciennes archives sont
+préservés. Le reçu se trouve dans `build/strength055-artifact.json`.
+
+[Pack normal](../build/Sanctuary-beta.055.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.055.mrpack).
diff --git a/docs/swimming-recipebook-beta017.md b/docs/swimming-recipebook-beta017.md
new file mode 100644
index 0000000..0cabb6f
--- /dev/null
+++ b/docs/swimming-recipebook-beta017.md
@@ -0,0 +1,146 @@
+# beta.017 — aptitudes Nage/S’allonger/Repos et retrait du livre de recettes
+
+Ticket du 13 septembre 2026, branche `codex/swimming-recipebook-beta017`.
+Contrat préalable aux changements de progression et d'interface.
+
+## Nage
+
+Nouvelle aptitude achetable `swimming`, libellés **Nage / Swimming**, coût par
+défaut **4 niveaux** réglable par `swimmingCost` côté serveur. Elle reste dans
+la liste des aptitudes et suit leur conservation générale après mort et New Game+.
+Les six compétences au maximum suffisent toujours au prestige.
+
+Sans cette aptitude, les joueurs Sanctuary en survie/aventure gardent les
+mouvements lents dans l'eau, la flottabilité et la possibilité de remonter.
+Le sprint aquatique et la posture de nage rapide sont refusés sur les deux
+côtés logiques, y compris en entrant dans l'eau alors qu'on court. Sur terre,
+le sprint reste disponible selon la faim. Après achat, le sprint aquatique
+suit les règles de faim existantes : plus de six points de nourriture.
+Respiration, bulles vanilla, bateaux, rampement dans les petits espaces et
+mouvements des animaux restent inchangés. Créatif/spectateur et joueurs sans
+progression Sanctuary gardent leur comportement natif.
+
+## S’allonger
+
+Nouvelle aptitude `lying`, **S’allonger / Lie down**, 4 niveaux par défaut,
+configurable par `lyingCost`. **W** bascule la position et se configure dans
+Contrôles → Sanctuary. Le défaut résout la lettre W selon la disposition active
+du clavier, y compris AZERTY ; les raccourcis personnalisés restent prioritaires.
+Elle autorise la posture basse native et le déplacement
+lent au sol. La touche relève le personnage quand l'espace le permet ; les
+collisions natives gardent la posture basse sous un plafond trop bas.
+Pas de sommeil, soin ou changement d'heure. L'entrée dans l'eau, le vol,
+le véhicule, le lit ou la mort arrêtent la position volontaire.
+
+La posture temporaire utilise `sanctuary:lying` synchronisé du serveur aux
+clients qui voient le joueur, sans persistance ni copie à la mort. L'aptitude,
+elle, reste dans l'historique et après le prestige. La requête réseau contient
+un état souhaité et une révision ; le serveur vérifie l'achat et la position
+au sol avant de l'accepter. En créatif, on peut tester la posture sans achat.
+
+## Se reposer
+
+Aptitude distincte `resting`, **Se reposer / Rest**, 4 niveaux par défaut, coût
+`restingCost`. **Maj + W** (Maj + la touche configurée pour S’allonger) commence
+ou arrête le repos. W seul quitte également toute position basse. V reste libre
+pour le vocal. Le repos permet sa propre posture immobile, sans imposer un autre
+achat ; S’allonger autorise séparément la posture mobile au sol.
+
+Survie/aventure : toutes les **200 ticks serveur (10 secondes à 20 TPS)**,
+récupérer **1 point de vie (un demi-cœur)** contre **1 point de faim (une demi-barre)**.
+Aucun soin si la santé est pleine ou si la faim est vide ; aucune consommation
+sans soin. Le repos n'affecte ni le temps ni le point de réapparition. Bouger,
+recevoir des dégâts, quitter le sol ou entrer dans une situation incompatible
+interrompt le repos. Le délai recommence à chaque nouvelle séance, sans soin
+immédiat en répétant la touche. Le créatif permet de tester la posture sans
+produire de soin artificiel. La régénération naturelle de Minecraft garde ses règles.
+
+### Migration additive
+
+`ProgressRecord` passe de 8 à 9 et ajoute `swimming: false`, `lying: false` et `resting: false` aux anciens habitants,
+sans changer leurs achats, XP, compétences, aptitudes, révision ou prestige.
+L'achat ajoute une preuve dans l'historique ; le New Game+ la conserve.
+Les événements historiques du journal de cycle restent intacts, leurs données
+de progression sont lues avec la migration existante étendue. Aucun ancien
+journal n'est réécrit. Les états inconnus/corrompus restent refusés.
+
+La configuration de progression passe de 4 à 5 : ajouter `swimmingCost: 4`, `lyingCost: 4` et `restingCost: 4`,
+conserver tous les autres réglages. Écriture atomique uniquement lors de cette
+migration ou création initiale. Capacité réseau `sanctuary:swimming_v1` :
+client et serveur beta.017 ensemble. Requête `sanctuary:lying` pour la posture (0 debout, 1 allongé, 2 repos). Aucun monde personnel n'est modifié.
+
+## Livre de recettes
+
+Quand le mod est installé, retirer le bouton et le panneau natifs des écrans
+Inventaire, Établi, Four, Haut-fourneau et Fumoir, y compris un ancien réglage
+« livre ouvert ». Retirer aussi les recettes fantômes du panneau et les toasts
+qui demandent de consulter le livre. Conserver les grilles, les sorties de
+fabrication, les recettes serveur, leurs statistiques et les advancements.
+La fabrication manuelle reste libre, selon la règle de jeu existante.
+
+L'intégration d'une version modifiée de Just Enough Items révélant ses recettes
+selon les advancements Minecraft constitue la direction suivante ; ce ticket
+n'installe pas JEI et ne prétend pas implémenter ce catalogue. Son intégration
+exige une version compatible avec Minecraft 26.3-pre-2 et son propre contrat
+révélation/catalogue. Le Blocodex reste utilisable.
+
+## Prix des compétences
+
+Le texte `1/7` (ou `3/10`) à côté de chaque jauge ASCII est remplacé par le prix
+serveur du prochain rang, par exemple `4 niv.` / `4 lv.`. Au maximum : `Max.`.
+La jauge conserve la position actuelle, le bouton + garde son contrôle d'XP et
+le bouton − reste réservé aux opérateurs. Les colonnes partagent la largeur
+nécessaire aux prix de la configuration, y compris dans une petite interface.
+
+## Son des Premiers pas
+
+Chaque étape récompensée par de l'XP joue le son vanilla de ramassage d'expérience
+pour le joueur concerné. Le serveur envoie ce son avec le gain effectif : pas de
+répétition sur une étape déjà validée, à la reconnexion ou si la récompense vaut
+zéro. Les réglages audio de Minecraft restent appliqués. Aucun nouveau fichier
+sonore, attachement ou message réseau Sanctuary n'est nécessaire.
+
+## Livraison et vérifications
+
+Le 13 septembre 2026, avec Java 25, Minecraft **26.3-pre-2**, Fabric Loader
+**0.19.5** et Fabric API **0.160.0+26.3** :
+
+- `./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,mining,building,inventory,cycle,inventoryflow,sorting,tutorial,movement` :
+ contrôles purs et **62 tests serveur réussis**, 4 min 24 s ;
+ journal `build/movement017-check-build.log`.
+- Après les derniers ajouts son/clavier,
+ `./gradlew check build assemblePack -PsanctuaryFocusedTests=progression,tutorial,movement` :
+ **15 tests serveur réussis**, tous les contrôles purs et assemblage validés,
+ 4 min 20 s ; `build/movement017-final-build.log`.
+- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true -PsanctuaryMovementClientTests=true` :
+ parcours client natif réussi en 1 min 33 s ; `build/movement017-final-client.log`.
+ Monde jetable de graine **42**, capture des interfaces FR/EN : achat des trois
+ aptitudes, déplacement lent et remontée avant achat, nage rapide après achat,
+ W selon la disposition du clavier, Maj+W, soin après dix secondes, interruption
+ par mouvement, remappage sur G, absence de conflit avec le double-Maj pour
+ s'asseoir, cinq écrans sans livre à deux échelles et fabrication manuelle réelle.
+ Le client reçoit un son d'XP à l'ouverture de l'inventaire et un autre lors de
+ la fabrication des planches ; vingt répétitions du signal n'en produisent aucun.
+ Les six lignes affichent le prix serveur du prochain rang, sans ratio redondant.
+
+Les tests serveur utilisent un monde de développement de graine **0** et couvrent
+aussi les paquets périmés/refusés, les coûts, la faim, les délais de repos,
+l'absence de changement d'heure, les dégâts, les modes de jeu, les données
+temporaires non sauvegardées et la conservation des aptitudes après mort/New Game+.
+Les contrôles purs vérifient les migrations additives, les réglages personnalisés
+et le refus des nouveaux champs corrompus. Les premiers essais de clic client
+utilisaient le code souris 0 ; la version native exige `MOUSE_BUTTON_LEFT` (1).
+La correction porte sur le test ; la fabrication native était fonctionnelle.
+
+Export local : [Sanctuary-beta.017.mrpack](../build/Sanctuary-beta.017.mrpack),
+**2 567 056 octets**. Vérificateur `build/verify-beta017-pack.py`, reçu
+`build/movement017-artifact.json` : ZIP/CRC, versions internes, JAR et Demeure
+imbriqué identiques aux builds, dépendances, mixins, textes FR/EN et six PNG
+identiques aux sources du resource pack. Les exports beta.003–016 restent identiques.
+
+SHA-256 MRpack : `4f07c8070aa2b3f5b256f070a236b0fd56b37d7ec8ddc974a30dc7dfc1f8a41a`.
+SHA-256 JAR : `058ce9f24ff15da2c362a92fef648deeaee9019e7f177c503dee096666429325`.
+
+Le pack contient les récompenses et textures beta.016 ; la respiration du HUD
+reste vanilla. Aucune installation personnelle ni canal packwiz publié n'a été
+modifié. JEI et la révélation de son catalogue par advancements restent à développer.
diff --git a/docs/test-rapide-beta033.md b/docs/test-rapide-beta033.md
new file mode 100644
index 0000000..394e241
--- /dev/null
+++ b/docs/test-rapide-beta033.md
@@ -0,0 +1,88 @@
+# DEV-033 — Sanctuary Test, monde plat et intro passée
+
+Le petit module facultatif `sanctuary_test` dépend du vrai mod Sanctuary.
+Il permet de tester les fonctionnalités réelles sur un terrain plat natif,
+sans générer Sanctuary Island ni jouer les 29,5 secondes de cinématique.
+Client natif validé sur la graine `42`, Minecraft **26.3-pre-2**, Java 25.
+
+## Contrat du profil de développement
+
+Le pack normal n’embarque pas ce module. Avec le module présent côté serveur,
+les nouveaux mondes plats peuvent ouvrir la progression Sanctuary. La protection
+des mondes déjà habités reste la même : aucune activation implicite sur une
+ancienne sauvegarde qui n’a pas de contrat Sanctuary.
+
+Le preset `sanctuary_test:flat` remplit seulement 64 couches natives : une bedrock,
+60 pierres, deux terres et une herbe. Pas de structures, lacs ou décorations
+supplémentaires. Les dimensions Nether et End gardent leurs générateurs natifs,
+et ne génèrent leurs chunks qu’à leur visite. Graine libre ; aucun changement
+apporté au preset Sanctuary ni à sa génération.
+
+Le formulaire d’habitant et l’admission restent réels, suivis du même échange
+réseau d’introduction. Le serveur de test signale le mode rapide au client,
+qui accuse immédiatement la fin de l’intro. Pas de délai, musique ou flash.
+Sur un monde Sanctuary normal, l’intro complète reste active même si ce module
+est installé. `/sanctuary intro` permet toujours de revoir la cinématique.
+
+Le client propose le preset de test par défaut à la création d’un nouveau monde,
+sans réimposer ce choix après un changement volontaire ou un redimensionnement.
+Conserver le module pour jouer ses mondes de test. Ne pas confondre ceux-ci avec
+les mondes du pack normal ; aucune installation personnelle modifiée par les outils.
+
+## Lancer une partie d’essai
+
+Importer [Sanctuary-Test-beta.033.mrpack](../build/Sanctuary-Test-beta.033.mrpack)
+dans un lanceur compatible, puis créer un **nouveau monde**. Le type
+**Sanctuary — Test rapide** est présélectionné. Valider la fiche d’habitant :
+l’arrivée suit directement, sans cinématique. Choisir le mode de jeu et les
+commandes selon le test souhaité ; les règles de progression restent réelles.
+
+Le [pack normal beta.033](../build/Sanctuary-beta.033.mrpack) contient les mêmes
+corrections et fonctionnalités, sans le module facultatif. Le petit JAR
+`mods/sanctuary-test/build/libs/sanctuary-test-beta.033.jar` nécessite Sanctuary
+beta.033 ; ce n’est pas une copie autonome du jeu.
+
+## Développement
+
+`./gradlew :sanctuary-test:runClient` lance une instance isolée dans
+`mods/sanctuary-test/run/quick-test`. Créer un nouveau monde avec le preset de test.
+La fiche d’habitant n’apparaît qu’à la première arrivée, comme dans le pack normal.
+
+Les GameTests client peuvent ajouter `-PsanctuaryQuickTests=true` ; ils gardent
+les vrais mods et utilisent explicitement le preset plat. Leurs données restent
+dans les dossiers de développement ignorés. Les tests du terrain et de la
+cinématique complète continuent d’utiliser leurs profils habituels.
+
+## Vérifications du profil rapide
+
+Trois parcours client natifs ont créé un monde plat de graine `42`, conservé
+la création d’identité et ouvert les véritables menus Sanctuary. L’essai final
+vérifie aussi le preset présélectionné, l’absence d’écran Intro, le générateur
+`FlatLevelSource` et la session serveur de progression active.
+
+La création jusqu’à l’admission a pris **7 293 ms**, **8 935 ms**, puis **8 735 ms** sur cette
+machine, avec validation automatisée de la fiche. Ces mesures commencent après
+le chargement du client ; elles ne décrivent pas le démarrage du lanceur, la
+saisie humaine ou une garantie de durée sur d’autres machines.
+
+Commande de contrôle client :
+
+```sh
+./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true \
+ -PsanctuaryMenusClientTests=true -PsanctuaryQuickTests=true \
+ -PsanctuaryFocusedTests=menus
+```
+
+Journaux : `build/menus033-quick-client-final.log` et
+`build/menus033-quick-client-history.log` (avec le bouton History final). Le parcours normal conserve
+le preset Sanctuary et sa séquence d’introduction (`build/menus033-client2.log`).
+Le monde plat facilite les essais d’interfaces et de mécaniques ; les essais
+d’îles et d’expansions doivent conserver leur générateur Sanctuary.
+
+## Distribution vérifiée
+
+Le module mesure **21 504 octets** ; il est la seule différence de contenu
+entre les deux packs (outre le nom du manifeste). Le JAR Sanctuary est identique,
+ainsi que JEI et Demeure. Archives, métadonnées et classes compilées vérifiées ;
+reçu : `build/quick033-artifact.json`. Aucun serveur ni installation personnelle
+n’a été mis à jour. La distribution normale n’embarque pas `sanctuary_test`.
diff --git a/docs/title-menu-beta045.md b/docs/title-menu-beta045.md
new file mode 100644
index 0000000..cf67d05
--- /dev/null
+++ b/docs/title-menu-beta045.md
@@ -0,0 +1,46 @@
+# UI-045 — menu principal
+
+Branche `codex/title-menu-beta045`, à partir de beta.044.
+
+## Contrat
+
+Le menu principal affiche `Sanctuary beta.045` en haut à gauche. La version
+provient des métadonnées du mod installé et suit donc les futures livraisons.
+Les boutons Singleplayer, Multiplayer puis Friends / Amis ont chacun le format
+Minecraft normal de 200 × 20 pixels GUI. Friends prend la place de Realms.
+Les petites icônes Friends, Language et Accessibility disparaissent de cet écran.
+Options et Quit remontent sous Friends ; leur espacement et l'ordre clavier
+restent cohérents avec les trois boutons principaux.
+
+Friends ouvre le parcours natif de la liste d'amis, avec les restrictions du
+compte et sa confirmation éventuelle. Les options de langue et d'accessibilité
+restent accessibles dans Options. Les notifications Realms sont désactivées sur
+ce menu. Le menu pause et les règles de jeu restent identiques à beta.044.
+Aucune modification des textures, sauvegardes ou de la génération.
+
+## Vérification
+
+Le parcours client natif vérifie les libellés FR/EN, la largeur, l'espacement,
+l'ordre clavier et l'absence des anciennes entrées, ainsi que l'ouverture des
+options et du parcours Friends. Il ne valide aucune modification des paramètres
+sociaux du compte. Les réglages GUI 1 à 4 sont essayés ; Minecraft limite leur
+échelle effective à 2 dans la fenêtre de test de 854 × 480 pixels.
+Journal : `build/title045-client.log`. Captures : `build/title045-evidence/`.
+
+## Livraison
+
+`./gradlew check build assemblePack assembleTestPack
+-PsanctuaryFocusedTests=menus` réussit : contrôles purs du dépôt et **4 tests
+serveur** ciblés. Le parcours client FR/EN passe ; les captures du menu sont
+vérifiées visuellement après la fin du chargement des ressources.
+
+Les deux MRpacks sont exportés et contrôlés (archives, versions, dépendances,
+ressources et 1 471 classes compilées). Seule la nouvelle classe TitleMenuMixin
+s'ajoute aux classes de beta.044 ; les classes existantes, textures et données
+de jeu sont identiques. L'icône officielle fournie reste intacte.
+
+[Pack normal](../build/Sanctuary-beta.045.mrpack) ·
+[Monde plat rapide](../build/Sanctuary-Test-beta.045.mrpack).
+Reçu : `build/title045-artifact.json`. Journaux : `build/title045-delivery.log`
+et `build/title045-client.log`. Les exports beta.044 sont préservés.
+Livraison locale ; canal public et instance personnelle inchangés.
diff --git a/docs/tutorial-xp-beta016.md b/docs/tutorial-xp-beta016.md
new file mode 100644
index 0000000..0c29305
--- /dev/null
+++ b/docs/tutorial-xp-beta016.md
@@ -0,0 +1,98 @@
+# beta.016 — XP des premiers pas
+
+Ticket du 13 septembre 2026, branche `codex/tutorial-xp-beta016`.
+Contrat préalable à l'implémentation et à l'ajout de données persistantes.
+
+## Comportement et budget
+
+Les cinq actions de `TutorialSteps` dans Minecraft 26.3-pre-2 reçoivent une
+récompense personnelle de **8 points d'XP** chacune par défaut. Les 40 points
+font quatre niveaux depuis zéro sans autre gain ni dépense. Ouvrir l'inventaire
+suffit à pouvoir acheter le premier rang d'une compétence. Chaque action peut
+être accomplie indépendamment de l'ordre du tutoriel client.
+
+| Identifiant stable | Action validée |
+| --- | --- |
+| `movement` | Marcher au moins deux mètres et tourner la vue d'au moins 15° pendant la connexion |
+| `find_tree` | Observer un bloc du tag natif `completes_find_tree_tutorial`, ou obtenir/miner une bûche |
+| `punch_tree` | Miner réellement une première bûche (`logs`) en survie |
+| `open_inventory` | Ouvrir l'inventaire personnel |
+| `craft_planks` | Fabriquer des planches : statistique native de fabrication du tag `planks` |
+
+Un message personnel FR/EN annonce l'étape et les points reçus. Le tutoriel
+vanilla et ses préférences restent en place ; ces récompenses fonctionnent
+aussi quand Minecraft n'affiche plus ses conseils. Aucun changement des
+advancements Minecraft, aucun nouvel advancement Sanctuary pour ces gestes.
+
+Les étapes ne se récompensent qu'une fois par UUID dans ce monde : conserver
+leur état après reconnexion, mort et New Game+. Les points non dépensés suivent
+les règles existantes de mort. Créatif et spectateur n'accordent rien ; un
+opérateur jouant en survie suit les mêmes étapes que les autres habitants.
+Les anciennes statistiques ne déclenchent pas de paiement rétroactif : les
+habitants existants peuvent accomplir ces actions une fois après la mise à jour.
+
+## Autorité et migration
+
+Le serveur attribue les XP et constate les déplacements/statistiques, la vue
+des blocs et la fabrication. L'ouverture de l'inventaire est un écran local :
+le client la signale, et le serveur vérifie que le joueur est vivant, en survie
+ou aventure, dans son propre menu d'inventaire, avec une progression active.
+Ce signal ne contient ni quantité d'XP ni identifiant de récompense libre. Un
+client modifié pourrait anticiper ce seul geste ; il ne peut pas répéter le gain.
+
+Ajout de l'attachement persistant `sanctuary:tutorial`, schéma 1, contenant les
+étapes terminées, leur date et le nombre de points payé. Absent des anciennes
+sauvegardes, il commence vide. Il est enregistré **dans le même instantané joueur
+que l'XP**, copié à la mort et laissé intact par le New Game+. Le schéma 8 de
+progression et les journaux existants ne changent pas. Les données inconnues ou
+corrompues sont refusées sans remplacement silencieux.
+
+Nouvelle configuration `config/sanctuary/tutorial.json`, schéma 1 : `enabled`
+et `xpPerStep` (8 par défaut, 0 à 10 000), lue au démarrage du serveur. La créer uniquement si absente ;
+conserver toute configuration valide existante. Une étape terminée ne peut
+pas être repayée après changement du montant. Aucun paiement périodique,
+aucune remise à zéro des étapes au prestige.
+
+Capacité réseau `sanctuary:tutorial_v1`, message d'ouverture
+`sanctuary:tutorial_inventory_opened`, état personnel `sanctuary:tutorial_state`.
+Client et serveur utilisent beta.016 ensemble. Aucune intervention sur le
+terrain, les expansions ou une installation de jeu personnelle.
+
+## Vérification prévue
+
+Contrôler les déclencheurs réels, le total, les actions répétées, les modes de
+jeu, l'achat avec l'XP du tutoriel, les sauvegardes, le respawn et le New Game+.
+Vérifier les configurations et données malformées. Exécuter les tests Gradle
+et un parcours client natif, puis exporter un MRpack beta.016 vérifié qui conserve
+les nouvelles icônes beta.015 et les sprites vanilla de respiration/faim.
+
+## Validation serveur réalisée
+
+`./gradlew check build assemblePack
+-PsanctuaryFocusedTests=progression,mining,building,inventory,cycle,inventoryflow,sorting,tutorial`
+réussit avec **57 tests natifs** et tous les contrôles purs (`build/tutorial016-check-build.log`).
+
+Les tests beta.016 passent par les statistiques natives de déplacement, une
+observation Blocodex, un vrai bloc détruit et la sortie d'une recette du menu 2 × 2.
+Le total de 40 XP atteint quatre niveaux ; une récompense d'ouverture seule
+permet un achat. Répétition, coffre, réception de planches, créatif/spectateur,
+désactivation et changement de montant n'offrent aucun gain supplémentaire indu.
+Le même instantané joueur recharge l'XP et ses étapes ; le respawn et un vrai
+New Game+ conservent les étapes, sans recréditer les points perdus.
+
+Les contrôles purs vérifient les champs inconnus/manquants/dupliqués, schémas
+futurs, montants fractionnaires/négatifs/hors limite et configurations invalides
+sans écrasement. Le schéma de progression reste 8.
+
+Le client natif (`runClientGameTest -PsanctuaryClientTests=true
+-PsanctuaryTutorialClientTests=true`) passe également : E rapporte 8 XP,
+vingt répétitions du message ne rapportent rien, puis l'achat du premier cœur
+fonctionne. Les six icônes et les libellés FR/EN sont contrôlés à l'écran.
+Capture : `mods/sanctuary/build/run/clientGameTest/screenshots/0001_sanctuary-beta016-tutorial-xp-fr_fr.png`.
+
+Export local vérifié : [Sanctuary-beta.016.mrpack](../build/Sanctuary-beta.016.mrpack),
+2 549 212 octets, SHA-256
+`79f92b05d41c35d7408298831d1ac9edb18be9f86349400fdc044ee1ef37e60c`.
+Versions internes, dépendance Fabric API, intégrité ZIP et icônes identiques aux
+sources du resource pack contrôlées (`build/tutorial016-artifact.json`).
+Les exports beta.003–015 sont inchangés. Aucun déploiement personnel effectué.
diff --git a/docs/versioning.md b/docs/versioning.md
new file mode 100644
index 0000000..46b1a25
--- /dev/null
+++ b/docs/versioning.md
@@ -0,0 +1,104 @@
+# Versionnement Sanctuary
+
+Convention adoptée le 12 septembre 2026, ticket local **BETA-01**, branche
+`codex/versioning-beta`. Le dernier socle alpha est `0.1.0-alpha.30.7` ;
+la première version du nouveau cycle est **`beta.001`**.
+
+Le nom du produit reste **Sanctuary** ; la version contient seulement
+`beta.xxx`, pour obtenir `Sanctuary-beta.001.mrpack` et
+`sanctuary-beta.001.jar` sans répéter le nom dans les métadonnées.
+
+La préparation BETA-01 n’a pas été publiée séparément. À la demande du créateur,
+la même version inclut ensuite [EXP-01, les quatre anciennes expéditions](expeditions-beta001.md),
+sur `codex/ancient-expeditions-beta001`. Les observations BETA-01 ci-dessous
+concernent la première étape, avant ce changement de jeu ; les essais finaux
+d’EXP-01 sont consignés dans son propre document.
+
+## Compteur de livraison
+
+- Format exact en minuscules : `beta.xxx`, avec trois chiffres.
+- Première livraison `001`, puis `002`, `003`, … jusqu’à `999` ; décider d’une
+ nouvelle convention avant d’épuiser le compteur, sans retour à zéro.
+- Chaque livraison du mod consomme le numéro suivant, correctifs compris.
+ Aucun suffixe `.1`, `-hotfix` ou numéro lié à un chantier worldgen.
+- Une modification documentaire seule n’incrémente pas les binaires.
+- Une version publiée désigne un commit et des artefacts immuables. Ne pas
+ renommer les tags alpha ni remplacer leurs pièces jointes.
+
+| Emplacement | Valeur pour cette transition |
+| --- | --- |
+| `gradle.properties` → `mod_version` et `pack_version` | `beta.001` |
+| `packwiz/pack.toml` → `version` | `beta.001` |
+| Métadonnées Fabric du JAR, issues de Gradle | `beta.001` |
+| Futur tag Git / Gitea, sans préfixe `v` | `beta.001` |
+| JAR, convention Gradle `sanctuary-.jar` | `sanctuary-beta.001.jar` |
+| Export MRpack | `Sanctuary-beta.001.mrpack` |
+
+`scripts/pack.py check`, également exécuté par `verifyPack`, refuse un format
+différent ou un désalignement entre le mod et les manifestes du pack.
+`scripts/publish_pack.py` utilise la version exacte pour les nouveaux tags.
+Le canal packwiz et le chemin `mods/sanctuary.pw.toml` restent stables.
+
+Ce nom est une version textuelle personnalisée, pas une version SemVer.
+Fabric Loader 0.19.5 charge le mod avec sa valeur exacte, mais écrit un
+avertissement `isn't compatible with Loader's extended semantic version format`.
+Pour les futures dépendances à Sanctuary, ne pas supposer qu’une plage SemVer
+compare ce compteur ; les GameTests actuels déclarent `"sanctuary": "*"` dans
+leurs dépendances. Les mises à jour du pack suivent les manifestes et hashes
+du canal packwiz.
+
+## Génération, sauvegardes et suite
+
+Ce compteur désigne une livraison, pas une version de génération ou de format
+de sauvegarde. La première étape BETA-01 reprenait le code de jeu de l’alpha.30.7, notamment le
+codec `sanctuary:island_v24`, sans modifier les identifiants `sanctuary:*`, les
+journaux, les paramètres ou les chunks. Ce renommage n’impose pas à lui seul
+un nouveau monde ; les contraintes des anciennes générations restent celles
+de leurs notes de version. Aucune migration de sauvegarde n’est livrée ici.
+
+Le créateur clôt le chantier worldgen sur ce socle. Le [backlog](backlog.md)
+prévoit une passe courte sur les expansions, puis la progression en jeu.
+Les systèmes de progression décrits dans la vision restent à implémenter.
+
+## Validation et distribution
+
+La transition est préparée localement. La publication des artefacts, l’avancement
+du canal et la synchronisation de l’instance Prism suivent une demande de
+livraison et la [procédure packwiz](packwiz.md). Aucune de ces opérations n’est
+effectuée par le seul changement de version des sources.
+
+Vérifications de la première étape BETA-01 sur macOS, Java 25, Minecraft 26.3-pre-2.
+Elles précèdent les expéditions et la simplification finale du nom interne ;
+les anciens logs gardent leur libellé temporaire :
+
+- Les 13 cas de versionnement passent : compteurs admis, formats refusés et
+ désalignements mod/pack/manifeste. Les scripts Python compilent.
+- `./gradlew check build assemblePack` échoue après 6 min 56 s : **30 GameTests
+ réussissent sur 32**. Les contrôles purs précédant les GameTests passent.
+ `populationTerrainAndNaturalSpawnRemainPresent` attend encore une hauteur de
+ 384 blocs, alors que le monde courant mesure 640 blocs.
+ `retainedHydrologySurvivesNativeDecoration` refuse le support naturel d’une
+ cellule de sédiment en `x=-141, z=-86, bedY=238`, sur Moyen 724, graine `0`.
+ Ces deux assertions figurent déjà dans l’alpha.30.7 ; aucune n’est corrigée
+ ou désactivée dans les sources de BETA-01. Leur remise en cohérence reste ouverte.
+- `./gradlew build assemblePack -x :sanctuary:check` réussit ensuite en 2 s,
+ pour construire les artefacts séparément. Cette exclusion ponctuelle ne
+ constitue pas une réussite du contrôle complet ci-dessus.
+- Le JAR alpha.30.7 local correspond au SHA-256 publié dans le dépôt. La
+ comparaison des archives montre **1 116 entrées strictement identiques** ;
+ seul `fabric.mod.json` change, et seule sa valeur `version` diffère.
+- Le packwiz assemblé et l’export MRpack sont vérifiés : version, dépendances
+ exactes, index et empreintes, intégrité ZIP, un seul JAR Sanctuary.
+
+Les reçus et logs restent ignorés dans `build/beta001-validation.json`,
+`build/beta001-check-build-pack.log` et `build/beta001-assemble.log`.
+Artefacts locaux : `mods/sanctuary/build/libs/sanctuary-beta.001.jar`
+et `build/Sanctuary-beta.001.mrpack`. Aucun essai client graphique,
+Windows ou rechargement de sauvegarde personnelle n’est revendiqué.
+
+La simplification finale vers `beta.001` est également vérifiée : 12 cas
+de format et d’alignement, construction et assemblage réussis, nom/version
+du MRpack et version Fabric alignés. Comparé au JAR des expéditions ayant
+passé les essais natifs, seul le champ `version` de `fabric.mod.json` change.
+Le [reçu final des expéditions](expeditions-beta001.md#vérifications) remplace
+les empreintes des artefacts de préparation.
diff --git a/docs/vision.md b/docs/vision.md
index 6568f12..6b8eab3 100644
--- a/docs/vision.md
+++ b/docs/vision.md
@@ -2,7 +2,7 @@
Ce document conserve les intentions exprimées au démarrage de Sanctuary Beta. Il décrit une **destination de conception**, pas une liste de fonctionnalités déjà livrées. Le code, les tests et les notes de version font foi pour l'état réel du mod. Les valeurs d'équilibrage ci-dessous sont des propositions initiales à éprouver en jeu.
-La cible demandée est **Minecraft 26.3 avec Fabric**. Au démarrage du dépôt, le 8 septembre 2026, la base disponible retenue est **26.3-pre-2** ; les versions effectivement utilisées restent indiquées dans la configuration de construction. La première étape est d'initialiser proprement le projet, puis de retrouver et d'adapter les idées pertinentes du générateur de l'ancienne version 26.2.
+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.
## Le projet en quelques mots
@@ -25,7 +25,7 @@ L'objectif mythologique est de progresser d'une condition très limitée, proche
## Minecraft comme archéologie d’un autre Minecraft
**Canon et ligne de conception retenus.** Steve, Ari, Sunny, Kai, Zuri, Alex,
-Mefe, Makena, Noor sont les personnages disparus de la partie. Ils ont découvert
+Efe, Makena, Noor sont les personnages disparus de la partie. Ils ont découvert
quelque chose de révolutionnaire et ont continué parce que ça marchait. Jusqu’au
moment où ça n’a plus marché.
@@ -38,8 +38,9 @@ derrière son fonctionnement. Son ancien usage, ses circulations, ses réparatio
ses extensions et ses chantiers doivent permettre de comprendre ce qui avait
été résolu ici, même quand l’installation est abandonnée.
-La nature de la découverte et les rôles individuels restent à définir. Aucune
-catastrophe précise n’est confirmée par ce principe. **La mise en scène de cette
+Le [lore fourni pour la bêta.001](cosmologie.md#minecraft-comme-archéologie-dun-autre-minecraft)
+précise les rôles des personnages et la piste du sacrifice de Steve. La nature
+exacte de la découverte et le dénouement restent à définir. **La mise en scène de cette
histoire et ses liens avec la progression restent à implémenter** ; les versions
livrées et leurs tests établissent ce que chaque structure contient effectivement.
@@ -59,10 +60,12 @@ Le premier incrément porte sur le socle Fabric et la génération du monde : re
La progression, l'économie, les nouveaux monstres, les interfaces de pack et les autres dimensions restent des intentions futures. Ils ne constituent pas des dépendances du premier générateur. Le [backlog](backlog.md) précise les premiers tickets et leurs critères d'acceptation.
-L’alpha se poursuit vers les versions 15 ou 16 pour éprouver la génération et
-ses structures. La bêta véritable commencera ensuite les systèmes de Sanctuary,
-en particulier la progression. Ce cap décrit un ordre de travail : une version
-n’est pas considérée terminée avant ses essais et sa livraison.
+Le 12 septembre 2026, le créateur clôt le chantier worldgen sur l’alpha.30.7.
+Les livraisons suivent désormais [le compteur `beta.xxx`](versioning.md),
+à partir de `beta.001`. L’ordre de travail est une passe courte sur
+les expansions existantes, puis la progression en jeu. Cette décision de cap
+ne valide pas les fonctionnalités futures par avance : chaque ticket garde
+ses critères, ses essais et ses limites documentées.
## Mod, modpack et modules autonomes
@@ -297,29 +300,74 @@ Un espace dit « orphelin » devra rester correctement identifié et sauvegardé
par Minecraft ; aucune perte réelle de référence ou corruption de chunks n’est
prévue. Backrooms et Indoors restent à implémenter.
+Le [ticket BR-01 du 15 septembre 2026](backrooms-implementation.md) précise un
+premier parcours, à développer **après le suivi du ballast** : dormir mène à sa
+salle personnelle dans les Backrooms communes ; un tableau cache une porte
+ouverte vers le réseau. D'autres joueurs peuvent y entrer par exploration.
+Tout lit des Backrooms ramène chacun à son lit d'origine dans l'Overworld.
+Les nouvelles régions traduisent le ballast en architecture, conservent les
+secteurs existants et se découvrent sans coordonnées ni carte automatique.
+Ce ticket est préparé, pas implémenté ; les Indoors généraux ci-dessous restent
+un chantier distinct.
+
La géométrie des indoors reste à décider : certains peuvent être des volumes précisément délimités, d'autres des espaces fermés qui se rebouclent en trois dimensions, de type tore 3D. Leurs objets d'accès sont liés à la mailbox de leur propriétaire et doivent être récupérables s'ils sont perdus. Les règles de propriété, de partage, de transfert et de sortie sûre seront définies avant leur implémentation.
## Progression personnelle et collective
+Les [décisions et le premier lot beta.003](progression-beta003.md), issus de la
+discussion du 12 septembre, précisent désormais la distinction entre aptitudes
+et compétences, le HELLO_WORLD et la nature diégétique du Blocodex. Ce cahier
+distingue les décisions de l'auteur, les propositions et le code encore absent.
+
### Capacités initiales
-Le joueur commence avec trois cœurs, trois icônes de nourriture, une capacité respiratoire réduite, une vitesse de minage très faible et l'impossibilité de courir. L'armure conserve son fonctionnement habituel.
+Le joueur commence avec trois cœurs, trois icônes de nourriture et trois bulles de respiration. Les intentions initiales de minage très lent et d'impossibilité de courir restent à éprouver avec les premiers rangs. L'armure conserve son fonctionnement habituel.
-L'XP et les niveaux permettent d'acheter des améliorations. L'exemple de courbe souhaitée double les coûts : **1, 2, 4, 8, 16, 32, 64 niveaux**. Les valeurs doivent être testées séparément pour chaque capacité ; il reste notamment à confirmer si chaque achat consomme des niveaux entiers ou une quantité d'XP équivalente.
+L'XP et les niveaux permettent d'acheter des améliorations. La courbe retenue
+pour beta.013 est **1, 2, 4, 8, 16, 32, 48 niveaux**, en abaissant le dernier
+achat de l'ancien exemple à 64. Les achats consomment des niveaux entiers ;
+l'Inventaire utilise les cinq premiers prix. Les réglages restent configurables
+par le serveur et l'équilibrage devra être éprouvé en jeu. Voir le
+[contrat beta.013](progression-inline-beta013.md).
Les capacités peuvent atteindre les valeurs usuelles de dix cœurs, dix icônes de nourriture et une respiration complète par la progression normale en XP. Le nombre d'étapes et les plafonds seront arrêtés dans les tickets concernés.
### Inventaire, prestige et déblocages
+**Décision du 13 septembre 2026 :** accéder au New Game+ exige les six
+compétences au maximum — Vie, Faim, Minage, Construction, Souffle, Inventaire.
+Toutes les aptitudes ne sont pas nécessaires ; celles déjà acquises restent.
+Le développement doit achever ces compétences avant le premier cycle jouable.
+Les factions et leurs capacités liées au prestige sont cadrées avec ce cycle
+par les [prochains tickets](prochains-tickets.md).
+
+
L'inventaire commence à une rangée et peut s'étendre jusqu'à six par la progression normale. La place de la barre rapide dans ce comptage reste à préciser.
-Les **prestiges débloquent des slots de factions**. Ils ne donnent pas de capacités bonus ni de rangées d'inventaire supplémentaires. Le nombre de slots accordés par prestige et leurs règles d'utilisation seront précisés dans les tickets de factions.
+Les aptitudes sont conservées lorsque le personnage recommence ; les compétences
+sont à remonter. Une mort ordinaire conserve les deux, mais libère une part
+importante de l'XP non investie sous forme récupérable, y compris par les autres
+joueurs. Le taux reste à fixer. L'ancienne intention associait les prestiges à
+des slots de factions ; son articulation avec les charges de faction et le
+New Game Plus reste à préciser, sans bonus de capacité ou d'inventaire décidé.
La progression peut débloquer une table de fabrication dans l'inventaire, des capacités de minage et de construction, et l'accès à certaines fonctions de mods communautaires. Les joueurs peuvent également porter des capes et des familiers ; les spawn eggs correspondent à différents pouvoirs de familier.
-Les recettes de Just Enough Items sont révélées par paliers liés aux advancements. Les advancements Minecraft donnent accès aux recettes Minecraft ; ceux de Sanctuary ouvrent les blocs décoratifs, équipements et systèmes correspondants. Il faudra distinguer dans chaque ticket l'affichage des recettes, leur connaissance et l'autorisation réelle de fabriquer.
+L'accès au catalogue de recettes et la révélation progressive de son contenu
+sont distincts. La fabrication reste libre : un joueur qui connaît une recette
+peut la composer même lorsqu'elle n'est pas révélée dans le catalogue. Les
+advancements Minecraft priment ; ceux de Sanctuary portent surtout les
+accomplissements du serveur et le déblocage des prochaines expansions.
-Le **Blocodex natif** fournit une mémoire personnelle des blocs : inconnus, vus,
+Le **Blocodex** donne sa nature diégétique aux menus et au GUI : une interface
+au langage visuel de tablette, qui se métamorphose avec le contexte et les
+possibilités débloquées. Le catalogue visé couvre blocs, items et entités.
+Le [cadrage après beta.004](blocodex-navigation.md) précise la demande de cacher
+les inconnus aux joueurs, la vue opérateur, l'accès aux statistiques et
+advancements, les plans et le passeport avec exploits et favoris. Ces nouvelles
+règles et intégrations restent à implémenter ; la note sépare décisions et
+propositions d'organisation.
+Le **socle natif actuel** fournit une mémoire personnelle des blocs : inconnus, vus,
minés, déjà possédés, jetés et posés, avec le stock porté actuel séparé de
l’historique. Cette base appartient à Sanctuary et reste disponible sans
appareil à fabriquer. Les palettes de construction, recherches ou objectifs
@@ -335,6 +383,16 @@ de ballast de ces systèmes.
### Construction et production
+La carte immersive de [beta.007](atlas-immersive-beta007.md) précède le vein
+mining et le vein building. Le paysage s'étend à l'écran entier, derrière
+les habillages sombres ; un carré central reste éclairci. Commandes et
+coordonnées sont superposées, et l'absence de surface dessinée est transparente.
+Le cadre ne réduit plus la carte à une bande centrale. MAP-03 implémente cette
+disposition ; [beta.008](mining-beta008.md) fournit le Minage et la sélection
+de veines par faces. [beta.009](building-beta009.md) fournit Construction,
+ses plans orientés et ses poses natives limitées par les rangs.
+
+
- **Mining** améliore la vitesse de minage et ouvre le vein mining : extraction de blocs d'une même veine ou sur un plan défini par l'orientation du joueur.
- **Building** ouvre des outils de pose et de remplissage de surfaces, dont le vein building.
- **Plans et préfabriqués** : conserver et copier ses constructions, puis accéder à un catalogue de prefabs. Une intégration adaptée de Litematica est envisagée. Un ancien concept de « voxelier » dans un ordinateur permettait de créer des modèles en cubes ; cette piste est considérée comme complexe et n'est pas prioritaire.
@@ -415,7 +473,7 @@ Les **lucky blocks** déclenchent volontairement un événement imprévisible, p
| **Happy Ghast** | Monture volante à quatre places, avec une vitesse doublée. « Happy gust » a été prononcé dans la description ; l'identifiant exact est à vérifier. |
| **Totem de Notch / Magic Carpet** | En tenant le totem, faire apparaître une plateforme de verre sous ses pieds pour marcher dans les airs et faciliter la construction. La taille de la plateforme, évoquée comme trois blocs, reste à fixer. |
-Un sneak + clic droit permet de porter un mob sur sa tête, avec une limite d'un mob porté. Sur un autre joueur, le même geste permet de se placer sur sa tête. Un double sneak sans déplacement permet de s'asseoir.
+Un sneak + clic droit permet de porter un mob sur sa tête, avec une limite d'un mob porté. Sur un autre joueur, le même geste permet de se placer sur sa tête. S'asseoir est une aptitude débloquée de base, sans achat ni coût d'XP ; le geste envisagé est un double sneak sans déplacement.
## Faune, créatures et personnages
@@ -427,6 +485,25 @@ Le système d'insomnie et les phantoms vanilla disparaissent lorsque le monde pa
Un **Happy Creeper**, bienveillant, porte une fleur sur la tête et un visage différent. Des creepers rares ont également des apparences variées.
+### Doubles de Steve
+
+La [conception ANO-01 du 15 septembre 2026](steve-anomalies.md) ajoute trois
+anomalies, encore à implémenter :
+
+- **Herobrine**, caché dans les Backrooms, frappe, avance, pose des blocs,
+ enferme et peut attendre derrière un joueur ; il disparaît lorsqu'il est vu.
+- **Le mineur fantôme**, dont on voit l'arrière de la tête de face comme de dos,
+ creuse dans l'Overworld et les Backrooms, en privilégiant les chunks les moins
+ marqués par Demeure.
+- **Le Steve bugué**, extrêmement rare, reste en T-pose avec les textures mal
+ placées. Une fois découvert, il persiste, peut être embarqué et déplacé en
+ bateau, et constitue une preuve que Steve existe. Son lieu d'apparition reste
+ à définir.
+
+Les doubles prolongent le [réseau des Backrooms BR-01](backrooms-implementation.md)
+par des lots distincts. Ils ne sont pas requis pour livrer le premier parcours
+de sommeil et d'exploration.
+
### Poules rares
Une poule ordinaire peut très rarement pondre un œuf en or. Les quatre variantes rares sont classées ainsi :
@@ -466,9 +543,15 @@ La loterie du dimanche accueille aussi l'unique occasion hebdomadaire d'acheter
### Étoiles et constellations
-Les premières nuits n'ont pas d'étoiles. Les advancements Minecraft et Sanctuary font naître progressivement des familles d'étoiles communes à tout le serveur. Avec une spyglass, le joueur peut observer une étoile, son nom et l'accomplissement qui l'a fait apparaître.
+Le ciel commence sans étoiles. La première arrivée d'un habitant fait apparaître
+son étoile ; les advancements Minecraft réalisés par chaque habitant ajoutent
+leurs étoiles et des étoiles adjacentes selon l'accomplissement. Le ciel est
+commun au serveur et s'enrichit lorsque plusieurs habitants réalisent le même
+advancement. L'organisation des groupes et le traitement des cycles restent
+à définir. L'intention d'observer une étoile, son nom et son accomplissement
+avec une spyglass reste disponible pour ce chantier.
-Il peut tracer des constellations depuis son point de vue, directement dans le ciel, sans ouvrir une interface séparée. Ajouter ou retirer un point coûte un niveau dans l'intention initiale ; il faudra préciser la différence entre point et segment lors de l'implémentation. Les constellations sont visibles par tous. Le ciel tourne plus lentement que le cycle quotidien, de sorte que les nuits évoluent au fil des jours.
+Le joueur pourrait tracer des constellations depuis son point de vue, directement dans le ciel, sans ouvrir une interface séparée. Ajouter ou retirer un point coûte un niveau dans l'intention initiale ; il faudra préciser la différence entre point et segment lors de l'implémentation. Les constellations seraient visibles par tous. La rotation stellaire doit accomplir un cycle en sept jours réels ; son articulation avec la journée et sa référence temporelle seront définies dans le contrat du ciel.
## Quêtes, récompenses et cosmologie
@@ -536,3 +619,33 @@ Les inconnues ne bloquent pas l'initialisation ni le terrain de base. Elles sont
- Date d'origine du calendrier, rythme des événements et trois boules de cristal encore non décrites.
La prochaine décision concrète reste la même : obtenir un monde flottant stable et intéressant, puis l'éprouver en jeu avant d'ajouter les systèmes qui le feront grandir.
+
+
+### Cartographie native — décision du 13 septembre 2026
+
+Sanctuary développe sa propre carte vue du dessus, inspirée des usages de
+Xaero. Le premier lot est [MAP-01, beta.005](atlas-beta005.md) : relevés
+personnels conservés avant le déblocage de Grande carte, inspection opérateur
+explicite, sans partage automatique. Mini-carte et marqueurs auront chacun
+leur aptitude ; la carte web/Galactium reste une projection future distincte.
+
+## État jouable beta.011 — cycles et factions
+
+Le [contrat commun](cycle-factions-beta011.md) fixe la première réalisation :
+un cycle terminé exige les six compétences au maximum, accorde un prestige
+et une charge personnelle par défaut. Fondation de deux places puis extension
+d'une place coûtent chacune une charge. Compétences et XP sont réinitialisées
+(option XP serveur), aptitudes et données personnelles/collectives conservées.
+Les rôles, invitations et dépenses de faction sont persistants ; les claims,
+la connexion au Web et la quête finale collective restent à développer.
+Les intentions historiques ci-dessus ne modifient pas ce contrat jouable.
+
+## Évolution beta.038 — fondation dès l’arrivée
+
+La [fondation des factions](graves-food-beta038.md) devient accessible sans
+prestige pour dix niveaux d’XP. Le fondateur dirige la faction et en occupe
+l’unique place de départ. Les places supplémentaires sont financées par les
+charges de prestige ; les invités peuvent rejoindre sans prestige. Les factions
+créées avant cette évolution gardent leur capacité, leur propriétaire et leur
+historique de dépenses. Le contrat de New Game+ et ses six compétences maximales
+reste identique.
diff --git a/docs/xaero-experimental.md b/docs/xaero-experimental.md
new file mode 100644
index 0000000..cafd1fe
--- /dev/null
+++ b/docs/xaero-experimental.md
@@ -0,0 +1,64 @@
+# CONNECT-01 — relevé Xaero, remplacé par MAP-01
+
+Décision du créateur, 13 septembre 2026. Cadrage et audit de disponibilité,
+sans livraison de mod ni installation personnelle à cette étape.
+
+## Décision actuelle
+
+Le créateur choisit finalement une **carte de dessus native Sanctuary**, inspirée
+des interactions de Xaero. Le [ticket MAP-01, beta.005](atlas-beta005.md) remplace
+l'attente et les pistes de portage. Aucun code ni asset Xaero repris.
+L'exploration est conservée avant le déblocage de Grande carte (choix confirmé).
+
+Ordre retenu : cartographie native → Demeure → factions et claims →
+New Game Plus et prestiges. La mini-carte et les marqueurs gardent leurs propres
+aptitudes futures ; le partage reste volontaire entre habitants.
+
+Les relevés ci-dessous et [les alternatives MIT/GPLv3](cartographie-open-source.md)
+sont conservés comme références de recherche, pas comme dépendances du pack.
+Le dépôt historique `26.2` reste une référence en lecture seule.
+
+## Premier relevé des versions
+
+API Modrinth interrogée le 13 septembre 2026 avec le loader Fabric :
+
+| Projet | Dernière version déclarée pour 26.2 | ID Modrinth | Versions déclarées pour 26.3-pre-2 |
+| --- | --- | --- | --- |
+| Xaero's Minimap | `fabric-26.2-26.5.0` | `tBIPWIKW` | 0 |
+| Xaero's World Map | `fabric-26.2-1.46.0` | `DxU1zijU` | 0 |
+
+Les deux versions datent du 10 septembre 2026 et déclarent Fabric API comme
+dépendance requise. Elles constituent les points de départ identifiés, pas
+des binaires validés sur Sanctuary. Aucune dépendance n'a été téléchargée ou
+ajoutée au pack pendant cet archivage.
+
+Sources : [Minimap](https://modrinth.com/mod/xaeros-minimap/versions),
+[World Map](https://modrinth.com/mod/xaeros-world-map/versions).
+Le relevé compact, les URLs officielles, métadonnées et empreintes annoncées
+sont conservés dans `archives/web/references/xaero-intake-2026-09-13.json`.
+
+## Constat motivant l'attente
+
+Les deux projets déclarent `LicenseRef-All-Rights-Reserved` et aucune URL de
+sources dans leurs métadonnées officielles. Les pages de licence de l'auteur
+confirment cet état : [Minimap](https://www.curseforge.com/minecraft/mc-mods/xaeros-minimap/license),
+[World Map](https://www.curseforge.com/minecraft/mc-mods/xaeros-world-map/license).
+Ne pas décrire un dépôt tiers comme un fork officiel ou une source libre
+sans en avoir établi l'origine et les conditions de réutilisation.
+
+La piste envisagée d'un module d'adaptation sur les binaires 26.2 est reportée
+avec le portage. [XaeroPlus](https://github.com/rfresh2/XaeroPlus) illustre le principe
+d'un mod complémentaire qui modifie le comportement de Minimap et World Map ;
+sa licence MIT ne s'étend pas aux deux dépendances Xaero. Ce n'est pas encore
+un choix d'intégrer XaeroPlus et aucune compatibilité 26.3 n'est supposée.
+
+Le relevé présent conserve les faits et les versions identifiées. Aucune surveillance
+automatique ni date de disponibilité n'est promise.
+
+## Suite
+
+Le périmètre jouable, les sauvegardes et les vérifications de la carte native
+sont suivis dans [MAP-01](atlas-beta005.md). Le reste du contrat UI-02 et les
+futurs favoris restent dans [Blocodex — navigation](blocodex-navigation.md).
+Les **24 heures réelles** entre changements de créature favorite sont confirmées ;
+les coefficients et la portée du bonus de production restent à définir.
diff --git a/gradle.properties b/gradle.properties
index 5c29f56..5efe676 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -8,6 +8,7 @@ loader_version=0.19.5
loom_version=1.17.20
fabric_api_version=0.160.0+26.3
-mod_version=0.1.0-alpha.30.7
-pack_version=0.1.0-alpha.30.7
+# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
+mod_version=beta.060
+pack_version=beta.060
maven_group=fr.koka.sanctuary
diff --git a/mods/demeure/PROVENANCE.md b/mods/demeure/PROVENANCE.md
new file mode 100644
index 0000000..e6ac3f9
--- /dev/null
+++ b/mods/demeure/PROVENANCE.md
@@ -0,0 +1,21 @@
+# Demeure — provenance du port beta.006
+
+Auteur historique : KOKA99CAB. Licence : GPL-3.0-or-later. Sources locales `Structures/demeure`, citées par `26.2/inventory/README.md`, ciblant 26.1.2. Aucun JAR historique repris. Modèle des influences et états adapté ; événements et sauvegarde réécrits pour 26.3-pre-2, jours UTC réels, limites et séparation Atlas. AtomicJson et le hook de placement proviennent de Sanctuary (même licence).
+
+Empreintes SHA-256 des sources de référence :
+
+- `src/main/java/fr/koka99cab/demeure/DemeureMod.java` : `fef440779af58ca21a26dc79fd71676d27f79370239fa9b24ee691beb78becdd`
+- `src/main/java/fr/koka99cab/demeure/command/DemeureCommands.java` : `ecd8fea6b7811cfa3b682ea566af748c5338a5c702970badc5a183480eda5706`
+- `src/main/java/fr/koka99cab/demeure/network/DemeureFootprintPayload.java` : `621b9912560a27b1ef8c785be4d077ab5e153394c333833419d8b0a1e6c8b021`
+- `src/main/java/fr/koka99cab/demeure/network/DemeureFootprintSync.java` : `790ac0c26b13f23e7619d1fffaf389d16ef44e9c3180552c6b1f307487646bea`
+- `src/main/java/fr/koka99cab/demeure/server/DemeureEvents.java` : `983e1e0f417ca6ac71978ad4d6250067480425d7cfa6b5b14cdc9abbf7038a62`
+- `src/main/java/fr/koka99cab/demeure/server/DemeureGesture.java` : `c50ce2b5a5897453ee7fe4b7cf3db9f0d5343522666acd7b69b5f2399678f8d7`
+- `src/main/java/fr/koka99cab/demeure/server/DemeureTracker.java` : `bc362a3815a955fd576a481305a8006089242a3c4855be3607d549114373ea3c`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureApi.java` : `4cf5f353ff4d1149ec8ccceae5af315a500ce3d977572eeecc51c511058f73dd`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureChunkFootprint.java` : `fc85e880218522f4e535fc13e7b3895dca8425040d9028dd5d3d4232a1d39673`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureChunkView.java` : `516cf5f349e3364ca2dfb845838127b760efb62ca9eefb408bbdf78c32214879`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureInfluence.java` : `f9aa33bcf974b041f2ccd3edf1cb3975f2fc81d8b2bef6adb259c46461737c01`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureInfluenceView.java` : `13315b2400fc82f33fd7f636f3e4505e480b27d782f7b039b1e3f187784c71e3`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureSavedData.java` : `ce842b4062266d52b5778de315e18526ee3370056cab358ead6dc5e0630ef1c6`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureSubject.java` : `501fa36c7544da51ec625d999bed0d98c8caf1382e62cfb5500da5f3b2fa820f`
+- `src/main/java/fr/koka99cab/demeure/world/DemeureTerritoryState.java` : `0753ae22d166ccd7283914349c327c9b92899e13ce82c3e2bce0ef5ffcd142a5`
diff --git a/mods/demeure/README.md b/mods/demeure/README.md
new file mode 100644
index 0000000..e703a8b
--- /dev/null
+++ b/mods/demeure/README.md
@@ -0,0 +1,32 @@
+# Demeure
+
+Mod Fabric autonome pour Minecraft 26.3-pre-2, porté depuis les sources
+historiques référencées par Sanctuary 26.2. Il mesure les empreintes du vivant
+et l'attachement aux lieux ; il n'attribue ni propriété ni protection.
+
+Sanctuary l'embarque dans son JAR et utilise son API pour la surcouche de
+l'Atlas. Demeure ne dépend pas de Sanctuary. L'hôte peut restreindre les joueurs
+éligibles avec `DemeureService.setEligibility` ; le pack limite ainsi les
+traces aux mondes dont la progression est active et saine.
+
+Les gestes sont relevés après placement ou destruction réussis, lors d'un
+sommeil ou d'une mort, et par échantillonnage de l'activité. Les animaux,
+villageois et monstres proches sont regroupés par catégorie, jusqu'à
+128 entités chargées par habitant actif et par minute, dans un rayon de
+32 blocs (boîte englobante). Le mode spectateur ne contribue pas.
+
+Chaque chunk conserve des influences, une présence naturelle de départ,
+un budget par sujet et jour UTC, et une décroissance après quatorze jours
+d'absence. La consultation est indépendante des règles de protection et
+ne crée jamais de chunk. Aucun point d'expérience n'est distribué.
+
+Sauvegarde : `data/demeure/footprints-v1.json`, schéma 1 et graine du monde,
+écriture atomique, sauvegarde périodique, déconnexion et arrêt du serveur.
+La corruption ou une modification externe bloque l'écriture et produit
+une erreur serveur ; le fichier existant est conservé. Les limites sont
+explicitées dans [le contrat beta.006](../../docs/demeure-beta006.md).
+Les données de l'ancien Demeure ne sont pas importées automatiquement.
+
+`./gradlew :demeure:check :demeure:build` depuis la racine vérifie le modèle
+et construit le module. Les tests natifs des événements et de l'Atlas vivent
+dans Sanctuary. Licence GPL-3.0-or-later ; [provenance](PROVENANCE.md).
diff --git a/mods/demeure/build.gradle b/mods/demeure/build.gradle
new file mode 100644
index 0000000..b14d62a
--- /dev/null
+++ b/mods/demeure/build.gradle
@@ -0,0 +1,27 @@
+plugins { id 'net.fabricmc.fabric-loom' }
+group = 'fr.koka.demeure'
+version = rootProject.mod_version
+base { archivesName = 'demeure' }
+dependencies {
+ minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
+ implementation "net.fabricmc:fabric-loader:${rootProject.loader_version}"
+ implementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
+}
+java { toolchain.languageVersion = JavaLanguageVersion.of(25); withSourcesJar() }
+tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8'; options.release = 25 }
+processResources {
+ def values = [version: project.version, minecraft: rootProject.minecraft_version.replace('-pre-', '-pre.'), loader: rootProject.loader_version]
+ inputs.properties(values)
+ filesMatching('fabric.mod.json') { expand(values) }
+}
+tasks.named('jar') {
+ from(rootProject.file('LICENSE')) { rename { 'LICENSE_demeure' } }
+ from('PROVENANCE.md')
+}
+tasks.named('test') { failOnNoDiscoveredTests = false }
+tasks.register('demeureSmoke', JavaExec) {
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.demeure.DemeureSmoke'
+}
+tasks.named('check') { dependsOn('demeureSmoke') }
diff --git a/mods/demeure/src/main/java/fr/koka/demeure/AtomicJson.java b/mods/demeure/src/main/java/fr/koka/demeure/AtomicJson.java
new file mode 100644
index 0000000..9c75252
--- /dev/null
+++ b/mods/demeure/src/main/java/fr/koka/demeure/AtomicJson.java
@@ -0,0 +1,78 @@
+package fr.koka.demeure;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.Strictness;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.StringReader;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.util.Arrays;
+
+/** Refuses corrupt, oversized or externally replaced state; never resets evidence on failure. */
+public final class AtomicJson {
+ private AtomicJson() { }
+ public static JsonElement parse(String text) {
+ try {
+ var reader = new JsonReader(new StringReader(text));
+ reader.setStrictness(Strictness.STRICT);
+ scan(reader, 0);
+ if (reader.peek() != JsonToken.END_DOCUMENT) throw new IllegalArgumentException("Trailing JSON");
+ reader = new JsonReader(new StringReader(text));
+ reader.setStrictness(Strictness.STRICT);
+ JsonElement value = JsonParser.parseReader(reader);
+ if (reader.peek() != JsonToken.END_DOCUMENT) throw new IllegalArgumentException("Trailing JSON");
+ return value;
+ } catch (IOException error) { throw new IllegalArgumentException(error); }
+ }
+ public static void fields(JsonElement value, String... expected) {
+ if (!value.isJsonObject() || !value.getAsJsonObject().keySet().equals(java.util.Set.of(expected)))
+ throw new IllegalArgumentException("Missing or unknown JSON fields");
+ }
+ private static void scan(JsonReader reader, int depth) throws IOException {
+ if (depth > 32) throw new IllegalArgumentException("JSON nesting too deep");
+ switch (reader.peek()) {
+ case BEGIN_OBJECT -> {
+ reader.beginObject();
+ var keys = new java.util.HashSet();
+ while (reader.hasNext()) {
+ if (!keys.add(reader.nextName())) throw new IllegalArgumentException("Duplicate JSON field");
+ scan(reader, depth + 1);
+ }
+ reader.endObject();
+ }
+ case BEGIN_ARRAY -> { reader.beginArray(); while (reader.hasNext()) scan(reader, depth + 1); reader.endArray(); }
+ default -> reader.skipValue();
+ }
+ }
+ public static byte[] read(Path file, int limit) throws IOException {
+ if (Files.notExists(file, LinkOption.NOFOLLOW_LINKS)) return null;
+ if (!Files.isRegularFile(file, LinkOption.NOFOLLOW_LINKS) || Files.size(file) > limit)
+ throw new IOException("Invalid or oversized file: " + file);
+ byte[] bytes;
+ try (var stream = Files.newInputStream(file)) { bytes = stream.readNBytes(limit + 1); }
+ if (bytes.length > limit) throw new IOException("Oversized file: " + file);
+ return bytes;
+ }
+ public static byte[] write(Path file, byte[] previous, String text, int limit) throws IOException {
+ if (!Arrays.equals(previous, read(file, limit))) throw new IOException("File changed externally: " + file);
+ byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
+ if (bytes.length > limit) throw new IOException("Oversized state: " + file);
+ Files.createDirectories(file.getParent());
+ Path temporary = Files.createTempFile(file.getParent(), ".demeure-", ".tmp");
+ try {
+ try (var channel = FileChannel.open(temporary, StandardOpenOption.WRITE)) {
+ var buffer = ByteBuffer.wrap(bytes);
+ while (buffer.hasRemaining()) channel.write(buffer);
+ channel.force(true);
+ }
+ Files.move(temporary, file, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
+ if (!Arrays.equals(bytes, read(file, limit))) throw new IOException("Read-back mismatch: " + file);
+ return bytes;
+ } finally { Files.deleteIfExists(temporary); }
+ }
+}
diff --git a/mods/demeure/src/main/java/fr/koka/demeure/DemeureService.java b/mods/demeure/src/main/java/fr/koka/demeure/DemeureService.java
new file mode 100644
index 0000000..7c1ed46
--- /dev/null
+++ b/mods/demeure/src/main/java/fr/koka/demeure/DemeureService.java
@@ -0,0 +1,123 @@
+package fr.koka.demeure;
+
+import java.time.LocalDate;
+import java.time.ZoneOffset;
+import java.util.*;
+import java.util.function.Predicate;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.entity.event.v1.EntitySleepEvents;
+import net.fabricmc.fabric.api.entity.event.v1.ServerLivingEntityEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
+import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
+import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.entity.animal.Animal;
+import net.minecraft.world.entity.monster.Monster;
+import net.minecraft.world.entity.npc.villager.AbstractVillager;
+import net.minecraft.world.level.block.BedBlock;
+import net.minecraft.world.level.block.CropBlock;
+import net.minecraft.world.level.block.state.BlockState;
+import net.minecraft.world.level.storage.LevelResource;
+import org.slf4j.LoggerFactory;
+
+/** Autonomous server domain. Hosts may narrow activation; no client request can add influence. */
+public final class DemeureService implements ModInitializer {
+ private static final org.slf4j.Logger LOG=LoggerFactory.getLogger("demeure");
+ private static final Map SESSIONS=new WeakHashMap<>();
+ private static Predicate eligibility=player -> true;
+ private static final class Session {
+ FootprintStore store;boolean failed,capacityWarned;int lastSave;
+ final Map traces=new HashMap<>();
+ }
+ private static final class Trace {
+ FootprintStore.Key chunk;BlockPos pos;int activeTick,minuteTick;
+ }
+ public static void setEligibility(Predicate predicate) {eligibility=Objects.requireNonNull(predicate);}
+ public static long day() {return LocalDate.now(ZoneOffset.UTC).toEpochDay();}
+ public static boolean eligible(ServerPlayer player) {return player.isAlive() && !player.isSpectator() && eligibility.test(player);}
+ public static FootprintStore.Key key(ServerPlayer player,BlockPos pos) {
+ return new FootprintStore.Key(player.level().dimension().identifier().toString(),Math.floorDiv(pos.getX(),16),Math.floorDiv(pos.getZ(),16));
+ }
+ private static FootprintStore.Subject subject(ServerPlayer player) {
+ return new FootprintStore.Subject("player",player.getUUID().toString(),player.getGameProfile().name());
+ }
+ private static Session session(MinecraftServer server) {
+ return SESSIONS.computeIfAbsent(server,s -> {
+ var session=new Session();session.lastSave=s.getTickCount();
+ try {session.store=new FootprintStore(s.getWorldPath(LevelResource.ROOT).resolve("data/demeure/footprints-v1.json"),s.overworld().getSeed());}
+ catch (Exception error) {session.failed=true;LOG.error("Demeure unavailable; original file preserved",error);}
+ return session;
+ });
+ }
+ public static FootprintStore.View view(MinecraftServer server,FootprintStore.Key key) {
+ var session=session(server);return session.failed ? null : session.store.view(key,day());
+ }
+ public static boolean available(MinecraftServer server) {return !session(server).failed;}
+ public static double record(ServerPlayer player,BlockPos pos,double amount) {
+ if (!eligible(player)) return 0;
+ return recordUnchecked(player,pos,amount);
+ }
+ private static double recordUnchecked(ServerPlayer player,BlockPos pos,double amount) {
+ var session=session(player.level().getServer());if (session.failed) return 0;
+ var trace=session.traces.computeIfAbsent(player.getUUID(),id -> new Trace());trace.activeTick=player.level().getServer().getTickCount();
+ return session.store.touch(key(player,pos),subject(player),amount,day());
+ }
+ public static void placed(ServerPlayer player,BlockPos pos,BlockState state) {
+ record(player,pos,state.getBlock() instanceof BedBlock ? .5 : .05);
+ }
+ @Override public void onInitialize() {
+ PlayerBlockBreakEvents.AFTER.register((level,player,pos,state,entity) -> {
+ if (player instanceof ServerPlayer sp) record(sp,pos,state.getBlock() instanceof CropBlock crop && crop.isMaxAge(state) ? .3 : .05);
+ });
+ EntitySleepEvents.START_SLEEPING.register((entity,pos) -> {if (entity instanceof ServerPlayer sp) record(sp,pos,2);});
+ ServerLivingEntityEvents.AFTER_DEATH.register((entity,source) -> {
+ if (entity instanceof ServerPlayer sp && !sp.isSpectator() && eligibility.test(sp)) recordUnchecked(sp,sp.blockPosition(),1);
+ });
+ ServerTickEvents.END_SERVER_TICK.register(DemeureService::tick);
+ ServerPlayConnectionEvents.DISCONNECT.register((handler,server) -> {
+ var session=SESSIONS.get(server);if (session!=null) {session.traces.remove(handler.player.getUUID());save(session);}
+ });
+ ServerLifecycleEvents.SERVER_STOPPING.register(server -> {var session=SESSIONS.remove(server);if (session!=null) save(session);});
+ }
+ private static void tick(MinecraftServer server) {
+ if (server.getTickCount()%20!=0) return;
+ int tick=server.getTickCount();var seen=new HashSet();
+ for (var player:server.getPlayerList().getPlayers()) {
+ if (!eligible(player)) continue;
+ var session=session(server);if (session.failed) continue;
+ var trace=session.traces.computeIfAbsent(player.getUUID(),id -> new Trace());
+ var pos=player.blockPosition();var key=key(player,pos);
+ if (!key.equals(trace.chunk)) {record(player,pos,.01);trace.chunk=key;}
+ if (!pos.equals(trace.pos)) {trace.activeTick=tick;trace.pos=pos.immutable();}
+ if (tick-trace.minuteTick>=1200) {
+ boolean active=tick-trace.activeTick<1200;
+ if (active) session.store.touch(key,subject(player),.1,day());
+ trace.minuteTick=tick;
+ if (!active) continue;
+ // A bounded sample of loaded living neighbours; never searches or loads chunks.
+ int budget=128;
+ for (var entity:player.level().getEntitiesOfClass(LivingEntity.class,player.getBoundingBox().inflate(32))) {
+ if (budget--<=0) break;
+ if (!entity.isAlive() || !seen.add(entity.getUUID())) continue;
+ String kind=entity instanceof Animal ? "animals" : entity instanceof AbstractVillager ? "villagers" : entity instanceof Monster ? "monsters" : null;
+ if (kind==null) continue;
+ var at=entity.blockPosition();
+ session.store.touch(key(player,at),new FootprintStore.Subject(kind,kind,""),kind.equals("animals") ? .025 : kind.equals("villagers") ? .035 : .04,day());
+ }
+ }
+ }
+ var session=SESSIONS.get(server);
+ if (session!=null && !session.failed) {
+ if (!session.capacityWarned && session.store.atCapacity()) {session.capacityWarned=true;LOG.warn("Demeure capacity reached; existing footprints retained");}
+ if (tick-session.lastSave>=1200) {save(session);session.lastSave=tick;}
+ }
+ }
+ private static void save(Session session) {
+ if (session.failed) return;
+ try {session.store.save();} catch (Exception error) {session.failed=true;LOG.error("Demeure save refused; original file preserved",error);}
+ }
+}
diff --git a/mods/demeure/src/main/java/fr/koka/demeure/FootprintStore.java b/mods/demeure/src/main/java/fr/koka/demeure/FootprintStore.java
new file mode 100644
index 0000000..213de3c
--- /dev/null
+++ b/mods/demeure/src/main/java/fr/koka/demeure/FootprintStore.java
@@ -0,0 +1,117 @@
+package fr.koka.demeure;
+
+import com.google.gson.Gson;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.*;
+
+/** Adapted from KOKA99CAB's Demeure influence model. No ownership or protection is inferred. */
+public final class FootprintStore {
+ public static final int MAX_CHUNKS=16384,MAX_SUBJECTS=31;
+ private static final int MAX_BYTES=64*1024*1024;
+ private static final Gson JSON=new Gson();
+ public record Key(String dimension,int x,int z) {
+ public Key {
+ if (dimension==null || dimension.length()>128 || !dimension.matches("[a-z0-9_.-]+:[a-z0-9_./-]+")
+ || Math.abs((long)x)>1875000 || Math.abs((long)z)>1875000) throw new IllegalArgumentException("Demeure location");
+ }
+ }
+ public record Subject(String kind,String id,String label) {
+ public Subject {
+ if (!Set.of("player","animals","villagers","monsters","nature").contains(kind)
+ || id==null || id.isBlank() || id.length()>64 || label==null || label.length()>64
+ || label.codePoints().anyMatch(c -> Character.isISOControl(c) || c==0xA7)) throw new IllegalArgumentException("Demeure subject");
+ if (kind.equals("player")) UUID.fromString(id);
+ }
+ }
+ public record Influence(Subject subject,double score,long lastDay,int activeDays,double daily) {
+ public Influence {
+ if (subject==null || !Double.isFinite(score) || score<0 || score>1000000 || lastDay<0
+ || activeDays<1 || activeDays>16 || !Double.isFinite(daily) || daily<0 || daily>8.000001)
+ throw new IllegalArgumentException("Demeure influence");
+ }
+ public double base(long day) {return score*Math.pow(.985,Math.max(0,day-lastDay-14));}
+ public double effective(long day) {return base(day)*(1+Math.min(1.2,(activeDays-1)*.08));}
+ }
+ public record Share(Subject subject,double score,int percent) { }
+ public record View(String state,List influences) {public View {influences=List.copyOf(influences);}}
+ private record Chunk(Key key,List influences) { }
+ private record Document(int schema,long seed,List chunks) { }
+ private final Path path;
+ private final long seed;
+ private final Map> chunks=new LinkedHashMap<>();
+ private byte[] previous;
+ private boolean dirty,atCapacity;
+ public FootprintStore(Path path,long seed) throws IOException {
+ this.path=path;this.seed=seed;previous=AtomicJson.read(path,MAX_BYTES);
+ if (previous==null) return;
+ var tree=AtomicJson.parse(new String(previous,StandardCharsets.UTF_8));
+ AtomicJson.fields(tree,"schema","seed","chunks");
+ for (var chunk:tree.getAsJsonObject().getAsJsonArray("chunks")) {
+ AtomicJson.fields(chunk,"key","influences");
+ AtomicJson.fields(chunk.getAsJsonObject().get("key"),"dimension","x","z");
+ for (var inf:chunk.getAsJsonObject().getAsJsonArray("influences")) {
+ AtomicJson.fields(inf,"subject","score","lastDay","activeDays","daily");
+ AtomicJson.fields(inf.getAsJsonObject().get("subject"),"kind","id","label");
+ }
+ }
+ var doc=JSON.fromJson(tree,Document.class);
+ if (doc.schema!=1 || doc.seed!=seed || doc.chunks==null || doc.chunks.size()>MAX_CHUNKS) throw new IOException("Demeure schema/seed");
+ for (var chunk:doc.chunks) {
+ if (chunk.key==null || chunk.influences==null || chunk.influences.size()>MAX_SUBJECTS) throw new IOException("Demeure chunk");
+ var entries=new LinkedHashMap();
+ for (var inf:chunk.influences) {
+ if (inf==null || inf.subject.kind.equals("nature") || entries.put(subjectKey(inf.subject),inf)!=null) throw new IOException("Demeure duplicate subject");
+ }
+ if (chunks.put(chunk.key,entries)!=null) throw new IOException("Demeure duplicate chunk");
+ }
+ }
+ private static String subjectKey(Subject subject) {return subject.kind+":"+subject.id;}
+ /** Returns the accepted contribution. Caps survive reconnects and restarts. */
+ public double touch(Key key,Subject subject,double amount,long day) {
+ if (day<0 || !Double.isFinite(amount) || amount<=0 || subject.kind.equals("nature")) throw new IllegalArgumentException("Demeure gesture");
+ var entries=chunks.get(key);
+ if (entries==null) {
+ if (chunks.size()>=MAX_CHUNKS) {atCapacity=true;return 0;}
+ entries=new LinkedHashMap<>();chunks.put(key,entries);
+ }
+ String id=subjectKey(subject);var old=entries.get(id);
+ if (old==null && entries.size()>=MAX_SUBJECTS) {atCapacity=true;return 0;}
+ day=old==null ? day : Math.max(day,old.lastDay);
+ double daily=old==null || day>old.lastDay ? 0 : old.daily;
+ double accepted=Math.min(amount,Math.max(0,8-daily));
+ if (accepted<=0) return 0;
+ int days=old==null ? 1 : Math.min(16,old.activeDays+(day>old.lastDay ? 1 : 0));
+ double score=Math.min(1000000,(old==null ? 0 : old.base(day))+accepted);
+ entries.put(id,new Influence(subject,score,day,days,daily+accepted));dirty=true;
+ return accepted;
+ }
+ public View view(Key key,long day) {
+ var entries=chunks.get(key);if (entries==null) return null;
+ var raw=new ArrayList();
+ raw.add(new Share(new Subject("nature","nature",""),.35,0));
+ for (var inf:entries.values()) raw.add(new Share(inf.subject,inf.effective(day),0));
+ raw.sort(Comparator.comparingDouble(Share::score).reversed().thenComparing(s -> subjectKey(s.subject)));
+ double total=raw.stream().mapToDouble(Share::score).sum();
+ var shares=raw.stream().map(s -> new Share(s.subject,s.score,(int)Math.round(s.score*100/total))).toList();
+ var top=shares.getFirst();double first=top.score*100/total;
+ double second=shares.size()>1 ? shares.get(1).score*100/total : 0;
+ boolean player=shares.stream().anyMatch(s -> s.subject.kind.equals("player") && s.score*100/total>=20);
+ long last=entries.values().stream().filter(i -> !i.subject.kind.equals("animals")).mapToLong(Influence::lastDay).max().orElse(day);
+ String state=total<.8 ? "neutral"
+ : day-last>14 && shares.stream().anyMatch(s -> !Set.of("nature","animals").contains(s.subject.kind) && s.score*100/total>=20) ? "abandoned"
+ : top.subject.kind.equals("monsters") && !player ? "abandoned"
+ : Set.of("nature","animals").contains(top.subject.kind) && !player ? "wild"
+ : second>=20 && first-second<=10 ? "contested"
+ : second>=20 ? "shared" : first>=50 ? "dominated" : "inhabited";
+ return new View(state,shares);
+ }
+ public int size() {return chunks.size();}
+ public boolean atCapacity() {return atCapacity || chunks.size()>=MAX_CHUNKS;}
+ public void save() throws IOException {
+ if (!dirty) return;
+ var entries=chunks.entrySet().stream().map(e -> new Chunk(e.getKey(),new ArrayList<>(e.getValue().values()))).toList();
+ previous=AtomicJson.write(path,previous,JSON.toJson(new Document(1,seed,entries)),MAX_BYTES);dirty=false;
+ }
+}
diff --git a/mods/demeure/src/main/java/fr/koka/demeure/mixin/PlacementMixin.java b/mods/demeure/src/main/java/fr/koka/demeure/mixin/PlacementMixin.java
new file mode 100644
index 0000000..a80c492
--- /dev/null
+++ b/mods/demeure/src/main/java/fr/koka/demeure/mixin/PlacementMixin.java
@@ -0,0 +1,29 @@
+package fr.koka.demeure.mixin;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import fr.koka.demeure.DemeureService;
+import net.minecraft.core.BlockPos;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.LivingEntity;
+import net.minecraft.world.item.BlockItem;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.level.Level;
+import net.minecraft.world.level.block.Block;
+import net.minecraft.world.level.block.state.BlockState;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+
+/** Count the confirmed block state, including wall variants and merged slabs. */
+@Mixin(BlockItem.class)
+public abstract class PlacementMixin {
+ @WrapOperation(method = "place", at = @At(value = "INVOKE", target =
+ "Lnet/minecraft/world/level/block/Block;setPlacedBy(Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/ItemStack;)V"))
+ private void demeure$placed(Block block, Level level, BlockPos pos,
+ BlockState state, LivingEntity placer, ItemStack stack, Operation original) {
+ original.call(block, level, pos, state, placer, stack);
+ if (placer instanceof ServerPlayer player) {
+ DemeureService.placed(player,pos,state);
+ }
+ }
+}
diff --git a/mods/demeure/src/main/resources/demeure.mixins.json b/mods/demeure/src/main/resources/demeure.mixins.json
new file mode 100644
index 0000000..1a62143
--- /dev/null
+++ b/mods/demeure/src/main/resources/demeure.mixins.json
@@ -0,0 +1 @@
+{"required":true,"package":"fr.koka.demeure.mixin","compatibilityLevel":"JAVA_25","mixins":["PlacementMixin"],"injectors":{"defaultRequire":1}}
diff --git a/mods/demeure/src/main/resources/fabric.mod.json b/mods/demeure/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..05447dc
--- /dev/null
+++ b/mods/demeure/src/main/resources/fabric.mod.json
@@ -0,0 +1,13 @@
+{
+ "schemaVersion": 1,
+ "id": "demeure",
+ "version": "${version}",
+ "name": "Demeure",
+ "description": "Living footprints, activity and attachment to places. Sanctuary adaptation.",
+ "authors": ["KOKA99CAB", "Koka"],
+ "license": "GPL-3.0-or-later",
+ "environment": "*",
+ "entrypoints": {"main": ["fr.koka.demeure.DemeureService"]},
+ "mixins": ["demeure.mixins.json"],
+ "depends": {"fabricloader": ">=${loader}", "minecraft": "${minecraft}", "java": ">=25", "fabric-api": "*"}
+}
diff --git a/mods/demeure/src/test/java/fr/koka/demeure/DemeureSmoke.java b/mods/demeure/src/test/java/fr/koka/demeure/DemeureSmoke.java
new file mode 100644
index 0000000..600c403
--- /dev/null
+++ b/mods/demeure/src/test/java/fr/koka/demeure/DemeureSmoke.java
@@ -0,0 +1,46 @@
+package fr.koka.demeure;
+
+import java.nio.file.Files;
+import java.util.UUID;
+
+public final class DemeureSmoke {
+ public static void main(String[] args) throws Exception {
+ var dir=Files.createTempDirectory("demeure-smoke");var path=dir.resolve("footprints.json");
+ var store=new FootprintStore(path,42);
+ var key=new FootprintStore.Key("minecraft:overworld",-2,3);
+ var player=new FootprintStore.Subject("player",UUID.randomUUID().toString(),"Inhabitant");
+ require(store.view(key,20000)==null,"Reading an unknown chunk does not invent habitation");
+ require(store.touch(key,player,6,20000)==6 && store.touch(key,player,5,20000)==2,"Daily accepted budget is eight");
+ require(store.touch(key,player,1,19999)==0,"Clock rollback cannot reset budget");
+ store.save();store=new FootprintStore(path,42);
+ require(store.touch(key,player,1,20000)==0,"Restart does not reset budget");
+ var original=store.view(key,20000);
+ require(original.state().equals("dominated"),"Strong inhabitant influence is represented without a claim");
+ double decayed=score(store.view(key,20100),player);
+ require(decayed<8 && score(store.view(key,20014),player)==8,"Fourteen real days of grace then decay");
+ store.touch(key,player,.1,20100);
+ require(Math.abs(score(store.view(key,20100),player)-(decayed+.1)*1.08)<1e-8,"Returning materializes decay instead of resurrecting stale influence");
+ require(score(original,player)==8,"Snapshots are immutable");
+ store.save();require(new FootprintStore(path,42).view(key,20100).equals(store.view(key,20100)),"Exact persistence round trip");
+ require(store.view(new FootprintStore.Key("minecraft:the_nether",-2,3),20100)==null,"Dimensions are independent");
+ var other=new FootprintStore.Subject("player",UUID.randomUUID().toString(),"Neighbour");
+ var shared=new FootprintStore.Key("minecraft:overworld",1,1);
+ store.touch(shared,player,2,20100);store.touch(shared,other,2,20100);
+ require(store.view(shared,20100).state().equals("contested"),"Close equal influences remain contested, never owned");
+ require(store.view(shared,20115).state().equals("abandoned"),"Absence is evaluated on real days");
+ var animals=new FootprintStore.Key("minecraft:overworld",2,2);
+ store.touch(animals,new FootprintStore.Subject("animals","animals",""),2,20100);
+ require(store.view(animals,20100).state().equals("wild"),"Animal presence gives a wild footprint");
+ for (int i=0;i<40;i++) store.touch(shared,new FootprintStore.Subject("player",new UUID(0,i+1).toString(),"Visitor"),.1,20100);
+ require(store.atCapacity() && store.view(shared,20100).influences().size()==32,"Subject cap preserves existing contributors and nature");
+ var bytes=Files.readAllBytes(path);Files.writeString(path,"{broken");
+ var held=store;failure(held::save);failure(() -> new FootprintStore(path,42));
+ require(Files.readString(path).equals("{broken"),"External corruption is preserved");
+ Files.write(path,bytes);failure(() -> new FootprintStore(path,43));
+ System.out.println("Demeure smoke passed: real-day budget, rollback, decay, persistence, dimensions, territories and capacity.");
+ }
+ private static double score(FootprintStore.View view,FootprintStore.Subject subject) {return view.influences().stream().filter(s -> s.subject().equals(subject)).findFirst().orElseThrow().score();}
+ private static void require(boolean value,String message) {if (!value) throw new AssertionError(message);}
+ private static void failure(Checked action) throws Exception {boolean failed=false;try {action.run();}catch (Exception expected) {failed=true;}require(failed,"Invalid state must be refused");}
+ private interface Checked {void run() throws Exception;}
+}
diff --git a/mods/jei/PROVENANCE.md b/mods/jei/PROVENANCE.md
new file mode 100644
index 0000000..468e415
--- /dev/null
+++ b/mods/jei/PROVENANCE.md
@@ -0,0 +1,45 @@
+# Just Enough Items — Sanctuary preview
+
+Source officielle : https://github.com/mezz/JustEnoughItems/tree/26.2
+Commit : `aae2dfcfb82e6b5bce787ba72bb2eda7339b274f`.
+Version source 30.32.0 ; fork `30.32.0-sanctuary.2` pour **Minecraft 26.3-pre-2**
+(Fabric normalise ce nom en `26.3-pre.2`), Loader 0.19.5, API 0.160.0+26.3, Java 25.
+Licence MIT, copyright 2014–2015 mezz. `LICENSE.txt` est inclus sans modification.
+
+Le fork adapte les entrées SDL, les pipelines RenderPearl, les recettes devenues
+un registre, les tags d’ingrédients et les recettes d’alchimie natives. Les valeurs
+contextuelles de combustible/compost ont une page descriptive ; une valeur numérique
+n’est exposée que si le composant est explicitement constant.
+
+L’interface de pont `mezz.jei.api.sanctuary.CatalogueAccess` laisse Sanctuary gérer
+l’accès au catalogue, la visibilité des recettes, les favoris et l’inventaire étendu.
+Les recettes apprises sont consultables avant la découverte de leur poste de travail.
+Les recettes cachées restent cachées lorsqu’un favori tente de les rouvrir directement.
+Le vieux rectangle cliquable de l’inventaire 26.2 est retiré ; R/U et Blocodex restent
+les points d’entrée. Les autres interfaces et le transfert réseau restent ceux de JEI.
+
+## Reconstruction
+
+`./gradlew :jei:build` exécute `scripts/prepare_jei.py` à la configuration lorsque
+l’empreinte source/patch change. Le script télécharge le dépôt officiel seulement si
+le cache ignoré manque, extrait le commit fixé et applique `patches/26.3.patch` dans
+`build/jei-fork`. Aucun source tiers téléchargé ni JAR n’est versionné.
+La première reconstruction nécessite Internet, Git, patch et Python 3.
+`./gradlew :jei:sourcesJar` produit aussi les sources Java effectivement compilées.
+
+Le mod JEI est imbriqué dans le JAR Sanctuary avec deux dépendances :
+
+- `net.mezzdev:baked-substring-index:0.1.0`, MIT, James Mitchell.
+ https://github.com/mezz/baked-substring-index — licence dans `licenses/baked-substring-index.txt`.
+- `net.mezzdev:suffixtree:1.1.0`, Apache-2.0, Alessandro Bahgat Shehata.
+ https://github.com/mezz/generalized-suffix-tree — licence dans `licenses/suffixtree.txt`.
+
+Amecs 26.2 sert uniquement à compiler le point d’intégration optionnel historique ;
+il n’est ni embarqué ni annoncé compatible 26.3. Le client vérifié utilise les touches
+natives sans Amecs. Les fichiers de licences des dépendances sont ajoutés au JAR JEI.
+Ce fork expérimental ne revendique pas une validation upstream de cette préversion.
+
+La révision Sanctuary.2 retire le bouton de configuration du catalogue lorsque
+Sanctuary le demande (dessin, infobulle et interactions), et adapte les libellés
+FR/EN des favoris, de l’historique et des raccourcis. Les attributions, identifiants
+JEI, formats de réglages et mécanismes de recettes restent conservés.
diff --git a/mods/jei/build.gradle b/mods/jei/build.gradle
new file mode 100644
index 0000000..22cf2cf
--- /dev/null
+++ b/mods/jei/build.gradle
@@ -0,0 +1,36 @@
+plugins { id 'net.fabricmc.fabric-loom' }
+version='30.32.0-sanctuary.2'
+group='mezz.jei'
+base { archivesName='jei-sanctuary-26.3-pre-2' }
+repositories { mavenCentral(); maven { url='https://maven.siphalor.de/' }; maven { url='https://maven.blamejared.com/' } }
+providers.exec { workingDir rootProject.projectDir; commandLine 'python3', 'scripts/prepare_jei.py' }.result.get()
+def upstream=rootProject.file('build/jei-fork')
+sourceSets.main.java.setSrcDirs(['Common/src/main/java','Common/src/api/java','Fabric/src/main/java','Fabric/src/api/java','Library/src/main/java','Gui/src/main/java'].collect{new File(upstream,it)})
+sourceSets.main.resources.setSrcDirs(['Common/src/main/resources','Fabric/src/main/resources','Library/src/main/resources','Gui/src/main/resources'].collect{new File(upstream,it)})
+loom { accessWidenerPath=new File(upstream,'Fabric/src/main/resources/jei.accesswidener') }
+dependencies {
+ minecraft 'com.mojang:minecraft:26.3-pre-2'
+ implementation 'net.fabricmc:fabric-loader:0.19.5'
+ implementation 'net.fabricmc.fabric-api:fabric-api:0.160.0+26.3'
+ implementation 'org.jetbrains:annotations:26.0.2'
+ implementation 'org.jspecify:jspecify:1.0.0'
+ implementation 'net.mezzdev:baked-substring-index:0.1.0'
+ include 'net.mezzdev:baked-substring-index:0.1.0'
+ implementation 'net.mezzdev:suffixtree:1.1.0'
+ include 'net.mezzdev:suffixtree:1.1.0'
+ compileOnly 'de.siphalor.amecs.amecs-key-modifiers:amecs-key-modifiers-mc26.2.0:1.0.2'
+}
+java { toolchain { languageVersion=JavaLanguageVersion.of(25) } }
+tasks.withType(JavaCompile).configureEach { options.encoding='UTF-8'; options.compilerArgs+=['-Xmaxerrs','300'] }
+processResources {
+ def values=[modId:'jei', version:project.version, modName:'Just Enough Items — Sanctuary preview', modDescription:'Experimental source port for Sanctuary, Minecraft 26.3-pre-2.', modAuthor:'mezz', curseHomepageUrl:'https://www.curseforge.com/minecraft/mc-mods/jei', githubUrl:'https://github.com/mezz/JustEnoughItems', fabricLoaderVersionRange:'>=0.19.5', fabricApiVersionRange:'>=0.160.0+26.3', modJavaVersion:'25', amecsVersionFabric:'1.0.2']
+ inputs.properties(values)
+ filesMatching('fabric.mod.json') { expand(values) }
+ duplicatesStrategy=DuplicatesStrategy.EXCLUDE
+}
+tasks.withType(Jar).configureEach {
+ from(new File(upstream,'LICENSE.txt'))
+ from('PROVENANCE.md')
+ from('licenses') { into 'licenses' }
+}
+java { withSourcesJar() }
diff --git a/mods/jei/licenses/baked-substring-index.txt b/mods/jei/licenses/baked-substring-index.txt
new file mode 100644
index 0000000..0405259
--- /dev/null
+++ b/mods/jei/licenses/baked-substring-index.txt
@@ -0,0 +1,16 @@
+MIT License
+
+Copyright (c) 2026 James Mitchell
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
+Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/mods/jei/licenses/suffixtree.txt b/mods/jei/licenses/suffixtree.txt
new file mode 100644
index 0000000..72b90ee
--- /dev/null
+++ b/mods/jei/licenses/suffixtree.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2012 Alessandro Bahgat Shehata
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
\ No newline at end of file
diff --git a/mods/jei/patches/26.3.patch b/mods/jei/patches/26.3.patch
new file mode 100644
index 0000000..9e0850e
--- /dev/null
+++ b/mods/jei/patches/26.3.patch
@@ -0,0 +1,1491 @@
+--- /dev/null
++++ b/Common/src/api/java/mezz/jei/api/sanctuary/CatalogueAccess.java
+@@ -0,0 +1,18 @@
++package mezz.jei.api.sanctuary;
++
++import java.util.List;
++import java.util.function.BooleanSupplier;
++import java.util.function.BiFunction;
++import net.minecraft.world.inventory.AbstractContainerMenu;
++import net.minecraft.world.inventory.Slot;
++
++/** Sanctuary fork bridge. Defaults retain JEI behavior outside Sanctuary. */
++public final class CatalogueAccess {
++ private CatalogueAccess() {}
++ public static BooleanSupplier configButtonVisible = () -> true;
++ public static BooleanSupplier allowed = () -> true;
++ public static BooleanSupplier blueprints = () -> false;
++ public static java.util.function.BiPredicate,Object> recipe = (category,value) -> true;
++ public static java.util.function.Predicate ingredient = item -> true;
++ public static BiFunction,List> inventory = (menu,slots) -> slots;
++}
+--- a/Common/src/main/java/mezz/jei/common/config/ClientToggleState.java
++++ b/Common/src/main/java/mezz/jei/common/config/ClientToggleState.java
+@@ -13,7 +13,7 @@
+
+ @Override
+ public boolean isOverlayEnabled() {
+- return overlayEnabled;
++ return overlayEnabled && mezz.jei.api.sanctuary.CatalogueAccess.allowed.getAsBoolean();
+ }
+
+ @Override
+--- a/Common/src/main/java/mezz/jei/common/input/keys/AbstractJeiKeyMappingBuilder.java
++++ b/Common/src/main/java/mezz/jei/common/input/keys/AbstractJeiKeyMappingBuilder.java
+@@ -1,7 +1,7 @@
+ package mezz.jei.common.input.keys;
+
+ import com.mojang.blaze3d.platform.InputConstants;
+-import org.lwjgl.glfw.GLFW;
++import com.mojang.blaze3d.platform.InputConstants;
+
+ public abstract class AbstractJeiKeyMappingBuilder implements IJeiKeyMappingBuilder {
+ protected abstract IJeiKeyMappingInternal buildMouse(int mouseButton);
+@@ -23,6 +23,6 @@
+
+ @Override
+ public final IJeiKeyMappingInternal buildUnbound() {
+- return buildKeyboardKey(GLFW.GLFW_KEY_UNKNOWN);
++ return buildKeyboardKey(InputConstants.UNKNOWN.getValue());
+ }
+ }
+--- a/Common/src/main/java/mezz/jei/common/input/keys/IJeiKeyMappingInternal.java
++++ b/Common/src/main/java/mezz/jei/common/input/keys/IJeiKeyMappingInternal.java
+@@ -4,7 +4,7 @@
+ import net.minecraft.client.Minecraft;
+ import net.minecraft.client.KeyMapping;
+ import net.minecraft.network.chat.Component;
+-import org.lwjgl.glfw.GLFW;
++import com.mojang.blaze3d.platform.InputConstants;
+
+ import java.util.function.Consumer;
+
+@@ -34,9 +34,8 @@
+ Minecraft minecraft = Minecraft.getInstance();
+ long windowHandle = minecraft.getWindow().handle();
+ return switch (key.getType()) {
+- case KEYSYM -> InputConstants.isKeyDown(minecraft.getWindow(), key.getValue());
+- case MOUSE -> GLFW.glfwGetMouseButton(windowHandle, key.getValue()) == GLFW.GLFW_PRESS;
+- case SCANCODE -> false;
++ case KEYBOARD -> InputConstants.isKeyDown(key.getValue());
++ case MOUSE -> (org.lwjgl.sdl.SDLMouse.SDL_GetMouseState((java.nio.FloatBuffer)null, (java.nio.FloatBuffer)null) & (1 << (key.getValue()-1))) != 0;
+ };
+ }
+ }
+--- a/Common/src/main/java/mezz/jei/common/input/keys/JeiKeyModifier.java
++++ b/Common/src/main/java/mezz/jei/common/input/keys/JeiKeyModifier.java
+@@ -5,7 +5,7 @@
+ import net.minecraft.client.Minecraft;
+ import net.minecraft.client.input.InputQuirks;
+ import net.minecraft.network.chat.Component;
+-import org.lwjgl.glfw.GLFW;
++import com.mojang.blaze3d.platform.InputConstants;
+
+ public enum JeiKeyModifier {
+ CONTROL {
+@@ -26,7 +26,7 @@
+ if (InputQuirks.REPLACE_CTRL_KEY_WITH_CMD_KEY) {
+ Minecraft minecraft = Minecraft.getInstance();
+ Window window = minecraft.getWindow();
+- return InputConstants.isKeyDown(window, GLFW.GLFW_KEY_LEFT_SUPER) || InputConstants.isKeyDown(window, GLFW.GLFW_KEY_RIGHT_SUPER);
++ return InputConstants.isKeyDown(InputConstants.KEY_LGUI) || InputConstants.isKeyDown(InputConstants.KEY_RGUI);
+ }
+ return CONTROL.isActive(context);
+ }
+--- a/Common/src/main/java/mezz/jei/common/platform/IPlatformBrewingHelper.java
++++ b/Common/src/main/java/mezz/jei/common/platform/IPlatformBrewingHelper.java
+@@ -6,7 +6,7 @@
+ import mezz.jei.api.runtime.IIngredientManager;
+ import mezz.jei.common.recipes.BrewingExtensionHelper;
+ import net.minecraft.util.context.ContextMap;
+-import net.minecraft.world.item.alchemy.PotionBrewing;
++import net.minecraft.world.item.crafting.RecipeMap;
+
+ import java.util.List;
+
+@@ -27,7 +27,7 @@
+ List getBrewingRecipes(
+ IIngredientManager ingredientManager,
+ IVanillaRecipeFactory vanillaRecipeFactory,
+- PotionBrewing potionBrewing,
++ RecipeMap potionBrewing,
+ ContextMap contextMap,
+ BrewingExtensionHelper brewingExtensionHelper
+ );
+--- a/Common/src/main/java/mezz/jei/common/platform/IPlatformIngredientHelper.java
++++ b/Common/src/main/java/mezz/jei/common/platform/IPlatformIngredientHelper.java
+@@ -4,7 +4,7 @@
+ import net.minecraft.core.HolderSet;
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.ItemStack;
+-import net.minecraft.world.item.alchemy.PotionBrewing;
++import net.minecraft.world.item.crafting.RecipeMap;
+ import net.minecraft.world.item.enchantment.Enchantment;
+ import net.minecraft.world.item.crafting.Ingredient;
+
+@@ -12,9 +12,9 @@
+ import java.util.stream.Stream;
+
+ public interface IPlatformIngredientHelper {
+- List getPotionContainers(PotionBrewing potionBrewing);
++ List getPotionContainers(RecipeMap potionBrewing);
+
+- Stream getPotionIngredients(PotionBrewing potionBrewing);
++ Stream getPotionIngredients(RecipeMap potionBrewing);
+
+ float getCompostValue(ItemStack itemStack);
+
+--- a/Common/src/main/java/mezz/jei/common/platform/IPlatformItemStackHelper.java
++++ b/Common/src/main/java/mezz/jei/common/platform/IPlatformItemStackHelper.java
+@@ -10,14 +10,13 @@
+ import net.minecraft.world.item.component.ItemAttributeModifiers;
+ import net.minecraft.world.item.crafting.RecipeType;
+ import net.minecraft.world.item.enchantment.Enchantment;
+-import net.minecraft.world.level.block.entity.FuelValues;
+ import org.jspecify.annotations.Nullable;
+
+ import java.util.List;
+ import java.util.Optional;
+
+ public interface IPlatformItemStackHelper {
+- int getBurnTime(ItemStack itemStack, RecipeType> recipeType, FuelValues fuelValues);
++ int getBurnTime(ItemStack itemStack, RecipeType> recipeType);
+
+ Optional getCreatorModId(ItemStack stack);
+
+--- a/Common/src/main/java/mezz/jei/common/platform/IPlatformRenderHelper.java
++++ b/Common/src/main/java/mezz/jei/common/platform/IPlatformRenderHelper.java
+@@ -1,6 +1,6 @@
+ package mezz.jei.common.platform;
+
+-import com.mojang.blaze3d.pipeline.RenderPipeline;
++import com.mojang.renderpearl.api.pipeline.RenderPipeline;
+ import com.mojang.blaze3d.platform.NativeImage;
+ import com.mojang.datafixers.util.Either;
+ import net.minecraft.client.Minecraft;
+--- /dev/null
++++ b/Common/src/main/java/mezz/jei/common/recipes/RecipeMaps.java
+@@ -0,0 +1,15 @@
++package mezz.jei.common.recipes;
++import com.mojang.serialization.Lifecycle;
++import java.util.Collection;
++import net.minecraft.core.MappedRegistry;
++import net.minecraft.core.RegistrationInfo;
++import net.minecraft.core.registries.Registries;
++import net.minecraft.world.item.crafting.*;
++public final class RecipeMaps {
++ private RecipeMaps() {}
++ public static RecipeMap create(Collection> recipes) {
++ var registry=new MappedRegistry>(Registries.RECIPE,Lifecycle.stable());
++ for(var holder:recipes)registry.register(holder.id(),holder.value(),RegistrationInfo.BUILT_IN);
++ return RecipeMap.create(registry.freeze());
++ }
++}
+--- a/Common/src/main/java/mezz/jei/common/recipes/VanillaClientRecipeLoader.java
++++ b/Common/src/main/java/mezz/jei/common/recipes/VanillaClientRecipeLoader.java
+@@ -50,13 +50,13 @@
+
+ try (CloseableResourceManager resourceManager = createVanillaServerDataResourceManager()) {
+ RegistryOps ops = createVanillaServerDataSerializationContext(registryAccess, resourceManager);
+- SimpleJsonResourceReloadListener.scanDirectory(
+- resourceManager,
+- RECIPE_LISTER,
+- ops,
+- Recipe.CODEC,
+- recipes
+- );
++ for(var entry:RECIPE_LISTER.listMatchingResources(resourceManager).entrySet()) {
++ try(var reader=entry.getValue().openAsReader()) {
++ Recipe.DIRECT_CODEC.parse(ops,com.google.gson.JsonParser.parseReader(reader))
++ .resultOrPartial(error -> LOGGER.warn("Cannot read vanilla recipe {}: {}",entry.getKey(),error))
++ .ifPresent(recipe -> recipes.put(RECIPE_LISTER.fileToId(entry.getKey()),recipe));
++ } catch(java.io.IOException error) {LOGGER.warn("Cannot read vanilla recipe {}",entry.getKey(),error);}
++ }
+ } catch (RuntimeException e) {
+ LOGGER.error("Failed to load vanilla recipes from client resources.", e);
+ return RecipeMap.EMPTY;
+@@ -68,7 +68,7 @@
+ recipeHolders.add(new RecipeHolder<>(key, recipe));
+ });
+
+- RecipeMap recipeMap = RecipeMap.create(recipeHolders);
++ RecipeMap recipeMap = RecipeMaps.create(recipeHolders);
+ LOGGER.info("Loaded {} vanilla recipes from client resources.", recipeMap.values().size());
+ return recipeMap;
+ }
+@@ -76,7 +76,7 @@
+ private static CloseableResourceManager createVanillaServerDataResourceManager() {
+ return new MultiPackResourceManager(
+ PackType.SERVER_DATA,
+- List.of(ServerPacksSource.createVanillaPackSource())
++ List.of(ServerPacksSource.createVanillaPackSource().fullResources())
+ );
+ }
+
+--- a/Common/src/main/java/mezz/jei/common/transfer/BasicRecipeTransferHandlerServer.java
++++ b/Common/src/main/java/mezz/jei/common/transfer/BasicRecipeTransferHandlerServer.java
+@@ -373,7 +373,7 @@
+ ItemStack remainder = stowItem(player, inventorySlots, itemStack);
+ if (!remainder.isEmpty()) {
+ if (!player.getInventory().add(remainder)) {
+- player.drop(remainder, false);
++ player.drop(remainder, false, net.minecraft.util.Prediction.SERVER_ONLY);
+ }
+ }
+ }
+--- a/Common/src/main/java/mezz/jei/common/util/ServerCommandUtil.java
++++ b/Common/src/main/java/mezz/jei/common/util/ServerCommandUtil.java
+@@ -166,7 +166,7 @@
+ boolean flag = entityplayermp.getInventory().add(itemStack);
+ if (flag && itemStack.isEmpty()) {
+ itemStack.setCount(1);
+- ItemEntity entityitem = entityplayermp.drop(itemStack, false);
++ ItemEntity entityitem = entityplayermp.drop(itemStack, false, net.minecraft.util.Prediction.SERVER_ONLY);
+ if (entityitem != null) {
+ entityitem.makeFakeItem();
+ }
+@@ -174,7 +174,7 @@
+ entityplayermp.level().playSound(null, entityplayermp.getX(), entityplayermp.getY(), entityplayermp.getZ(), SoundEvents.ITEM_PICKUP, SoundSource.PLAYERS, 0.2F, ((entityplayermp.getRandom().nextFloat() - entityplayermp.getRandom().nextFloat()) * 0.7F + 1.0F) * 2.0F);
+ entityplayermp.inventoryMenu.broadcastChanges();
+ } else {
+- ItemEntity entityitem = entityplayermp.drop(itemStack, false);
++ ItemEntity entityitem = entityplayermp.drop(itemStack, false, net.minecraft.util.Prediction.SERVER_ONLY);
+ if (entityitem != null) {
+ entityitem.setNoPickUpDelay();
+ entityitem.setTarget(entityplayermp.getUUID());
+--- a/Common/src/main/resources/assets/jei/lang/en_us.json
++++ b/Common/src/main/resources/assets/jei/lang/en_us.json
+@@ -1,5 +1,6 @@
+ {
+- "_comment": "Tooltips",
++ "jei.tooltip.bookmarks": "Bookmarks",
++ "_comment": "Debug (for a debug mode, do not need translation)",
+ "jei.tooltip.config": "JEI Config",
+ "jei.tooltip.show.recipes": "Show all recipes",
+ "jei.tooltip.show.all.recipes.hotkey": "%s to show all recipes.",
+@@ -20,22 +21,22 @@
+ "jei.tooltip.cheat.mode.how.to.disable.hover.config.button.hotkey": "Press \"%s\" here to toggle it.",
+ "jei.tooltip.recipe.by": "Recipe By: %s",
+ "jei.tooltip.recipe.id": "Recipe ID: %s",
+- "jei.tooltip.not.enough.space": "The area on the right-hand side of this screen is too small for the JEI ingredient list overlay to display.",
+- "jei.tooltip.ingredient.list.disabled": "JEI overlays are hidden.",
++ "jei.tooltip.not.enough.space": "The area on the right-hand side of this screen is too small for the ingredient list overlay to display.",
++ "jei.tooltip.ingredient.list.disabled": "Catalogue overlays are hidden.",
+ "jei.tooltip.ingredient.list.disabled.how.to.fix": "Press \"%s\" to show them again.",
+- "jei.tooltip.bookmarks.enable": "Show JEI Bookmarks",
+- "jei.tooltip.bookmarks.disable": "Hide JEI Bookmarks",
+- "jei.tooltip.bookmarks.usage.nokey": "Add a key binding for JEI bookmarks in your Controls settings.",
++ "jei.tooltip.bookmarks.enable": "Show Bookmarks",
++ "jei.tooltip.bookmarks.disable": "Hide Bookmarks",
++ "jei.tooltip.bookmarks.usage.nokey": "Add a key binding for bookmarks in your Controls settings.",
+ "jei.tooltip.bookmarks.usage.key": "Hover over an ingredient and press \"%s\" to bookmark it.",
+- "jei.tooltip.bookmarks.not.enough.space": "The area on the left-hand side of this screen is too small for the JEI bookmark list overlay to display.",
++ "jei.tooltip.bookmarks.not.enough.space": "The area on the left-hand side of this screen is too small for the bookmark list overlay to display.",
+ "jei.tooltip.bookmarks.recipe.add": "Bookmark this recipe.",
+ "jei.tooltip.bookmarks.recipe.remove": "Remove the bookmark for this recipe.",
+ "jei.tooltip.bookmarks.tooltips.usage": "[Hold \"%s\" for recipe details]",
+ "jei.tooltip.bookmarks.tooltips.transfer.usage": "[Press \"%s\" to craft one]",
+ "jei.tooltip.bookmarks.tooltips.transfer.max.usage": "[Press \"%s\" to craft many]",
+ "jei.tooltip.bookmarks.preview.pin.usage": "[Hold \"%s\" to pin this preview, then click an ingredient to view recipes]",
+- "jei.tooltip.lookupHistory.enable": "Show JEI Lookup History",
+- "jei.tooltip.lookupHistory.disable": "Hide JEI Lookup History",
++ "jei.tooltip.lookupHistory.enable": "Show Lookup History",
++ "jei.tooltip.lookupHistory.disable": "Hide Lookup History",
+ "jei.tooltip.lookupHistory.usage": "Shows a list of the ingredients recently used for looking up recipes.",
+ "jei.tooltip.recipe.tooltips.craft.ingredients": "Ingredients summary:",
+ "jei.tooltip.recipe.sort.bookmarks.first.enabled": "Show bookmarked recipes first (enabled).",
+@@ -44,31 +45,25 @@
+ "jei.tooltip.recipe.sort.craftable.first.disabled": "Show craftable recipes first (disabled).",
+ "jei.config.client.appearance.toastReflowEnabled": "Toast Reflow",
+ "jei.config.client.appearance.toastReflowEnabled.description": "Reserves space for toast notifications so they do not overlap the JEI interface.",
+-
+- "_comment": "Error Tooltips",
+ "jei.tooltip.error.recipe.transfer.missing": "Missing Items",
+ "jei.tooltip.error.recipe.transfer.inventory.full": "Inventory is too full.",
+ "jei.tooltip.error.recipe.transfer.no.server": "The server must have JEI installed.",
+ "jei.tooltip.error.recipe.transfer.too.large.player.inventory": "Recipe is too large to craft in the 2x2 player crafting grid.",
+ "jei.tooltip.error.crash": "This ingredient crashed when getting its tooltip. Please see the client logs for details.",
+ "jei.tooltip.error.render.crash": "This ingredient crashed when being rendered. Please see the client logs for details.",
+-
+- "_comment": "Error Messages",
+ "jei.chat.error.no.cheat.permission.1": "You do not have permission to use JEI's cheat mode.",
+ "jei.chat.error.no.cheat.permission.disabled": "On this server, it is disabled for all players.",
+ "jei.chat.error.no.cheat.permission.enabled": "On this server, only the following types of players can use it:",
+ "jei.chat.error.no.cheat.permission.creative": "players who are in the creative mode",
+ "jei.chat.error.no.cheat.permission.op": "players who have an operator status (/op)",
+ "jei.chat.error.no.cheat.permission.give": "players who can use the \"/give\" command",
+- "_comment": "Key Bindings",
+- "key.category.jei.overlays": "JEI (Overlays)",
+- "key.jei.toggleOverlay": "Show/Hide JEI Overlays",
++ "key.category.jei.overlays": "Catalogue",
++ "key.jei.toggleOverlay": "Show/Hide Catalogue",
+ "key.jei.focusSearch": "Select Search Bar",
+ "key.jei.previousPage": "Previous Page",
+ "key.jei.nextPage": "Next Page",
+ "key.jei.toggleBookmarkOverlay": "Show/Hide Bookmarked Ingredients",
+-
+- "key.category.jei.recipe.gui": "JEI (Recipes)",
++ "key.category.jei.recipe.gui": "Recipes",
+ "key.jei.recipeBack": "Previous Recipe",
+ "key.jei.recipeForward": "Next Recipe",
+ "key.jei.previousCategory": "Previous Recipe Category",
+@@ -77,23 +72,19 @@
+ "key.jei.nextRecipePage": "Next Recipe Page",
+ "key.jei.pauseRecipeCycling": "Pause Recipe Ingredient Cycling",
+ "key.jei.closeRecipeGui": "Close Recipes GUI",
+-
+- "key.category.jei.cheat.mode": "JEI (Cheat Mode)",
++ "key.category.jei.cheat.mode": "Operator tools",
+ "key.jei.toggleCheatMode": "Toggle Cheat Mode",
+ "key.jei.cheatOneItem": "Cheat 1 Item",
+ "key.jei.cheatOneItem2": "Cheat 1 Item",
+ "key.jei.cheatItemStack": "Cheat 1 Stack",
+ "key.jei.cheatItemStack2": "Cheat 1 Stack",
+-
+- "key.category.jei.hover.config.button": "JEI (Hovering With Mouse Over Config Button)",
++ "key.category.jei.hover.config.button": "Configuration shortcuts",
+ "key.jei.toggleCheatModeConfigButton": "Toggle Cheat Mode",
+-
+- "key.category.jei.edit.mode": "JEI (Edit Mode)",
++ "key.category.jei.edit.mode": "Catalogue editing",
+ "key.jei.toggleEditMode": "Toggle Hide Ingredients Mode",
+ "key.jei.toggleHideIngredient": "Hide Ingredient",
+ "key.jei.toggleWildcardHideIngredient": "Hide Ingredient (With Wildcard)",
+-
+- "key.category.jei.mouse.hover": "JEI (Hovering With Mouse)",
++ "key.category.jei.mouse.hover": "Recipe shortcuts",
+ "key.jei.bookmark": "Add/Remove Bookmark",
+ "key.jei.showRecipe": "Show Recipe",
+ "key.jei.showRecipe2": "Show Recipe",
+@@ -103,22 +94,17 @@
+ "key.jei.maxTransferRecipeBookmark": "Craft Bookmarked Recipe (Many)",
+ "key.jei.quickMove": "Quick Move Ghost Item",
+ "key.jei.shareToChat": "Share Item to Chat",
+-
+- "key.category.jei.search": "JEI (Search Filter)",
++ "key.category.jei.search": "Catalogue search",
+ "key.jei.clearSearchBar": "Clear Search Filter",
+ "key.jei.previousSearch": "Previous Search",
+ "key.jei.nextSearch": "Next Search",
+-
+- "key.category.jei.dev.tools": "JEI (Dev Tools)",
++ "key.category.jei.dev.tools": "Developer tools",
+ "key.jei.copy.recipe.id": "Copy Recipe ID to Clipboard",
+-
+- "_comment": "Config",
+ "jei.config": "JEI Config",
+ "jei.config.name": "Name: %s",
+ "jei.config.description": "Description: %s",
+ "jei.config.valueValues": "Valid Values: %s",
+ "jei.config.defaultValue": "Default Value: %s",
+-
+ "jei.config.title": "%MODNAME Config",
+ "jei.config.default": "Default",
+ "jei.config.valid": "Valid",
+@@ -134,14 +120,12 @@
+ "jei.config.interface.overlayEnabled.description": "Show the ingredient list overlay next to open GUIs.",
+ "jei.config.interface.bookmarkOverlayEnabled": "Show Bookmark List Overlay",
+ "jei.config.interface.bookmarkOverlayEnabled.description": "Show the bookmark list overlay next to open GUIs.",
+-
+ "jei.config.client.appearance": "Appearance",
+ "jei.config.client.appearance.description": "Config options to change the appearance of JEI.",
+ "jei.config.client.appearance.centerSearch": "Center Search Bar",
+ "jei.config.client.appearance.centerSearch.description": "Move the JEI search bar to the bottom center of the screen.",
+ "jei.config.client.appearance.recipeGuiHeight": "Recipe GUI Height",
+ "jei.config.client.appearance.recipeGuiHeight.description": "The maximum height for the Recipes Gui (in pixels).",
+-
+ "jei.config.client.cheating": "Cheating",
+ "jei.config.client.cheating.description": "Config options related to Cheating.",
+ "jei.config.client.cheating.giveMode": "Give Mode",
+@@ -152,7 +136,6 @@
+ "jei.config.client.cheating.showHiddenIngredients.description": "Enable showing ingredients that are not in the creative menu.",
+ "jei.config.client.cheating.showTagRecipesEnabled": "Show Tag Recipes",
+ "jei.config.client.cheating.showTagRecipesEnabled.description": "Show recipes for ingredient tags like item tags and block tags.",
+-
+ "jei.config.client.bookmarks": "Bookmarks",
+ "jei.config.client.bookmarks.description": "Config options related to Bookmarking ingredients and recipes.",
+ "jei.config.client.bookmarks.addBookmarksToFrontEnabled": "Add Bookmarks to Front",
+@@ -161,7 +144,6 @@
+ "jei.config.client.bookmarks.bookmarkOutputAsRecipe.description": "When true, pressing the bookmark key on a recipe's output will bookmark the recipe instead of the ingredient.",
+ "jei.config.client.bookmarks.dragToRearrangeBookmarksEnabled": "Drag To Rearrange Bookmarks",
+ "jei.config.client.bookmarks.dragToRearrangeBookmarksEnabled.description": "Enable dragging bookmarks to rearrange them in the list.",
+-
+ "jei.config.client.tooltips": "Tooltips",
+ "jei.config.client.tooltips.description": "Config options related to Tooltips in JEI.",
+ "jei.config.client.tooltips.bookmarkTooltipFeatures": "Bookmarks Tooltips Features",
+@@ -176,14 +158,12 @@
+ "jei.config.client.tooltips.hideSingleTagContentTooltipEnabled.description": "Hide tag content in tooltips when there is only one ingredient in the tag.",
+ "jei.config.client.tooltips.enableRecipesGuiIngredientsSummary": "Recipe Ingredient Summary",
+ "jei.config.client.tooltips.enableRecipesGuiIngredientsSummary.description": "Show a summary of ingredients needed for a recipe, on its output ingredient's tooltip.",
+-
+ "jei.config.client.lookups": "Lookups",
+ "jei.config.client.lookups.description": "Config options related to looking up uses and recipes for ingredients in JEI.",
+ "jei.config.client.lookups.lookupFluidContentsEnabled": "Lookup Fluid Contents",
+ "jei.config.client.lookups.lookupFluidContentsEnabled.description": "When looking up recipes with items that contain fluids, also look up recipes for the fluids.",
+ "jei.config.client.lookups.lookupBlockTagsEnabled": "Lookup ItemBlock Tags",
+ "jei.config.client.lookups.lookupBlockTagsEnabled.description": "When searching for item tags, also include tags for the default blocks contained in the items.",
+-
+ "jei.config.client.lookupHistory": "Lookup History",
+ "jei.config.client.lookupHistory.description": "Config options for showing a history of ingredient lookups in JEI",
+ "jei.config.client.lookupHistory.enabled": "Enabled",
+@@ -194,7 +174,6 @@
+ "jei.config.client.lookupHistory.maxIngredients.description": "Max number of lookup history ingredients to save.",
+ "jei.config.client.lookupHistory.displaySide": "Display Side",
+ "jei.config.client.lookupHistory.displaySide.description": "Side of the screen to display the lookup history overlay.",
+-
+ "jei.config.client.input": "Input",
+ "jei.config.client.input.description": "Config options related to Inputs in JEI.",
+ "jei.config.client.input.dragDelayInMilliseconds": "Drag Delay",
+@@ -203,26 +182,22 @@
+ "jei.config.client.input.smoothScrollRate.description": "Scroll rate for scrolling the mouse wheel in smooth-scrolling scroll boxes. Measured in pixels.",
+ "jei.config.client.input.recipeSlotCyclingEnabled": "Recipe Slot Cycling",
+ "jei.config.client.input.recipeSlotCyclingEnabled.description": "Automatically cycle through ingredients in recipe slots over time.",
+-
+ "jei.config.client.performance": "Performance",
+ "jei.config.client.performance.description": "Config options related to performance optimizations in JEI.",
+ "jei.config.client.performance.lowMemorySlowSearchEnabled": "Low Memory Search",
+ "jei.config.client.performance.lowMemorySlowSearchEnabled.description": "Set search to low-memory mode (makes search slow but uses less RAM).",
+-
+ "jei.config.client.advanced": "Advanced",
+ "jei.config.client.advanced.description": "Advanced config options to change the way JEI functions.",
+ "jei.config.client.advanced.catchRenderErrorsEnabled": "Catch Render Errors",
+ "jei.config.client.advanced.catchRenderErrorsEnabled.description": "Catch render errors from modded ingredients and attempt to recover from them instead of crashing.",
+ "jei.config.client.advanced.recipeSyncWarningEnabled": "Recipe Sync Warning",
+ "jei.config.client.advanced.recipeSyncWarningEnabled.description": "Show a chat warning when the server doesn't provide synced recipes to JEI (e.g. vanilla servers, or servers without JEI installed).",
+-
+ "jei.config.client.sorting": "Sorting",
+ "jei.config.client.sorting.description": "Config options related to how JEI sorts recipes and ingredients.",
+ "jei.config.client.sorting.ingredientSortStages": "Ingredient Sorting Stages",
+ "jei.config.client.sorting.ingredientSortStages.description": "Sorting order for the ingredient list.",
+ "jei.config.client.sorting.recipeSorterStages": "Recipe Sorting Stages",
+ "jei.config.client.sorting.recipeSorterStages.description": "Sorting order for displayed recipes.",
+-
+ "jei.config.client.search": "Search",
+ "jei.config.client.search.description": "Config options related to how JEI searches recipes.",
+ "jei.config.client.search.modNameSearchMode": "@Mod Name Search Mode",
+@@ -247,7 +222,6 @@
+ "jei.config.client.search.searchShortModNames.description": "Search by the shorthand first letters of a mod's name.",
+ "jei.config.client.search.searchIngredientAliases": "Search Ingredient Aliases",
+ "jei.config.client.search.searchIngredientAliases.description": "Search ingredient aliases (alternative names) that are added by plugins, in addition to ingredient names.",
+-
+ "jei.config.client.ingredientList": "Ingredient List",
+ "jei.config.client.ingredientList.description": "Config options related to the Ingredient List (the list of ingredients on the right side of the screen)",
+ "jei.config.client.ingredientList.maxRows": "Max Rows",
+@@ -266,7 +240,6 @@
+ "jei.config.client.ingredientList.layoutMode.description": "RECTANGULAR keeps page navigation aligned to the ingredient grid while still allowing excluded grid slots to be cut out. MAXIMIZE_AVAILABLE_SPACE can resize and shift page navigation around excluded areas.",
+ "jei.config.client.ingredientList.navigationMode": "Navigation Mode",
+ "jei.config.client.ingredientList.navigationMode.description": "Choose PAGED for page buttons, SCROLLING for a row-stepped scroll bar, or SMOOTH_SCROLLING for a smooth scroll bar.",
+-
+ "jei.config.client.bookmarkList": "Bookmark List",
+ "jei.config.client.bookmarkList.description": "Config options related to the Bookmark List (the list of bookmarked ingredients on the left side of the screen)",
+ "jei.config.client.bookmarkList.maxRows": "Max Rows",
+@@ -285,17 +258,14 @@
+ "jei.config.client.bookmarkList.layoutMode.description": "RECTANGULAR keeps page navigation aligned to the bookmark grid while still allowing excluded grid slots to be cut out. MAXIMIZE_AVAILABLE_SPACE can resize and shift page navigation around excluded areas.",
+ "jei.config.client.bookmarkList.navigationMode": "Navigation Mode",
+ "jei.config.client.bookmarkList.navigationMode.description": "Choose PAGED for page buttons, SCROLLING for a row-stepped scroll bar, or SMOOTH_SCROLLING for a smooth scroll bar.",
+-
+ "jei.config.client.advanced.itemBlacklist": "Ingredient Blacklist",
+ "jei.config.client.advanced.itemBlacklist.description": "List of ingredients that should not be displayed in the ingredient list overlay. Format: modId[:name[:meta]]. The hide ingredients mode will automatically add or remove entries here.",
+ "jei.config.client.advanced.maxColumns": "Max Overlay Width",
+ "jei.config.client.advanced.maxColumns.description": "The maximum width of the ingredient and bookmark list overlays.",
+-
+ "jei.config.modIdFormat.modName": "Mod Name",
+ "jei.config.modIdFormat.modName.description": "Config options related to displaying Mod Names",
+ "jei.config.modIdFormat.modName.modNameFormat": "Mod Name Format",
+ "jei.config.modIdFormat.modName.modNameFormat.description": "Formatting for the mod names in tooltips for JEI GUIs. Leave blank to disable.",
+-
+ "jei.config.debug.debug": "Debug",
+ "jei.config.debug.debug.description": "Config options to help developers debug issues in JEI",
+ "jei.config.debug.debug.debugIngredientsEnabled": "Debug Ingredients",
+@@ -308,21 +278,14 @@
+ "jei.config.debug.debug.debugInfoTooltipsEnabled.description": "Add debug information to ingredient tooltips when advanced tooltips are enabled.",
+ "jei.config.debug.debug.logSuffixTreeStats": "Log Search Tree Statistics",
+ "jei.config.debug.debug.logSuffixTreeStats.description": "Log information about the suffix trees used for searching, to help debug JEI.",
+-
+ "jei.config.colors.colors": "Colors",
+ "jei.config.colors.colors.description": "Config options related to searching for colors of items in JEI",
+ "jei.config.colors.colors.searchColors": "Search Colors",
+ "jei.config.colors.colors.searchColors.description": "Color values to search for.",
+-
+- "_comment": "GUI",
+ "gui.jei.search": "Ingredient Filter",
+-
+- "_comment": "Hide Ingredients Mode",
+ "gui.jei.editMode.description": "JEI Hide Ingredients Mode:",
+ "gui.jei.editMode.description.hide": "Press \"%s\" to hide.",
+ "gui.jei.editMode.description.hide.wild": "Press \"%s\" to hide by wildcard.",
+-
+- "_comment": "Recipe Categories",
+ "gui.jei.category.craftingTable": "Crafting",
+ "gui.jei.category.stoneCutter": "Stonecutting",
+ "gui.jei.category.smelting": "Smelting",
+@@ -348,8 +311,6 @@
+ "gui.jei.category.tagInformation.fluid": "Fluid Tags",
+ "gui.jei.category.tagInformation.item": "Item Tags",
+ "gui.jei.category.recipe.crashed": "This recipe crashed. Please see the client logs for details.",
+-
+- "_comment": "Messages",
+ "jei.message.configured": "Install the \"Configured\" mod to access the in-game config",
+ "jei.message.config.folder": "Or click here to open the JEI config folder instead",
+ "jei.message.copy.recipe.id.success": "The following recipe ID was copied to the clipboard: %s",
+@@ -359,8 +320,6 @@
+ "jei.message.server.recipe.sync.vanilla": "This is a vanilla server. JEI is showing default recipes from your client, which may be different if the server uses datapacks. Install NeoForge or Fabric on the server, along with JEI, to show the server's recipes correctly.",
+ "jei.message.server.recipe.sync.unavailable": "This %s server does not provide recipes to JEI. JEI is showing default recipes from your client, which may be different on the server.",
+ "jei.message.server.recipe.sync.jei.missing": "This %s server does not have JEI installed. JEI is showing default recipes from your client, which may be different on the server. Install JEI on the server to show the server's recipes correctly.",
+-
+- "_comment": "Key Names",
+ "jei.key.combo.shift": "SHIFT + %s",
+ "jei.key.combo.control": "CTRL + %s",
+ "jei.key.combo.command": "CMD + %s",
+@@ -368,8 +327,6 @@
+ "jei.key.shift": "SHIFT",
+ "jei.key.mouse.left": "CLICK",
+ "jei.key.mouse.right": "RIGHT-CLICK",
+-
+- "_comment": "Debug (for a debug mode, do not need translation)",
+ "description.jei.wooden.door.1": "Wooden doors allow you to block monsters from entering your building.\\nTesting sentences.",
+ "description.jei.wooden.door.2": "Clicking on a door changes its state from open to closed and vice versa.",
+ "description.jei.wooden.door.3": "Wooden doors can be opened/closed via redstone circuits.",
+@@ -377,5 +334,7 @@
+ "description.jei.debug.formatting.2": "Testing %s %s formatting replacements.",
+ "description.jei.debug.formatting.3": "%s nested",
+ "jei.alias.panda.spawn.egg": "endangered",
+- "jei.alias.villager.spawn.egg": "HMMM"
++ "jei.alias.villager.spawn.egg": "HMMM",
++ "jei.tooltip.contextual_fuel": "This item can fuel a furnace. Its burn duration depends on server rules and the furnace type.",
++ "jei.tooltip.contextual_compost": "This item can be composted. The number of layers added depends on server rules and the composter level."
+ }
+--- a/Common/src/main/resources/assets/jei/lang/fr_fr.json
++++ b/Common/src/main/resources/assets/jei/lang/fr_fr.json
+@@ -1,5 +1,9 @@
+ {
+- "_comment": "Tooltips",
++ "jei.tooltip.lookupHistory.disable": "Masquer l’historique des recherches",
++ "jei.tooltip.lookupHistory.enable": "Afficher l’historique des recherches",
++ "jei.tooltip.bookmarks.disable": "Masquer les favoris",
++ "jei.tooltip.bookmarks.enable": "Afficher les favoris",
++ "_comment": "Debug (for a debug mode, do not need translation)",
+ "jei.tooltip.config": "Configurer JEI",
+ "jei.tooltip.show.recipes": "Afficher les recettes",
+ "jei.tooltip.delete.item": "Cliquer pour supprimer",
+@@ -15,75 +19,60 @@
+ "jei.tooltip.recipe.by": "Recette par : %s",
+ "jei.tooltip.recipe.id": "ID de la recette : %s",
+ "jei.tooltip.not.enough.space": "Il n'y a pas assez de place pour afficher la liste d'ingrédients.",
+- "jei.tooltip.ingredient.list.disabled": "L'overlay JEI a été désactivé.",
++ "jei.tooltip.ingredient.list.disabled": "Le catalogue est masqué.",
+ "jei.tooltip.ingredient.list.disabled.how.to.fix": "Pressez %s pour l'activer.",
+- "jei.tooltip.bookmarks": "Marque-pages JEI",
+- "jei.tooltip.bookmarks.usage.nokey": "Ajoutez un raccourci pour les marque-page JEI dans vos paramètres de raccourcis.",
++ "jei.tooltip.bookmarks": "Favoris",
++ "jei.tooltip.bookmarks.usage.nokey": "Ajoutez un raccourci pour les favoris dans les contrôles.",
+ "jei.tooltip.bookmarks.usage.key": "Survolez un objet avec votre souris et pressez \"%s\" pour l'épingler.",
+ "jei.tooltip.bookmarks.not.enough.space": "Il n'y a pas assez de place pour afficher les marque-pages ici.",
+-
+- "_comment": "Error Tooltips",
+ "jei.tooltip.error.recipe.transfer.missing": "Objets manquants",
+ "jei.tooltip.error.recipe.transfer.inventory.full": "Inventaire plein",
+ "jei.tooltip.error.recipe.transfer.no.server": "JEI doit aussi être installé sur le serveur",
+ "jei.tooltip.error.recipe.transfer.too.large.player.inventory": "Cette recette est trop grande pour être fabriquée dans la grille 2x2 du joueur.",
+ "jei.tooltip.error.crash": "Erreur d'infobulle, consulter le log",
+-
+- "_comment": "Error Messages",
+ "jei.chat.error.no.cheat.permission.1": "Vous n'avez pas l'autorisation d'utiliser le mode triche de JEI.",
+ "jei.chat.error.no.cheat.permission.disabled": "Sur ce serveur, le mode triche de JEI est désactivé pour tous les joueurs.",
+ "jei.chat.error.no.cheat.permission.enabled": "Sur ce serveur, les types de joueurs suivants peuvent utiliser le mode triche de JEI:",
+ "jei.chat.error.no.cheat.permission.creative": "les joueurs qui sont en mode créatif",
+ "jei.chat.error.no.cheat.permission.op": "les joueurs qui ont le statut d'opérateur (/op)",
+ "jei.chat.error.no.cheat.permission.give": "les joueurs qui peuvent utiliser /give",
+-
+- "_comment": "Key Bindings",
+- "key.category.jei.overlays": "JEI (Overlays)",
+- "key.jei.toggleOverlay": "Afficher/cacher JEI",
++ "key.category.jei.overlays": "Catalogue",
++ "key.jei.toggleOverlay": "Afficher/masquer le catalogue",
+ "key.jei.focusSearch": "Sélectionner la barre de recherche",
+ "key.jei.previousPage": "Afficher la page précédente",
+ "key.jei.nextPage": "Afficher la page suivante",
+ "key.jei.toggleBookmarkOverlay": "Afficher/cacher les objets épinglés",
+-
+- "key.category.jei.recipe.gui": "JEI (Recipes)",
++ "key.category.jei.recipe.gui": "Recettes",
+ "key.jei.recipeBack": "Montrer la recette précédente",
+ "key.jei.previousCategory": "Afficher la recette précédente",
+ "key.jei.nextCategory": "Affiàcher la prochaine catégorie de recette",
+ "key.jei.previousRecipePage": "Page précédente de la recette",
+ "key.jei.nextRecipePage": "Page suivante de la recette",
+ "key.jei.closeRecipeGui": "Fermer l'interface utilisateur des recettes",
+-
+- "key.category.jei.cheat.mode": "JEI (Cheat Mode)",
++ "key.category.jei.cheat.mode": "Outils opérateur",
+ "key.jei.toggleCheatMode": "Activer/désactiver le Mode triche",
+ "key.jei.cheatOneItem": "Triche 1 item",
+ "key.jei.cheatOneItem2": "Triche 1 item",
+ "key.jei.cheatItemStack": "Triche 1 stack",
+ "key.jei.cheatItemStack2": "Triche 1 stack",
+-
+- "key.category.jei.hover.config.button": "JEI (Hovering with Mouse over Config Button)",
++ "key.category.jei.hover.config.button": "Raccourcis de configuration",
+ "key.jei.toggleCheatModeConfigButton": "Basculer en mode triche",
+-
+- "key.category.jei.edit.mode": "JEI (Edit Mode)",
++ "key.category.jei.edit.mode": "Édition du catalogue",
+ "key.jei.toggleEditMode": "Basculer en mode Masquer/Modifier",
+ "key.jei.toggleHideIngredient": "Masquer l'ingrédient",
+ "key.jei.toggleWildcardHideIngredient": "Masquer l'ingrédient (joker)",
+-
+- "key.category.jei.mouse.hover": "JEI (Hovering with Mouse)",
++ "key.category.jei.mouse.hover": "Raccourcis des recettes",
+ "key.jei.bookmark": "Ajouter/supprimer un objet épinglé",
+ "key.jei.showRecipe": "Afficher la recette",
+ "key.jei.showRecipe2": "Afficher la recette",
+ "key.jei.showUses": "Montrer les utilisations",
+ "key.jei.showUses2": "Montrer les utilisations",
+-
+- "key.category.jei.search": "JEI (Search Filter)",
++ "key.category.jei.search": "Recherche du catalogue",
+ "key.jei.clearSearchBar": "Effacer le filtre de recherche",
+ "key.jei.previousSearch": "Recherche précédente",
+ "key.jei.nextSearch": "Recherche suivante",
+-
+- "key.category.jei.dev.tools": "JEI (Dev Tools)",
++ "key.category.jei.dev.tools": "Outils de développement",
+ "key.jei.copy.recipe.id": "Copier l'ID de la recette dans le presse-papiers",
+-
+- "_comment": "Config",
+ "jei.config": "Configurer JEI",
+ "jei.config.default": "Défaut",
+ "jei.config.valid": "Valide",
+@@ -131,13 +120,9 @@
+ "jei.config.client.cheating.giveMode.description": "Choisit si JEI doit placer les objets directement dans l'inventaire ou les accrocher au curseur de la souris.",
+ "jei.config.client.bookmarks.addBookmarksToFrontEnabled": "Ajouter de nouveaux marque-pages",
+ "jei.config.client.bookmarks.addBookmarksToFrontEnabled.description": "Si elle est vraie, elle ajoute les nouveaux marque-pages au début de la liste des marque-pages. Si elle est fausse, elle ajoute les nouveaux marque-pages à la fin de la liste des marque-pages.",
+-
+- "_comment": "Hide Ingredients Mode",
+ "gui.jei.editMode.description": "JEI Mode édition de la liste d'objets :",
+ "gui.jei.editMode.description.hide": "Cacher (%s).",
+ "gui.jei.editMode.description.hide.wild": "Cacher par caractères génériques (%s).",
+-
+- "_comment": "Recipe Categories",
+ "gui.jei.category.craftingTable": "Établi",
+ "gui.jei.category.stoneCutter": "Tailleur de pierre",
+ "gui.jei.category.smelting": "Fourneau",
+@@ -154,20 +139,14 @@
+ "gui.jei.category.compostable": "Compostable",
+ "gui.jei.category.compostable.chance": "Chance: %s%%",
+ "gui.jei.category.itemInformation": "Information",
+-
+- "_comment": "Messages",
+ "jei.message.configured": "Installez le mod \"Configured\" pour accéder à la configuration du jeu.",
+ "jei.message.config.folder": "Cliquez ici pour ouvrir le dossier de configuration de la JEI",
+ "jei.message.copy.recipe.id.success": "Copie de l'ID de la recette dans le presse-papiers: %s",
+ "jei.message.copy.recipe.id.failure": "Échec de la copie de l'ID de la recette dans le presse-papiers, l'ID de la recette est inconnu.",
+-
+- "_comment": "Key Names",
+ "jei.key.combo.shift": "SHIFT + %s",
+ "jei.key.combo.control": "CTRL + %s",
+ "jei.key.combo.command": "CMD + %s",
+ "jei.key.combo.alt": "ALT + %s",
+-
+- "_comment": "Debug (for a debug mode, do not need translation)",
+ "description.jei.wooden.door.1": "Wooden doors allow you to block monsters from entering your building.\\nTesting sentences.",
+ "description.jei.wooden.door.2": "Clicking on a door changes its state from open to closed and vice versa.",
+ "description.jei.wooden.door.3": "Wooden doors can be opened/closed via redstone circuits.",
+@@ -175,5 +154,7 @@
+ "description.jei.debug.formatting.2": "Testing %s %s formatting replacements.",
+ "description.jei.debug.formatting.3": "%s nested",
+ "jei.alias.panda.spawn.egg": "endangered",
+- "jei.alias.villager.spawn.egg": "HMMM"
++ "jei.alias.villager.spawn.egg": "HMMM",
++ "jei.tooltip.contextual_fuel": "Cet objet sert de combustible. Sa durée dépend des règles du serveur et du type de four.",
++ "jei.tooltip.contextual_compost": "Cet objet peut être composté. Le nombre de couches ajouté dépend des règles du serveur et du remplissage du composteur."
+ }
+--- a/Fabric/src/api/java/mezz/jei/api/package-info.java
++++ /dev/null
+@@ -1,4 +0,0 @@
+-@NullMarked
+-package mezz.jei.api;
+-
+-import org.jspecify.annotations.NullMarked;
+--- a/Fabric/src/main/java/mezz/jei/fabric/JustEnoughItemsClient.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/JustEnoughItemsClient.java
+@@ -28,7 +28,7 @@
+ Translator.setLocaleSupplier(new MinecraftLocaleSupplier());
+ ClientLifecycleHandler clientLifecycleHandler = new ClientLifecycleHandler();
+ ClientRecipeSynchronizedEvent.EVENT.register((minecraft, synchronizedRecipes) -> {
+- RecipeMap recipes = RecipeMap.create(synchronizedRecipes.recipes());
++ RecipeMap recipes = mezz.jei.common.recipes.RecipeMaps.create(synchronizedRecipes.recipes());
+ clientLifecycleHandler.onRecipesSynchronized(recipes);
+ });
+
+--- a/Fabric/src/main/java/mezz/jei/fabric/input/AmecsHelper.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/input/AmecsHelper.java
+@@ -9,13 +9,13 @@
+ import mezz.jei.common.input.keys.JeiKeyModifier;
+ import net.minecraft.client.input.InputQuirks;
+ import net.minecraft.network.chat.Component;
+-import org.lwjgl.glfw.GLFW;
++import com.mojang.blaze3d.platform.InputConstants;
+
+ import java.util.ArrayList;
+ import java.util.List;
+
+ public class AmecsHelper {
+- public static AmecsKeyModifier COMMAND = new AmecsJeiKeyModifier("jei.key.combo.command", null, GLFW.GLFW_KEY_LEFT_SUPER, GLFW.GLFW_KEY_RIGHT_SUPER);
++ public static AmecsKeyModifier COMMAND = new AmecsJeiKeyModifier("jei.key.combo.command", null, InputConstants.KEY_LGUI, InputConstants.KEY_RGUI);
+ private static boolean keyMappingManagerLayerInitialized = false;
+ private static boolean modifiersInitialized = false;
+
+--- a/Fabric/src/main/java/mezz/jei/fabric/input/AmecsJeiKeyMappingBuilder.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/input/AmecsJeiKeyMappingBuilder.java
+@@ -28,7 +28,7 @@
+
+ @Override
+ public IJeiKeyMappingInternal buildKeyboardKey(int key) {
+- var keyMapping = new AmecsKeyMappingWithContext(description, InputConstants.Type.KEYSYM, key, category, combination, context);
++ var keyMapping = new AmecsKeyMappingWithContext(description, InputConstants.Type.KEYBOARD, key, category, combination, context);
+ return new AmecsJeiKeyMapping(keyMapping);
+ }
+ }
+--- a/Fabric/src/main/java/mezz/jei/fabric/input/FabricJeiKeyMappingBuilder.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/input/FabricJeiKeyMappingBuilder.java
+@@ -7,7 +7,7 @@
+ import mezz.jei.common.input.keys.JeiKeyConflictContext;
+ import mezz.jei.common.input.keys.JeiKeyModifier;
+ import net.minecraft.client.KeyMapping;
+-import org.lwjgl.glfw.GLFW;
++import com.mojang.blaze3d.platform.InputConstants;
+
+ public class FabricJeiKeyMappingBuilder extends AbstractJeiKeyMappingBuilder {
+ protected final KeyMapping.Category category;
+@@ -35,7 +35,7 @@
+ @Override
+ protected IJeiKeyMappingInternal buildMouse(int mouseButton) {
+ if (hasUnsupportedModifier()) {
+- return buildKeyboardKey(GLFW.GLFW_KEY_UNKNOWN);
++ return buildKeyboardKey(InputConstants.UNKNOWN.getValue());
+ }
+
+ FabricKeyMapping keyMapping = new FabricKeyMapping(
+@@ -51,12 +51,12 @@
+ @Override
+ public IJeiKeyMappingInternal buildKeyboardKey(int key) {
+ if (hasUnsupportedModifier()) {
+- key = GLFW.GLFW_KEY_UNKNOWN;
++ key = InputConstants.UNKNOWN.getValue();
+ }
+
+ FabricKeyMapping keyMapping = new FabricKeyMapping(
+ description,
+- InputConstants.Type.KEYSYM,
++ InputConstants.Type.KEYBOARD,
+ key,
+ category,
+ context
+--- a/Fabric/src/main/java/mezz/jei/fabric/mixin/AmecsKeyModifiersEarlyInitMixin.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/mixin/AmecsKeyModifiersEarlyInitMixin.java
+@@ -12,7 +12,8 @@
+ * This mixin into AmecsKeyModifiers Early initializer because that
+ * where AmecsKeyModifiers are usually sealed.
+ */
+-@Mixin(AmecsKeyModifiersEarlyInit.class)
++@org.spongepowered.asm.mixin.Pseudo
++@Mixin(targets="de.siphalor.amecs.key_modifiers.impl.AmecsKeyModifiersEarlyInit")
+ public class AmecsKeyModifiersEarlyInitMixin {
+
+ @Inject(
+--- a/Fabric/src/main/java/mezz/jei/fabric/platform/BrewingHelper.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/platform/BrewingHelper.java
+@@ -7,7 +7,7 @@
+ import mezz.jei.common.recipes.BrewingExtensionHelper;
+ import mezz.jei.library.util.BrewingRecipeMakerCommon;
+ import net.minecraft.util.context.ContextMap;
+-import net.minecraft.world.item.alchemy.PotionBrewing;
++import net.minecraft.world.item.crafting.RecipeMap;
+
+ import java.util.ArrayList;
+ import java.util.List;
+@@ -17,7 +17,7 @@
+ public List getBrewingRecipes(
+ IIngredientManager ingredientManager,
+ IVanillaRecipeFactory vanillaRecipeFactory,
+- PotionBrewing potionBrewing,
++ RecipeMap potionBrewing,
+ ContextMap contextMap,
+ BrewingExtensionHelper brewingExtensionHelper
+ ) {
+--- a/Fabric/src/main/java/mezz/jei/fabric/platform/IngredientHelper.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/platform/IngredientHelper.java
+@@ -5,7 +5,7 @@
+ import net.minecraft.core.HolderSet;
+ import net.minecraft.world.item.Item;
+ import net.minecraft.world.item.ItemStack;
+-import net.minecraft.world.item.alchemy.PotionBrewing;
++import net.minecraft.world.item.crafting.RecipeMap;
+ import net.minecraft.world.item.enchantment.Enchantment;
+ import net.minecraft.world.item.crafting.Ingredient;
+ import net.minecraft.world.level.block.ComposterBlock;
+@@ -15,23 +15,20 @@
+
+ public class IngredientHelper implements IPlatformIngredientHelper {
+ @Override
+- public List getPotionContainers(PotionBrewing potionBrewing) {
+- return potionBrewing.containers;
++ public List getPotionContainers(RecipeMap potionBrewing) {
++ return potionBrewing.byType(net.minecraft.world.item.crafting.RecipeType.BREWING).stream().map(holder -> holder.value().getInput().ingredient()).distinct().toList();
+ }
+
+ @Override
+- public Stream getPotionIngredients(PotionBrewing potionBrewing) {
+- return Stream.concat(
+- potionBrewing.potionMixes.stream(),
+- potionBrewing.containerMixes.stream()
+- )
+- .map(PotionBrewing.Mix::ingredient);
++ public Stream getPotionIngredients(RecipeMap potionBrewing) {
++ return potionBrewing.byType(net.minecraft.world.item.crafting.RecipeType.BREWING).stream()
++ .map(holder -> holder.value().getReagent().ingredient()).distinct();
+ }
+
+ @Override
+ public float getCompostValue(ItemStack itemStack) {
+- Item item = itemStack.getItem();
+- return ComposterBlock.COMPOSTABLES.getOrDefault(item, 0f);
++ var compost=itemStack.get(net.minecraft.core.component.DataComponents.COMPOSTABLE);
++ return compost!=null&&compost.layers() instanceof net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt.Constant constant&&constant.value()==1?1F:0F;
+ }
+
+ @Override
+--- a/Fabric/src/main/java/mezz/jei/fabric/platform/ItemStackHelper.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/platform/ItemStackHelper.java
+@@ -10,7 +10,6 @@
+ import net.minecraft.world.item.TooltipFlag;
+ import net.minecraft.world.item.crafting.RecipeType;
+ import net.minecraft.world.item.enchantment.Enchantment;
+-import net.minecraft.world.level.block.entity.FuelValues;
+ import org.jspecify.annotations.Nullable;
+
+ import java.util.List;
+@@ -18,8 +17,13 @@
+
+ public class ItemStackHelper implements IPlatformItemStackHelper {
+ @Override
+- public int getBurnTime(ItemStack itemStack, RecipeType> recipeType, FuelValues fuelValues) {
+- return fuelValues.burnDuration(itemStack);
++ public int getBurnTime(ItemStack itemStack, RecipeType> recipeType) {
++ var fuel=itemStack.get(net.minecraft.core.component.DataComponents.COOKING_FUEL);
++ if(fuel==null||!(fuel.burnTime() instanceof net.minecraft.world.level.storage.loot.providers.number.ints.ResolvableInt.Constant time)
++ ||!(fuel.speedMultiplier() instanceof net.minecraft.world.level.storage.loot.providers.number.floats.ResolvableFloat.Constant speed))return 0;
++ // The public JEI count is expressed in equivalent normal-furnace ticks.
++ double ticks=time.value()*speed.value()*(recipeType==RecipeType.SMELTING?1:2);
++ return ticks>0&&ticks<=Integer.MAX_VALUE?(int)ticks:0;
+ }
+
+ @Override
+--- a/Fabric/src/main/java/mezz/jei/fabric/platform/RenderHelper.java
++++ b/Fabric/src/main/java/mezz/jei/fabric/platform/RenderHelper.java
+@@ -1,6 +1,6 @@
+ package mezz.jei.fabric.platform;
+
+-import com.mojang.blaze3d.pipeline.RenderPipeline;
++import com.mojang.renderpearl.api.pipeline.RenderPipeline;
+ import com.mojang.blaze3d.platform.NativeImage;
+ import com.mojang.datafixers.util.Either;
+ import mezz.jei.common.platform.IPlatformRenderHelper;
+@@ -115,7 +115,7 @@
+ ClientTooltipPositioner positioner
+ ) {
+ List components = createClientTooltipComponents(elements, font);
+- guiGraphics.tooltip(font, components, x, y, positioner, stack.get(DataComponents.TOOLTIP_STYLE));
++ guiGraphics.tooltip(font, components, x, y, positioner, stack.get(DataComponents.TOOLTIP_STYLE), false);
+ }
+
+ private List createClientTooltipComponents(List> elements, Font font) {
+--- a/Fabric/src/main/resources/fabric.mod.json
++++ b/Fabric/src/main/resources/fabric.mod.json
+@@ -37,11 +37,9 @@
+ "accessWidener" : "jei.accesswidener",
+
+ "depends": {
++ "minecraft": "26.3-pre.2",
+ "fabricloader": "${fabricLoaderVersionRange}",
+ "fabric-api": "${fabricApiVersionRange}",
+ "java": ">=${modJavaVersion}"
+- },
+- "suggests": {
+- "amecs_key_modifiers": ">=${amecsVersionFabric}"
+ }
+ }
+--- a/Fabric/src/main/resources/jei.accesswidener
++++ b/Fabric/src/main/resources/jei.accesswidener
+@@ -46,15 +46,11 @@
+ accessible field net/minecraft/world/item/crafting/ShieldDecorationRecipe target Lnet/minecraft/world/item/crafting/Ingredient;
+ accessible field net/minecraft/world/item/crafting/ShieldDecorationRecipe result Lnet/minecraft/world/item/ItemStackTemplate;
+
+-accessible field net/minecraft/world/item/alchemy/PotionBrewing containers Ljava/util/List;
+-accessible field net/minecraft/world/item/alchemy/PotionBrewing potionMixes Ljava/util/List;
+-accessible field net/minecraft/world/item/alchemy/PotionBrewing containerMixes Ljava/util/List;
+-accessible method net/minecraft/world/item/alchemy/PotionBrewing$Mix ingredient ()Lnet/minecraft/world/item/crafting/Ingredient;
+
+ accessible method net/minecraft/client/gui/GuiGraphicsExtractor setTooltipForNextFrameInternal (Lnet/minecraft/client/gui/Font;Ljava/util/List;IILnet/minecraft/client/gui/screens/inventory/tooltip/ClientTooltipPositioner;Lnet/minecraft/resources/Identifier;Z)V
+-accessible method net/minecraft/client/gui/GuiGraphicsExtractor blitSprite (Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;IIIIIIIII)V
+-accessible method net/minecraft/client/gui/GuiGraphicsExtractor blitNineSlicedSprite (Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;Lnet/minecraft/client/resources/metadata/gui/GuiSpriteScaling$NineSlice;IIIII)V
+-accessible method net/minecraft/client/gui/GuiGraphicsExtractor blitTiledSprite (Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;IIIIIIIIIII)V
++accessible method net/minecraft/client/gui/GuiGraphicsExtractor blitSprite (Lcom/mojang/renderpearl/api/pipeline/RenderPipeline;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;IIIIIIIII)V
++accessible method net/minecraft/client/gui/GuiGraphicsExtractor blitNineSlicedSprite (Lcom/mojang/renderpearl/api/pipeline/RenderPipeline;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;Lnet/minecraft/client/resources/metadata/gui/GuiSpriteScaling$NineSlice;IIIII)V
++accessible method net/minecraft/client/gui/GuiGraphicsExtractor blitTiledSprite (Lcom/mojang/renderpearl/api/pipeline/RenderPipeline;Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;IIIIIIIIIII)V
+
+ accessible method net/minecraft/world/inventory/GrindstoneMenu computeResult (Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Lnet/minecraft/world/item/ItemStack;
+
+--- a/Gui/src/main/java/mezz/jei/gui/bookmarks/IngredientBookmark.java
++++ b/Gui/src/main/java/mezz/jei/gui/bookmarks/IngredientBookmark.java
+@@ -35,7 +35,7 @@
+
+ @Override
+ public boolean isVisible() {
+- return visible;
++ return visible && mezz.jei.api.sanctuary.CatalogueAccess.allowed.getAsBoolean() && typedIngredient.getIngredient(mezz.jei.api.constants.VanillaTypes.ITEM_STACK).map(mezz.jei.api.sanctuary.CatalogueAccess.ingredient::test).orElse(false);
+ }
+
+ @Override
+--- a/Gui/src/main/java/mezz/jei/gui/bookmarks/RecipeBookmark.java
++++ b/Gui/src/main/java/mezz/jei/gui/bookmarks/RecipeBookmark.java
+@@ -118,7 +118,7 @@
+
+ @Override
+ public boolean isVisible() {
+- return visible;
++ return visible && mezz.jei.api.sanctuary.CatalogueAccess.recipe.test(recipeCategory,recipe);
+ }
+
+ @Override
+--- a/Gui/src/main/java/mezz/jei/gui/config/InternalKeyMappings.java
++++ b/Gui/src/main/java/mezz/jei/gui/config/InternalKeyMappings.java
+@@ -13,7 +13,7 @@
+ import mezz.jei.common.platform.Services;
+ import net.minecraft.client.KeyMapping;
+ import net.minecraft.resources.Identifier;
+-import org.lwjgl.glfw.GLFW;
++import com.mojang.blaze3d.platform.InputConstants;
+
+ import java.util.function.Consumer;
+ import java.util.function.Function;
+@@ -116,13 +116,13 @@
+ toggleOverlay = overlay.createMapping("key.jei.toggleOverlay")
+ .setContext(JeiKeyConflictContext.GUI)
+ .setModifier(JeiKeyModifier.CONTROL_OR_COMMAND)
+- .buildKeyboardKey(GLFW.GLFW_KEY_O)
++ .buildKeyboardKey(InputConstants.KEY_O)
+ .register(registerMethod);
+
+ focusSearch = overlay.createMapping("key.jei.focusSearch")
+ .setContext(JeiKeyConflictContext.GUI)
+ .setModifier(JeiKeyModifier.CONTROL_OR_COMMAND)
+- .buildKeyboardKey(GLFW.GLFW_KEY_F)
++ .buildKeyboardKey(InputConstants.KEY_F)
+ .register(registerMethod);
+
+ previousPage = overlay.createMapping("key.jei.previousPage")
+@@ -143,12 +143,12 @@
+ // Mouse Hover
+ bookmark = mouseHover.createMapping("key.jei.bookmark")
+ .setContext(JeiKeyConflictContext.JEI_GUI_HOVER)
+- .buildKeyboardKey(GLFW.GLFW_KEY_A)
++ .buildKeyboardKey(InputConstants.KEY_A)
+ .register(registerMethod);
+
+ showRecipe1 = mouseHover.createMapping("key.jei.showRecipe")
+ .setContext(JeiKeyConflictContext.JEI_GUI_HOVER)
+- .buildKeyboardKey(GLFW.GLFW_KEY_R)
++ .buildKeyboardKey(InputConstants.KEY_R)
+ .register(registerMethod);
+
+ showRecipe2 = mouseHover.createMapping("key.jei.showRecipe2")
+@@ -158,7 +158,7 @@
+
+ showUses1 = mouseHover.createMapping("key.jei.showUses")
+ .setContext(JeiKeyConflictContext.JEI_GUI_HOVER)
+- .buildKeyboardKey(GLFW.GLFW_KEY_U)
++ .buildKeyboardKey(InputConstants.KEY_U)
+ .register(registerMethod);
+
+ showUses2 = mouseHover.createMapping("key.jei.showUses2")
+@@ -197,12 +197,12 @@
+
+ previousSearch = search.createMapping("key.jei.previousSearch")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_UP)
++ .buildKeyboardKey(InputConstants.KEY_UP)
+ .register(registerMethod);
+
+ nextSearch = search.createMapping("key.jei.nextSearch")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_DOWN)
++ .buildKeyboardKey(InputConstants.KEY_DOWN)
+ .register(registerMethod);
+
+ // Cheat Mode
+@@ -260,7 +260,7 @@
+ // Recipes
+ recipeBack = recipeGui.createMapping("key.jei.recipeBack")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_BACKSPACE)
++ .buildKeyboardKey(InputConstants.KEY_BACKSPACE)
+ .register(registerMethod);
+
+ recipeForward = recipeGui.createMapping("key.jei.recipeForward")
+@@ -270,34 +270,34 @@
+
+ previousRecipePage = recipeGui.createMapping("key.jei.previousRecipePage")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_PAGE_UP)
++ .buildKeyboardKey(InputConstants.KEY_PAGEUP)
+ .register(registerMethod);
+
+ nextRecipePage = recipeGui.createMapping("key.jei.nextRecipePage")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_PAGE_DOWN)
++ .buildKeyboardKey(InputConstants.KEY_PAGEDOWN)
+ .register(registerMethod);
+
+ pauseRecipeCycling = recipeGui.createMapping("key.jei.pauseRecipeCycling")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_LEFT_SHIFT)
++ .buildKeyboardKey(InputConstants.KEY_LSHIFT)
+ .register(registerMethod);
+
+ previousCategory = recipeGui.createMapping("key.jei.previousCategory")
+ .setContext(JeiKeyConflictContext.GUI)
+ .setModifier(JeiKeyModifier.SHIFT)
+- .buildKeyboardKey(GLFW.GLFW_KEY_PAGE_UP)
++ .buildKeyboardKey(InputConstants.KEY_PAGEUP)
+ .register(registerMethod);
+
+ nextCategory = recipeGui.createMapping("key.jei.nextCategory")
+ .setContext(JeiKeyConflictContext.GUI)
+ .setModifier(JeiKeyModifier.SHIFT)
+- .buildKeyboardKey(GLFW.GLFW_KEY_PAGE_DOWN)
++ .buildKeyboardKey(InputConstants.KEY_PAGEDOWN)
+ .register(registerMethod);
+
+ closeRecipeGui = recipeGui.createMapping("key.jei.closeRecipeGui")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_ESCAPE)
++ .buildKeyboardKey(InputConstants.KEY_ESCAPE)
+ .register(registerMethod);
+
+ // Dev Tools
+@@ -316,7 +316,7 @@
+
+ escapeKey = jeiHidden.createMapping("key.jei.internal.escape.key")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_ESCAPE);
++ .buildKeyboardKey(InputConstants.KEY_ESCAPE);
+
+ leftClick = jeiHidden.createMapping("key.jei.internal.left.click")
+ .setContext(JeiKeyConflictContext.GUI)
+@@ -329,11 +329,11 @@
+ enterKey = new JeiMultiKeyMapping(
+ jeiHidden.createMapping("key.jei.internal.enter.key")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_ENTER),
++ .buildKeyboardKey(InputConstants.KEY_RETURN),
+
+ jeiHidden.createMapping("key.jei.internal.enter.key2")
+ .setContext(JeiKeyConflictContext.GUI)
+- .buildKeyboardKey(GLFW.GLFW_KEY_KP_ENTER)
++ .buildKeyboardKey(InputConstants.KEY_NUMPADENTER)
+ );
+ }
+
+--- a/Gui/src/main/java/mezz/jei/gui/overlay/IngredientListOverlay.java
++++ b/Gui/src/main/java/mezz/jei/gui/overlay/IngredientListOverlay.java
+@@ -95,7 +95,7 @@
+ @Override
+ public boolean isListDisplayed() {
+ updateScreenPropertiesIfDirty();
+- return this.controller.isListDisplayed();
++ return mezz.jei.api.sanctuary.CatalogueAccess.allowed.getAsBoolean() && this.controller.isListDisplayed();
+ }
+
+ private void markScreenPropertiesDirty() {
+@@ -150,7 +150,7 @@
+ this.contents.drawForeground(minecraft, guiGraphics, mouseX, mouseY, partialTicks);
+ }
+ if (this.controller.hasValidScreen()) {
+- this.configButton.draw(guiGraphics, mouseX, mouseY, partialTicks);
++ if (mezz.jei.api.sanctuary.CatalogueAccess.configButtonVisible.getAsBoolean()) this.configButton.draw(guiGraphics, mouseX, mouseY, partialTicks);
+
+ }
+ if (this.controller.hasValidScreen() && toggleState.isOverlayEnabled()) {
+@@ -164,7 +164,7 @@
+ this.contents.drawTooltips(minecraft, guiGraphics, mouseX, mouseY);
+ }
+ if (this.controller.hasValidScreen()) {
+- this.configButton.drawTooltips(guiGraphics, mouseX, mouseY);
++ if (mezz.jei.api.sanctuary.CatalogueAccess.configButtonVisible.getAsBoolean()) this.configButton.drawTooltips(guiGraphics, mouseX, mouseY);
+ }
+ if (this.controller.hasValidScreen() && toggleState.isOverlayEnabled()) {
+ this.lookupHistoryOverlay.drawTooltips(minecraft, guiGraphics, mouseX, mouseY);
+@@ -216,11 +216,11 @@
+ final IUserInputHandler displayedInputHandler = new CombinedInputHandler(
+ "IngredientListOverlay",
+ this.searchField.createInputHandler(),
+- this.configButton.createInputHandler(),
++ new ProxyInputHandler(() -> mezz.jei.api.sanctuary.CatalogueAccess.configButtonVisible.getAsBoolean() ? this.configButton.createInputHandler() : NullInputHandler.INSTANCE),
+ this.contents.createInputHandler()
+ );
+
+- final IUserInputHandler configButtonInputHandler = this.configButton.createInputHandler();
++ final IUserInputHandler configButtonInputHandler = new ProxyInputHandler(() -> mezz.jei.api.sanctuary.CatalogueAccess.configButtonVisible.getAsBoolean() ? this.configButton.createInputHandler() : NullInputHandler.INSTANCE);
+
+ return new ProxyInputHandler(() -> {
+ if (isListDisplayed()) {
+--- a/Gui/src/main/java/mezz/jei/gui/overlay/IngredientListOverlayLayout.java
++++ b/Gui/src/main/java/mezz/jei/gui/overlay/IngredientListOverlayLayout.java
+@@ -86,7 +86,7 @@
+ ) {
+ SearchAndConfigAreas getSearchAndConfigAreas(boolean contentsHasRoom, ImmutableRect2i contentsArea) {
+ ImmutableRect2i searchAndConfigArea = getSearchAndConfigArea(contentsHasRoom, contentsArea);
+- ImmutableRect2i searchArea = searchAndConfigArea.cropRight(BUTTON_SIZE);
++ ImmutableRect2i searchArea = mezz.jei.api.sanctuary.CatalogueAccess.configButtonVisible.getAsBoolean() ? searchAndConfigArea.cropRight(BUTTON_SIZE) : searchAndConfigArea;
+ ImmutableRect2i configButtonArea = searchAndConfigArea.keepRight(BUTTON_SIZE);
+ return new SearchAndConfigAreas(searchArea, configButtonArea);
+ }
+--- a/Gui/src/main/java/mezz/jei/gui/overlay/bookmarks/history/LookupHistoryOverlay.java
++++ b/Gui/src/main/java/mezz/jei/gui/overlay/bookmarks/history/LookupHistoryOverlay.java
+@@ -98,7 +98,7 @@
+
+ public boolean isListDisplayed() {
+ updateLayoutIfDirty();
+- return clientConfig.lookupHistoryEnabled().getValue() &&
++ return mezz.jei.api.sanctuary.CatalogueAccess.allowed.getAsBoolean() && clientConfig.lookupHistoryEnabled().getValue() &&
+ isDisplayedOnThisSide() &&
+ contents.hasRoom();
+ }
+--- a/Gui/src/main/java/mezz/jei/gui/recipes/RecipesGui.java
++++ b/Gui/src/main/java/mezz/jei/gui/recipes/RecipesGui.java
+@@ -561,6 +561,7 @@
+ }
+
+ private void open() {
++ if (!mezz.jei.api.sanctuary.CatalogueAccess.allowed.getAsBoolean()) return;
+ if (!isOpen()) {
+ parentScreen = minecraft.gui.screen();
+ }
+@@ -597,6 +598,8 @@
+
+ @Override
+ public void showRecipes(IRecipeCategory recipeCategory, List recipes, List> focuses) {
++ recipes = recipes.stream().filter(r -> mezz.jei.api.sanctuary.CatalogueAccess.recipe.test(recipeCategory,r)).toList();
++ if (recipes.isEmpty()) return;
+ ErrorUtil.checkNotNull(recipeCategory, "recipeCategory");
+ ErrorUtil.checkNotEmpty(recipes, "recipes");
+ IFocusGroup checkedFocuses = focusFactory.createFocusGroup(focuses);
+--- a/Library/src/main/java/mezz/jei/library/gui/recipes/RecipeLayoutInputHandler.java
++++ b/Library/src/main/java/mezz/jei/library/gui/recipes/RecipeLayoutInputHandler.java
+@@ -77,7 +77,7 @@
+ return guiEventListener.mouseReleased(relativeMouseX, relativeMouseY, key.getValue());
+ }
+ }
+- case KEYSYM -> {
++ case KEYBOARD -> {
+ if (!userInput.isSimulate()) {
+ return guiEventListener.keyPressed(relativeMouseX, relativeMouseY, key.getValue(), 0, userInput.getModifiers());
+ }
+--- a/Library/src/main/java/mezz/jei/library/helpers/CodecHelper.java
++++ b/Library/src/main/java/mezz/jei/library/helpers/CodecHelper.java
+@@ -41,7 +41,7 @@
+ ResourceKey.codec(Registries.RECIPE),
+ TupleCodec.of(
+ ResourceKey.codec(Registries.RECIPE),
+- Recipe.CODEC
++ Recipe.DIRECT_CODEC
+ )
+ )
+ .flatXmap(
+--- a/Library/src/main/java/mezz/jei/library/plugins/vanilla/VanillaPlugin.java
++++ b/Library/src/main/java/mezz/jei/library/plugins/vanilla/VanillaPlugin.java
+@@ -118,7 +118,7 @@
+ import net.minecraft.world.inventory.SmokerMenu;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.Items;
+-import net.minecraft.world.item.alchemy.PotionBrewing;
++import net.minecraft.world.item.crafting.RecipeMap;
+ import net.minecraft.world.item.crafting.BlastingRecipe;
+ import net.minecraft.world.item.crafting.CampfireCookingRecipe;
+ import net.minecraft.world.item.crafting.CraftingRecipe;
+@@ -224,9 +224,8 @@
+ interpretationBuilder.setWildcardForSubtypes(true);
+ });
+ registration.register(SlotDisplay.TagSlotDisplay.TYPE, (slotDisplay, ignoredContext, interpretationBuilder) -> {
+- interpretationBuilder
+- .setTagKey(slotDisplay.tag())
+- .setWildcardForSubtypes(true);
++ slotDisplay.tag().unwrapKey().ifPresent(interpretationBuilder::setTagKey);
++ interpretationBuilder.setWildcardForSubtypes(true);
+ });
+ registration.register(SlotDisplay.AnyFuel.TYPE, (ignoredSlotDisplay1, ignoredContext, interpretationBuilder) -> {
+ interpretationBuilder
+@@ -374,7 +373,15 @@
+ Minecraft minecraft = Minecraft.getInstance();
+ ClientLevel level = minecraft.level;
+ ErrorUtil.checkNotNull(level, "minecraft.level");
+- PotionBrewing potionBrewing = level.potionBrewing();
++ // Contextual server expressions do not provide a fixed client-side duration or chance.
++ // Keep their use discoverable without inventing a scalar value for the public JEI APIs.
++ for(var stack:ingredientManager.getAllItemStacks()) {
++ var fuel=stack.get(DataComponents.COOKING_FUEL);
++ if(fuel!=null)registration.addIngredientInfo(stack,VanillaTypes.ITEM_STACK,Component.translatable("jei.tooltip.contextual_fuel"));
++ var compost=stack.get(DataComponents.COMPOSTABLE);
++ if(compost!=null)registration.addIngredientInfo(stack,VanillaTypes.ITEM_STACK,Component.translatable("jei.tooltip.contextual_compost"));
++ }
++ RecipeMap potionBrewing = Internal.getClientSyncedRecipes();
+ IPlatformBrewingHelper brewingHelper = Services.PLATFORM.getBrewingHelper();
+ List brewingRecipes = brewingHelper.getBrewingRecipes(
+ ingredientManager,
+@@ -392,7 +399,7 @@
+ public void registerGuiHandlers(IGuiHandlerRegistration registration) {
+ registration.addRecipeClickArea(CraftingScreen.class, 88, 32, 28, 23, RecipeTypes.CRAFTING);
+ registration.addRecipeClickArea(CrafterScreen.class, 88, 32, 28, 23, RecipeTypes.CRAFTING);
+- registration.addRecipeClickArea(InventoryScreen.class, 137, 29, 10, 13, RecipeTypes.CRAFTING);
++ // Sanctuary uses a resized inventory; R/U and the Blocodex open recipes without a stale hitbox.
+ registration.addRecipeClickArea(BrewingStandScreen.class, 97, 16, 14, 30, RecipeTypes.BREWING);
+ registration.addRecipeClickArea(FurnaceScreen.class, 78, 32, 28, 23, RecipeTypes.SMELTING, RecipeTypes.SMELTING_FUEL);
+ registration.addRecipeClickArea(SmokerScreen.class, 78, 32, 28, 23, RecipeTypes.SMOKING, RecipeTypes.SMOKING_FUEL);
+--- a/Library/src/main/java/mezz/jei/library/plugins/vanilla/cooking/FurnaceRecipeTransferInfo.java
++++ b/Library/src/main/java/mezz/jei/library/plugins/vanilla/cooking/FurnaceRecipeTransferInfo.java
+@@ -47,7 +47,7 @@
+
+ @Override
+ public List getInventorySlots(FurnaceMenu container, Object recipe) {
+- return container.slots.subList(3, 39);
++ return mezz.jei.api.sanctuary.CatalogueAccess.inventory.apply(container, container.slots.subList(3, 39));
+ }
+
+ private static boolean hasSpecificFuel(Object recipe) {
+--- a/Library/src/main/java/mezz/jei/library/plugins/vanilla/cooking/fuel/FuelRecipeMaker.java
++++ b/Library/src/main/java/mezz/jei/library/plugins/vanilla/cooking/fuel/FuelRecipeMaker.java
+@@ -23,7 +23,7 @@
+ IPlatformItemStackHelper itemStackHelper = Services.PLATFORM.getItemStackHelper();
+ return ingredientManager.getAllItemStacks().stream()
+ .mapMulti((stack, consumer) -> {
+- int burnTime = itemStackHelper.getBurnTime(stack, recipeType, level.fuelValues());
++ int burnTime = itemStackHelper.getBurnTime(stack, recipeType);
+ if (burnTime > 0) {
+ consumer.accept(new FuelingRecipe(List.of(stack), burnTime));
+ }
+--- a/Library/src/main/java/mezz/jei/library/recipes/RecipeManagerInternal.java
++++ b/Library/src/main/java/mezz/jei/library/recipes/RecipeManagerInternal.java
+@@ -171,7 +171,7 @@
+ }
+
+ // hide the category if it has crafting stations, but they have all been hidden
+- if (hasCraftingStations(recipeType, true) &&
++ if (!mezz.jei.api.sanctuary.CatalogueAccess.blueprints.getAsBoolean() && hasCraftingStations(recipeType, true) &&
+ !hasCraftingStations(recipeType, false)
+ ) {
+ return true;
+@@ -244,14 +244,15 @@
+
+ public Stream getRecipesStream(IRecipeType recipeType, IFocusGroup focuses, boolean includeHidden) {
+ RecipeTypeData recipeTypeData = this.recipeTypeDataMap.get(recipeType);
+- return this.pluginManager.getRecipes(recipeType, recipeTypeData, focuses, includeHidden);
++ return this.pluginManager.getRecipes(recipeType, recipeTypeData, focuses, includeHidden)
++ .filter(recipe -> includeHidden || mezz.jei.api.sanctuary.CatalogueAccess.recipe.test(recipeTypeData.getRecipeCategory(), recipe));
+ }
+
+ public Stream>> getCraftingStations(IRecipeType recipeType, boolean includeHidden) {
+ Stream>> craftingStations = recipeTypeDataMap.get(recipeType)
+ .getCraftingStations()
+ .stream();
+- if (!includeHidden) {
++ if (!includeHidden && !mezz.jei.api.sanctuary.CatalogueAccess.blueprints.getAsBoolean()) {
+ craftingStations = craftingStations.filter(craftingStation -> getCraftingStationIngredients(craftingStation, false).findAny().isPresent());
+ }
+ return craftingStations;
+@@ -262,7 +263,7 @@
+ boolean includeHidden
+ ) {
+ Stream> ingredients = resolveCraftingStation(craftingStation);
+- if (!includeHidden) {
++ if (!includeHidden && !mezz.jei.api.sanctuary.CatalogueAccess.blueprints.getAsBoolean()) {
+ ingredients = ingredients.filter(this::isCraftingStationVisible);
+ }
+ return ingredients;
+--- a/Library/src/main/java/mezz/jei/library/transfer/BasicRecipeTransferInfo.java
++++ b/Library/src/main/java/mezz/jei/library/transfer/BasicRecipeTransferInfo.java
+@@ -76,6 +76,6 @@
+ Slot slot = container.getSlot(i);
+ slots.add(slot);
+ }
+- return slots;
++ return mezz.jei.api.sanctuary.CatalogueAccess.inventory.apply(container, slots);
+ }
+ }
+--- a/Library/src/main/java/mezz/jei/library/util/BrewingRecipeMakerCommon.java
++++ b/Library/src/main/java/mezz/jei/library/util/BrewingRecipeMakerCommon.java
+@@ -21,7 +21,7 @@
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.PotionItem;
+ import net.minecraft.world.item.alchemy.Potion;
+-import net.minecraft.world.item.alchemy.PotionBrewing;
++import net.minecraft.world.item.crafting.RecipeMap;
+ import net.minecraft.world.item.alchemy.PotionContents;
+ import net.minecraft.world.item.crafting.Ingredient;
+ import net.minecraft.world.item.crafting.display.SlotDisplay;
+@@ -43,42 +43,28 @@
+ public static Set getVanillaBrewingRecipes(
+ IVanillaRecipeFactory recipeFactory,
+ IIngredientManager ingredientManager,
+- PotionBrewing potionBrewing,
++ RecipeMap potionBrewing,
+ ContextMap contextMap
+ ) {
+- Set recipes = new HashSet<>();
+- Registry potionRegistry = RegistryUtil.getRegistry(Registries.POTION);
+- IIngredientHelper itemStackHelper = ingredientManager.getIngredientHelper(VanillaTypes.ITEM_STACK);
+-
+- IngredientSet knownPotions = getBaseKnownPotions(ingredientManager, potionRegistry, potionBrewing, contextMap);
+-
+- IPlatformIngredientHelper ingredientHelper = Services.PLATFORM.getIngredientHelper();
+- IngredientSet potionReagents = ingredientHelper.getPotionIngredients(potionBrewing)
+- .map(Ingredient::display)
+- .flatMap(display -> display.resolve(contextMap, SlotDisplay.ItemStackContentsFactory.INSTANCE))
+- .collect(Collectors.toCollection(() -> new IngredientSet<>(itemStackHelper, UidContext.Ingredient)));
+-
+- boolean foundNewPotions;
+- do {
+- List newPotions = getNewPotions(
+- potionBrewing,
+- recipeFactory,
+- itemStackHelper,
+- knownPotions,
+- potionReagents,
+- recipes
+- );
+- foundNewPotions = !newPotions.isEmpty();
+- knownPotions.addAll(newPotions);
+- } while (foundNewPotions);
+-
+- return recipes;
+- }
++ Set recipes = new HashSet<>();
++ Registry potionRegistry = RegistryUtil.getRegistry(Registries.POTION);
++ var knownPotions=getBaseKnownPotions(ingredientManager,potionRegistry,potionBrewing,contextMap);
++ var nativeRecipes=potionBrewing.byType(net.minecraft.world.item.crafting.RecipeType.BREWING);
++ nativeRecipes.forEach(holder -> knownPotions.add(holder.value().getOutput().create()));
++ for(var holder:nativeRecipes) {
++ var recipe=holder.value();
++ var inputs=knownPotions.stream().filter(recipe.getInput()::test).toList();
++ var reagents=recipe.getReagent().ingredient().display().resolve(contextMap,SlotDisplay.ItemStackContentsFactory.INSTANCE)
++ .filter(recipe.getReagent()::test).toList();
++ if(!inputs.isEmpty()&&!reagents.isEmpty())recipes.add(recipeFactory.createBrewingRecipe(reagents,inputs,recipe.getOutput().create(),holder.id().identifier()));
++ }
++ return recipes;
++ }
+
+ private static IngredientSet getBaseKnownPotions(
+ IIngredientManager ingredientManager,
+ Registry potionRegistry,
+- PotionBrewing potionBrewing,
++ RecipeMap potionBrewing,
+ ContextMap contextMap
+ ) {
+ IPlatformIngredientHelper ingredientHelper = Services.PLATFORM.getIngredientHelper();
+@@ -104,95 +90,4 @@
+ return knownPotions;
+ }
+
+- private static List getNewPotions(
+- PotionBrewing potionBrewing,
+- IVanillaRecipeFactory recipeFactory,
+- IIngredientHelper itemStackHelper,
+- Collection knownPotions,
+- Collection potionReagents,
+- Collection recipes
+- ) {
+- List newPotions = new ArrayList<>();
+- for (ItemStack potionInput : knownPotions) {
+- Object inputId = itemStackHelper.getUid(potionInput, UidContext.Recipe);
+- String inputPathId = PotionSubtypeInterpreter.INSTANCE.getStringName(potionInput);
+-
+- for (ItemStack potionReagent : potionReagents) {
+- ItemStack potionInputCopy = potionInput.copy();
+- ItemStack potionOutput = getOutput(potionBrewing, potionInputCopy, potionReagent);
+- if (potionOutput.isEmpty()) {
+- continue;
+- }
+-
+- if (potionInput.getItem() instanceof PotionItem && potionOutput.getItem() instanceof PotionItem) {
+- Optional> potionOutputType = potionOutput.getOrDefault(DataComponents.POTION_CONTENTS, PotionContents.EMPTY).potion();
+- if (potionOutputType.isEmpty()) {
+- continue;
+- }
+- }
+-
+- Object outputId = itemStackHelper.getUid(potionOutput, UidContext.Recipe);
+- if (Objects.equals(inputId, outputId)) {
+- continue;
+- }
+-
+- Identifier outputResourceLocation = itemStackHelper.getIdentifier(potionOutput);
+- String outputPathId = PotionSubtypeInterpreter.INSTANCE.getStringName(potionOutput);
+- String outputModId = outputResourceLocation.getNamespace();
+- String uidPath = ResourceLocationUtil.sanitizePath(inputPathId + ".to." + outputPathId);
+- IJeiBrewingRecipe recipe = recipeFactory.createBrewingRecipe(
+- List.of(potionReagent),
+- potionInputCopy,
+- potionOutput,
+- Identifier.fromNamespaceAndPath(outputModId, uidPath)
+- );
+-
+- IJeiBrewingRecipe existingRecipe = recipes.stream()
+- .filter(recipe::equals)
+- .findFirst()
+- .orElse(null);
+- if (existingRecipe == null) {
+- recipes.add(recipe);
+- newPotions.add(potionOutput);
+- } else {
+- // This is a recipe with the same uid and output as an existing recipe,
+- // but it has a different reagent.
+- // Create a recipe that combines the two.
+- IngredientSet reagents = new IngredientSet<>(itemStackHelper, UidContext.Recipe);
+- reagents.addAll(existingRecipe.getIngredients());
+- reagents.add(potionReagent);
+- if (reagents.size() != existingRecipe.getIngredients().size()) {
+- IJeiBrewingRecipe replacementRecipe = recipeFactory.createBrewingRecipe(
+- List.copyOf(reagents),
+- existingRecipe.getPotionInputs(),
+- existingRecipe.getPotionOutput(),
+- existingRecipe.getUid()
+- );
+- recipes.remove(existingRecipe);
+- recipes.add(replacementRecipe);
+- }
+- }
+- }
+- }
+- return newPotions;
+- }
+-
+- private static ItemStack getOutput(PotionBrewing potionBrewing, ItemStack potion, ItemStack itemStack) {
+- try {
+- ItemStack result = potionBrewing.mix(itemStack, potion);
+- if (result != itemStack) {
+- return result;
+- }
+- } catch (RuntimeException e) {
+- String potionInfo = ErrorUtil.getItemStackInfo(potion);
+- String itemStackInfo = ErrorUtil.getItemStackInfo(itemStack);
+- LOGGER.error(
+- "A modded potion mix crashed: \nPotion: {}\nItemStack: {}",
+- potionInfo,
+- itemStackInfo,
+- e
+- );
+- }
+- return ItemStack.EMPTY;
+- }
+ }
diff --git a/mods/sanctuary-test/build.gradle b/mods/sanctuary-test/build.gradle
new file mode 100644
index 0000000..74a5242
--- /dev/null
+++ b/mods/sanctuary-test/build.gradle
@@ -0,0 +1,20 @@
+plugins { id 'net.fabricmc.fabric-loom' }
+group='fr.koka.sanctuarytest'
+version=rootProject.mod_version
+base { archivesName='sanctuary-test' }
+dependencies {
+ minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
+ implementation "net.fabricmc:fabric-loader:${rootProject.loader_version}"
+ implementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
+ implementation project(':sanctuary')
+}
+java { toolchain.languageVersion=JavaLanguageVersion.of(25); withSourcesJar() }
+tasks.withType(JavaCompile).configureEach { options.encoding='UTF-8'; options.release=25 }
+processResources {
+ def values=[version:project.version,minecraft:rootProject.minecraft_version.replace('-pre-','-pre.'),loader:rootProject.loader_version]
+ inputs.properties(values)
+ filesMatching('fabric.mod.json') { expand(values) }
+}
+loom { runs { client { runDir 'run/quick-test' } } }
+tasks.named('test') { failOnNoDiscoveredTests=false }
+tasks.named('jar') { from(rootProject.file('LICENSE')) { rename { 'LICENSE_sanctuary_test' } } }
diff --git a/mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestClient.java b/mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestClient.java
new file mode 100644
index 0000000..852743a
--- /dev/null
+++ b/mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestClient.java
@@ -0,0 +1,24 @@
+package fr.koka.sanctuarytest;
+
+import fr.koka.sanctuary.client.IntroClient;
+import java.util.*;
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.CreateWorldScreen;
+
+public final class QuickTestClient implements ClientModInitializer {
+ private final Set initialized=Collections.newSetFromMap(new WeakHashMap<>());
+ public void onInitializeClient(){
+ ClientConfigurationNetworking.registerGlobalReceiver(QuickTestMod.Mode.TYPE,(payload,context)->
+ context.client().execute(()->IntroClient.setQuickTestIntro(payload.quick())));
+ ScreenEvents.AFTER_INIT.register((minecraft,screen,width,height)->{
+ if(screen instanceof CreateWorldScreen creation&&initialized.add(screen)){
+ var state=creation.getUiState();
+ state.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat"))
+ .findFirst().ifPresent(state::setWorldType);
+ }
+ });
+ }
+}
diff --git a/mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestMod.java b/mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestMod.java
new file mode 100644
index 0000000..ae1393c
--- /dev/null
+++ b/mods/sanctuary-test/src/main/java/fr/koka/sanctuarytest/QuickTestMod.java
@@ -0,0 +1,25 @@
+package fr.koka.sanctuarytest;
+
+import fr.koka.sanctuary.testing.QuickTestWorld;
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.networking.v1.*;
+import net.minecraft.network.FriendlyByteBuf;
+import net.minecraft.network.codec.StreamCodec;
+import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
+import net.minecraft.resources.Identifier;
+
+public final class QuickTestMod implements ModInitializer {
+ public record Mode(boolean quick) implements CustomPacketPayload {
+ public static final Type TYPE=new Type<>(Identifier.fromNamespaceAndPath("sanctuary_test","mode"));
+ public static final StreamCodec CODEC=CustomPacketPayload.codec((p,b)->b.writeBoolean(p.quick),b->new Mode(b.readBoolean()));
+ public Type type(){return TYPE;}
+ }
+ public void onInitialize(){
+ QuickTestWorld.enable();
+ PayloadTypeRegistry.clientboundConfiguration().register(Mode.TYPE,Mode.CODEC);
+ ServerConfigurationConnectionEvents.CONFIGURE.register((handler,server)->{
+ if(ServerConfigurationNetworking.canSend(handler,Mode.TYPE))
+ ServerConfigurationNetworking.send(handler,new Mode(QuickTestWorld.active(server)));
+ });
+ }
+}
diff --git a/mods/sanctuary-test/src/main/resources/assets/sanctuary_test/lang/en_us.json b/mods/sanctuary-test/src/main/resources/assets/sanctuary_test/lang/en_us.json
new file mode 100644
index 0000000..a81d908
--- /dev/null
+++ b/mods/sanctuary-test/src/main/resources/assets/sanctuary_test/lang/en_us.json
@@ -0,0 +1,3 @@
+{
+ "generator.sanctuary_test.flat": "Sanctuary — Quick Test"
+}
diff --git a/mods/sanctuary-test/src/main/resources/assets/sanctuary_test/lang/fr_fr.json b/mods/sanctuary-test/src/main/resources/assets/sanctuary_test/lang/fr_fr.json
new file mode 100644
index 0000000..07cd071
--- /dev/null
+++ b/mods/sanctuary-test/src/main/resources/assets/sanctuary_test/lang/fr_fr.json
@@ -0,0 +1,3 @@
+{
+ "generator.sanctuary_test.flat": "Sanctuary — Test rapide"
+}
diff --git a/mods/sanctuary-test/src/main/resources/data/minecraft/tags/worldgen/world_preset/normal.json b/mods/sanctuary-test/src/main/resources/data/minecraft/tags/worldgen/world_preset/normal.json
new file mode 100644
index 0000000..b43aae0
--- /dev/null
+++ b/mods/sanctuary-test/src/main/resources/data/minecraft/tags/worldgen/world_preset/normal.json
@@ -0,0 +1,6 @@
+{
+ "replace": false,
+ "values": [
+ "sanctuary_test:flat"
+ ]
+}
diff --git a/mods/sanctuary-test/src/main/resources/data/sanctuary_test/worldgen/world_preset/flat.json b/mods/sanctuary-test/src/main/resources/data/sanctuary_test/worldgen/world_preset/flat.json
new file mode 100644
index 0000000..c1b3e4a
--- /dev/null
+++ b/mods/sanctuary-test/src/main/resources/data/sanctuary_test/worldgen/world_preset/flat.json
@@ -0,0 +1,55 @@
+{
+ "dimensions": {
+ "minecraft:overworld": {
+ "type": "minecraft:overworld",
+ "generator": {
+ "type": "minecraft:flat",
+ "settings": {
+ "biome": "minecraft:plains",
+ "features": false,
+ "lakes": false,
+ "layers": [
+ {
+ "block": "minecraft:bedrock",
+ "height": 1
+ },
+ {
+ "block": "minecraft:stone",
+ "height": 60
+ },
+ {
+ "block": "minecraft:dirt",
+ "height": 2
+ },
+ {
+ "block": "minecraft:grass_block",
+ "height": 1
+ }
+ ],
+ "structure_overrides": []
+ }
+ }
+ },
+ "minecraft:the_end": {
+ "type": "minecraft:the_end",
+ "generator": {
+ "type": "minecraft:noise",
+ "biome_source": {
+ "type": "minecraft:the_end"
+ },
+ "settings": "minecraft:end"
+ }
+ },
+ "minecraft:the_nether": {
+ "type": "minecraft:the_nether",
+ "generator": {
+ "type": "minecraft:noise",
+ "biome_source": {
+ "type": "minecraft:multi_noise",
+ "preset": "minecraft:nether"
+ },
+ "settings": "minecraft:nether"
+ }
+ }
+ }
+}
diff --git a/mods/sanctuary-test/src/main/resources/fabric.mod.json b/mods/sanctuary-test/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..b980599
--- /dev/null
+++ b/mods/sanctuary-test/src/main/resources/fabric.mod.json
@@ -0,0 +1,15 @@
+{
+ "schemaVersion": 1,
+ "id": "sanctuary_test",
+ "version": "${version}",
+ "name": "Sanctuary — Quick Test",
+ "description": "Optional development profile: flat worlds, real Sanctuary systems, automatically skipped introduction.",
+ "authors": ["KOKA99CAB"],
+ "license": "GPL-3.0-or-later",
+ "environment": "*",
+ "entrypoints": {
+ "main": ["fr.koka.sanctuarytest.QuickTestMod"],
+ "client": ["fr.koka.sanctuarytest.QuickTestClient"]
+ },
+ "depends": {"fabricloader": ">=${loader}", "minecraft": "${minecraft}", "java": ">=25", "fabric-api": "*", "sanctuary": "${version}"}
+}
diff --git a/mods/sanctuary/build.gradle b/mods/sanctuary/build.gradle
index 63080b8..1b78468 100644
--- a/mods/sanctuary/build.gradle
+++ b/mods/sanctuary/build.gradle
@@ -25,6 +25,11 @@ if (clientRenderTests) {
java.srcDir('src/clientGametest/java')
}
tasks.named('runClientGameTest') {
+ systemProperty('sanctuary.test.noVsync', providers.gradleProperty('sanctuaryClientNoVsync').getOrElse('false'))
+ systemProperty('sanctuary.test.quick', providers.gradleProperty('sanctuaryQuickTests').getOrElse('false'))
+ systemProperty('sanctuary.test.introVisualOnly', providers.gradleProperty('sanctuaryIntroVisualOnly').getOrElse('false'))
+ systemProperty('sanctuary.test.atlasBaseline', providers.gradleProperty('sanctuaryAtlasBaseline').getOrElse('false'))
+ systemProperty('sanctuary.test.inventoryClient', providers.gradleProperty('sanctuaryInventoryClientTests').getOrElse('false'))
systemProperty('sanctuary.test.highland304', providers.gradleProperty('sanctuaryHighland304Visual').getOrElse('false'))
}
}
@@ -90,9 +95,139 @@ tasks.named('runGameTest') {
tasks.named('processGametestResources') {
def villageOnly = providers.gradleProperty('sanctuaryVillageOnly').map { it.toBoolean() }.getOrElse(false)
def focused = providers.gradleProperty('sanctuaryFocusedTests').getOrElse('').split(',').findAll { !it.isEmpty() }
- def focusClasses = [aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests']
+ def focusClasses = [starter052: 'Starter052GameTests', dispenser051: 'Dispenser051GameTests', head050: 'Head050GameTests', boatshead: 'BoatsHead049GameTests', poultry: 'Poultry048GameTests', starinspect: 'StarInspection047GameTests', skyflight: 'SkyFlight046GameTests', eggaudit: 'Companion044GameTests', headblocks: 'HeadBlocks043GameTests', operator: 'OperatorStars040GameTests', starter: 'Starter039GameTests', gravelayout: 'GraveInventory039GameTests', foodknowledge: 'FoodKnowledge039GameTests', graves: 'GravesFood038GameTests', realtime: 'Realtime037GameTests', corrections: 'Corrections036GameTests', collections: 'Collections035GameTests', notifications: 'Notifications034GameTests', menus: 'Menus033GameTests', aerial: 'Aerial24WorldGameTests', transit: 'Transit29WorldGameTests', village: 'Village306GameTests', drained: 'Drained307GameTests', expeditions: 'AncientExpeditions001GameTests', progression: 'Progression003GameTests', map: 'Atlas005GameTests', demeure: 'Demeure006GameTests', mining: 'Mining008GameTests', building: 'Building009GameTests', inventory: 'Inventory010GameTests', cycle: 'Cycle011GameTests', inventoryflow: 'Inventory012GameTests', sorting: 'Inventory014GameTests', tutorial: 'Tutorial016GameTests', movement: 'Movement017GameTests', accessories: 'Accessory018GameTests', companions: 'Companion019GameTests', familiar: 'Familiar020GameTests', carry: 'Carry027GameTests', familiarhit: 'Familiar029GameTests', refonte: 'Companion032GameTests', lights: 'Dynamic030GameTests', cosmetics: 'HeadCosmetic021GameTests', startup: 'StartupPerformanceGameTests', recipes: 'Recipes024GameTests']
focused.each { if (!focusClasses.containsKey(it)) throw new GradleException('Unknown focused native test: ' + it) }
inputs.property('focusedNative', focused)
+ def introClient = providers.gradleProperty('sanctuaryIntroClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('introClient', introClient)
+ if (introClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.client.Intro031ClientChecks') }
+ def progressionClient = providers.gradleProperty('sanctuaryProgressionClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('progressionClient', progressionClient)
+ def mapPerfClient = providers.gradleProperty('sanctuaryMapPerfClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('mapPerfClient', mapPerfClient)
+ if (mapPerfClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Atlas026ClientChecks') }
+ def lightsClient = providers.gradleProperty('sanctuaryLightsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('lightsClient', lightsClient)
+ if (lightsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Dynamic030ClientChecks') }
+ def skillsClient = providers.gradleProperty('sanctuarySkillsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('skillsClient', skillsClient)
+ if (skillsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Skills025ClientChecks') }
+ def fogClient = providers.gradleProperty('sanctuaryFogClientTests').map { it.toBoolean() }.getOrElse(false)
+ def sky056Client = providers.gradleProperty('sanctuarySky056ClientTests').map { it.toBoolean() }.getOrElse(false)
+ def mobHead060Client = providers.gradleProperty('sanctuaryMobHead060ClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('mobHead060Client', mobHead060Client)
+ if (mobHead060Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.MobHead060ClientChecks') }
+ def atlas059Client = providers.gradleProperty('sanctuaryAtlas059ClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('atlas059Client', atlas059Client)
+ if (atlas059Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Atlas059ClientChecks') }
+ def atlas058Client = providers.gradleProperty('sanctuaryAtlas058ClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('atlas058Client', atlas058Client)
+ if (atlas058Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Atlas058ClientChecks') }
+ def weather057Client = providers.gradleProperty('sanctuaryWeather057ClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('weather057Client', weather057Client)
+ if (weather057Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Weather057ClientChecks') }
+ inputs.property('sky056Client', sky056Client)
+ if (sky056Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Sky056ClientChecks') }
+ def strengthCarryClient = providers.gradleProperty('sanctuaryStrengthCarryClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('strengthCarryClient', strengthCarryClient)
+ if (strengthCarryClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Strength055ClientChecks') }
+ def combatClient = providers.gradleProperty('sanctuaryCombatClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('combatClient', combatClient)
+ if (combatClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Combat054ClientChecks') }
+ inputs.property('fogClient', fogClient)
+ if (fogClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Fog053ClientChecks') }
+ def starterNameClient = providers.gradleProperty('sanctuaryStarterNameClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('starterNameClient', starterNameClient)
+ if (starterNameClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Starter052ClientChecks') }
+ def head050Client = providers.gradleProperty('sanctuaryHead050ClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('head050Client', head050Client)
+ if (head050Client) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Head050ClientChecks') }
+ def boatsHeadClient = providers.gradleProperty('sanctuaryBoatsHeadClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('boatsHeadClient', boatsHeadClient)
+ if (boatsHeadClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.BoatsHead049ClientChecks') }
+ def poultryClient = providers.gradleProperty('sanctuaryPoultryClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('poultryClient', poultryClient)
+ if (poultryClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Poultry048ClientChecks') }
+ def starInspectionClient = providers.gradleProperty('sanctuaryStarInspectionClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('starInspectionClient', starInspectionClient)
+ if (starInspectionClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.StarInspection047ClientChecks') }
+ def skyFlightClient = providers.gradleProperty('sanctuarySkyFlightClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('skyFlightClient', skyFlightClient)
+ if (skyFlightClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.SkyFlight046ClientChecks') }
+ def titleClient = providers.gradleProperty('sanctuaryTitleClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('titleClient', titleClient)
+ if (titleClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Title045ClientChecks') }
+ def headBlocksClient = providers.gradleProperty('sanctuaryHeadBlocksClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('headBlocksClient', headBlocksClient)
+ if (headBlocksClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.HeadBlocks043ClientChecks') }
+ def lanCarryClient = providers.gradleProperty('sanctuaryLanCarryClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('lanCarryClient', lanCarryClient)
+ if (lanCarryClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.LanCarry042ClientChecks') }
+ def operatorClient = providers.gradleProperty('sanctuaryOperatorClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('operatorClient', operatorClient)
+ if (operatorClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Operator040ClientChecks') }
+ def foodKnowledgeClient = providers.gradleProperty('sanctuaryFoodKnowledgeClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('foodKnowledgeClient', foodKnowledgeClient)
+ if (foodKnowledgeClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.FoodPanorama039ClientChecks') }
+ def gravesClient = providers.gradleProperty('sanctuaryGravesClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('gravesClient', gravesClient)
+ if (gravesClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.GravesFood038ClientChecks') }
+ def realtimeClient = providers.gradleProperty('sanctuaryRealtimeClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('realtimeClient', realtimeClient)
+ if (realtimeClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Realtime037ClientChecks') }
+ def blueprintClient = providers.gradleProperty('sanctuaryBlueprintClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('blueprintClient', blueprintClient)
+ if (blueprintClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Blueprint036ClientChecks') }
+ def correctionsClient = providers.gradleProperty('sanctuaryCorrectionsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('correctionsClient', correctionsClient)
+ if (correctionsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Corrections036ClientChecks') }
+ def collectionsClient = providers.gradleProperty('sanctuaryCollectionsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('collectionsClient', collectionsClient)
+ if (collectionsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Collections035ClientChecks') }
+ def notificationsClient = providers.gradleProperty('sanctuaryNotificationsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('notificationsClient', notificationsClient)
+ if (notificationsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Notifications034ClientChecks') }
+ def menusClient = providers.gradleProperty('sanctuaryMenusClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('menusClient', menusClient)
+ if (menusClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Menus033ClientChecks') }
+ def recipesClient = providers.gradleProperty('sanctuaryRecipesClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('recipesClient', recipesClient)
+ if (recipesClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Recipes024ClientChecks') }
+ def cosmeticsClient = providers.gradleProperty('sanctuaryHeadCosmeticsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('cosmeticsClient', cosmeticsClient)
+ if (cosmeticsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.HeadCosmetic021ClientChecks') }
+ def familiarClient = providers.gradleProperty('sanctuaryFamiliarClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('familiarClient', familiarClient)
+ if (familiarClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Familiar020ClientChecks') }
+ def refonteClient = providers.gradleProperty('sanctuaryRefonteClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('refonteClient', refonteClient)
+ if (refonteClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Companion032ClientChecks') }
+ def companionsClient = providers.gradleProperty('sanctuaryCompanionsClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('companionsClient', companionsClient)
+ if (companionsClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Companion019ClientChecks') }
+ def accessoriesClient = providers.gradleProperty('sanctuaryAccessoriesClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('accessoriesClient', accessoriesClient)
+ if (accessoriesClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Accessory018ClientChecks') }
+ def movementClient = providers.gradleProperty('sanctuaryMovementClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('movementClient', movementClient)
+ if (movementClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Movement017ClientChecks') }
+ def tutorialClient = providers.gradleProperty('sanctuaryTutorialClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('tutorialClient', tutorialClient)
+ if (tutorialClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Tutorial016ClientChecks') }
+ def sortingClient = providers.gradleProperty('sanctuarySortingClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('sortingClient', sortingClient)
+ if (sortingClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Inventory014ClientChecks') }
+ def inlineClient = providers.gradleProperty('sanctuaryInlineClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('inlineClient', inlineClient)
+ if (inlineClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Progression013ClientChecks') }
+ def inventoryClient = providers.gradleProperty('sanctuaryInventoryClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('inventoryClient', inventoryClient)
+ def cycleClient = providers.gradleProperty('sanctuaryCycleClientTests').map { it.toBoolean() }.getOrElse(false)
+ inputs.property('cycleClient', cycleClient)
+ if (cycleClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Cycle011ClientChecks') }
+ if (inventoryClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Inventory010ClientChecks') }
+
+ if (progressionClient) filter { line -> line.replace('fr.koka.sanctuary.gametest.SanctuaryClientRenderTests', 'fr.koka.sanctuary.gametest.Progression003ClientChecks') }
inputs.property('villageOnly', villageOnly)
def atlasOnly = providers.gradleProperty('sanctuaryAtlasOnly').map { it.toBoolean() }.getOrElse(false)
@@ -153,6 +288,10 @@ tasks.named('processGametestResources') {
}
dependencies {
+ implementation project(':jei')
+ include project(':jei')
+ implementation project(':demeure')
+ include project(':demeure')
minecraft "com.mojang:minecraft:${rootProject.minecraft_version}"
implementation "net.fabricmc:fabric-loader:${rootProject.loader_version}"
implementation "net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}"
@@ -609,6 +748,14 @@ tasks.register('expansionOcean23Smoke', JavaExec) {
}
tasks.named('check') { dependsOn('expansionOcean23Smoke') }
+tasks.register('ancientExpeditions001Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.expansion.AncientExpeditions001Smoke'
+}
+tasks.named('check') { dependsOn('ancientExpeditions001Smoke') }
+
[
secrets23Smoke: 'fr.koka.sanctuary.worldgen.mineral.Secrets23Smoke',
structureCensusSmoke: 'fr.koka.sanctuary.knowledge.structures.StructureCensusSmoke',
@@ -703,3 +850,143 @@ tasks.register('balancedRelief303Smoke', JavaExec) {
mainClass = 'fr.koka.sanctuary.worldgen.BalancedRelief303Smoke'
}
tasks.named('check') { dependsOn('balancedRelief303Smoke') }
+
+// PROG-01: pure contract checks plus native server/player tests.
+tasks.register('progression003Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.progression.ProgressionSmoke'
+}
+tasks.named('check') { dependsOn('progression003Smoke') }
+
+// MAP-01: durable observation and progression migration contracts.
+tasks.register('atlas005Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.atlas.AtlasSmoke'
+}
+tasks.named('check') { dependsOn('atlas005Smoke') }
+
+// CYCLE-01 / FACTION-01: durable charge and reset contract.
+tasks.register('cycle011Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.cycle.CycleSmoke'
+}
+tasks.named('check') { dependsOn('cycle011Smoke') }
+
+// First tutorial actions: bounded rewards and independent durable claims.
+tasks.register('tutorial016Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.tutorial.TutorialSmoke'
+}
+tasks.named('check') { dependsOn('tutorial016Smoke') }
+
+tasks.register('movement017Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.progression.Movement017Smoke'
+}
+tasks.named('check') { dependsOn('movement017Smoke') }
+
+tasks.register('inventoryDrag018Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.client.InventoryDragPathSmoke'
+}
+tasks.named('check') { dependsOn('inventoryDrag018Smoke') }
+
+tasks.register('companion019Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.companion.Companion019Smoke'
+}
+tasks.named('check') { dependsOn('companion019Smoke') }
+
+// Visual-light spatial index, dirty budget and dropped-item stress checks.
+tasks.register('dynamic030Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.lighting.Dynamic030Smoke'
+}
+tasks.named('check') { dependsOn('dynamic030Smoke') }
+
+// INTRO-01: reversible UUID encoding, accelerating score and durable first arrival.
+tasks.register('intro031Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.progression.Intro031Smoke'
+}
+tasks.named('check') { dependsOn('intro031Smoke') }
+
+if (providers.gradleProperty('sanctuaryQuickTests').map { it.toBoolean() }.getOrElse(false)) {
+ dependencies { gametestRuntimeOnly project(':sanctuary-test') }
+}
+
+tasks.register('notifications034Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.notifications.NotificationFeedSmoke'
+}
+tasks.named('check') { dependsOn('notifications034Smoke') }
+
+tasks.register('weather057Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.weather.Weather057Smoke'
+}
+tasks.named('check') { dependsOn('weather057Smoke') }
+
+
+tasks.register('collections035Catalogue', Exec) {
+ group = 'verification'
+ workingDir rootProject.projectDir
+ commandLine 'python3', 'scripts/compile_collections.py'
+}
+tasks.named('check') { dependsOn('collections035Catalogue') }
+
+tasks.register('notifications036Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.notifications.NotificationLayoutSmoke'
+}
+tasks.named('check') { dependsOn('notifications036Smoke') }
+
+
+tasks.register('realtime037Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.realtime.Realtime037Smoke'
+}
+tasks.named('check') { dependsOn('realtime037Smoke') }
+
+tasks.register('poultry048Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.boats.FlightBalance048Smoke'
+}
+tasks.named('check') { dependsOn('poultry048Smoke') }
+
+// Personal map markers: durable discoveries, independent identities and bounded data.
+tasks.register('atlas059Smoke', JavaExec) {
+ group = 'verification'
+ dependsOn('testClasses')
+ classpath = sourceSets.test.runtimeClasspath
+ mainClass = 'fr.koka.sanctuary.atlas.AtlasMarker059Smoke'
+}
+tasks.named('check') { dependsOn('atlas059Smoke') }
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Intro031ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Intro031ClientChecks.java
new file mode 100644
index 0000000..9b1a651
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Intro031ClientChecks.java
@@ -0,0 +1,138 @@
+package fr.koka.sanctuary.client;
+
+import fr.koka.sanctuary.progression.*;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+
+public final class Intro031ClientChecks implements FabricClientGameTest {
+ private static final KeyEvent ENTER=new KeyEvent(com.mojang.blaze3d.platform.InputConstants.KEY_RETURN,0,0);
+ public void runTest(ClientGameTestContext c) {
+ UUID testUuid=UUID.fromString("01234567-89ab-cdef-fedc-ba9876543210");
+ Screen previous=c.computeOnClient(m -> m.gui.screen());
+ int scale=c.computeOnClient(m -> m.options.guiScale().get());
+ String language=c.computeOnClient(m -> m.getLanguageManager().getSelected());
+ var clock=new AtomicLong();var completed=new AtomicInteger();
+ c.runOnClient(m -> m.gui.setScreen(new IntroScreen(testUuid,skipped -> completed.incrementAndGet(),clock::get)));
+ c.waitTick();c.takeScreenshot("sanctuary-beta031-intro-00-black");
+ c.runOnClient(m -> m.gui.screen().onClose());
+ require(completed.get()==0,"Opening cannot be skipped accidentally");
+ for (int seconds:new int[]{3,7,13,20,23,25,27,29}) {
+ clock.set(seconds*1_000_000_000L);c.waitTicks(2);
+ c.takeScreenshot("sanctuary-beta031-intro-"+seconds);
+ }
+ clock.set(24_800_000_000L);
+ c.runOnClient(m -> { m.options.guiScale().set(0);m.resizeGui(); });
+ c.waitTick();c.takeScreenshot("sanctuary-beta031-intro-small");
+ c.runOnClient(m -> {
+ m.getLanguageManager().setSelected("fr_fr");m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
+ });
+ c.waitTick();c.takeScreenshot("sanctuary-beta031-intro-fr");
+ clock.set(29_600_000_000L);c.waitTicks(3);
+ require(completed.get()==1,"Completion exactly once despite extra ticks");
+ c.runOnClient(m -> m.gui.screen().onClose());require(completed.get()==1,"Skip cannot double-send completion");
+ c.runOnClient(m -> {m.gui.setScreen(previous);m.options.guiScale().set(scale);m.resizeGui();});
+ // Removing a screen (disconnect/replacement) must clean up without accepting the connection.
+ c.runOnClient(m -> m.gui.setScreen(new IntroScreen(testUuid,skipped -> completed.incrementAndGet())));
+ c.waitTicks(2);c.runOnClient(m -> m.gui.setScreen(previous));
+ require(completed.get()==1,"Unexpected removal never acknowledges arrival");
+
+ if(Boolean.getBoolean("sanctuary.test.introVisualOnly")) {
+ c.runOnClient(m -> { m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager()); });
+ System.out.println("INTRO031_VISUAL_PASS: spectral glyphs, circular spiral, expanding portal, white frame, resize");
+ return;
+ }
+ var armed=new AtomicBoolean(true);var submitted=new AtomicBoolean();var observed=new AtomicBoolean();
+ var sawWorldUnderFlash=new AtomicBoolean();
+ var count=new AtomicInteger();var introRef=new java.util.concurrent.atomic.AtomicReference();
+ ClientTickEvents.END_CLIENT_TICK.register(m -> {
+ if(!armed.get())return;
+ if(m.player!=null && IntroSpawnFlash.active()) sawWorldUnderFlash.set(true);
+ if(m.gui.screen() instanceof HelloWorldScreen screen && submitted.compareAndSet(false,true)) {
+ require(m.player==null && m.getSingleplayerServer().getPlayerCount()==0,"Form before spawn");
+ // Early acknowledgements cannot bypass character creation.
+ ClientConfigurationNetworking.send(new ProgressionPayloads.IntroFinished());
+ screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(ENTER);
+ screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals("sanctuary.progression.enter"))
+ .findFirst().orElseThrow().onPress(ENTER);
+ }
+ if(m.gui.screen() instanceof IntroScreen screen && !screen.isFinished()) {
+ if(introRef.getAndSet(screen)!=screen)count.incrementAndGet();
+ require(m.player==null && m.level==null,"No client world or player during the cinematic");
+ require(m.getSingleplayerServer().getPlayerCount()==0,"No server player during the cinematic");
+ if(observed.compareAndSet(false,true)) {
+ require(screen.uuid().equals(m.getGameProfile().id()),"Server UUID reaches the real screen");
+ m.getSingleplayerServer().execute(() -> {
+ var identity=ProgressionService.session(m.getSingleplayerServer()).inhabitants.get(screen.uuid());
+ require(identity!=null && !identity.arrived(),"Validated identity precedes hello announcement");
+ });
+ // A repeated form packet while waiting must not reopen the form or duplicate the identity.
+ ClientConfigurationNetworking.send(new ProgressionPayloads.Create("duplicate",-1));
+ }
+ }
+ });
+ net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave save;
+ try {
+ try(var world=c.worldBuilder().adjustSettings(settings -> {
+ settings.setName("Sanctuary intro031 "+UUID.randomUUID());settings.setSeed("42");
+ settings.setWorldType(settings.getNormalPresetList().stream()
+ .filter(e -> e.preset()!=null && e.preset().getRegisteredName().equals("sanctuary:sanctuary"))
+ .findFirst().orElseThrow());
+ settings.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
+ }).create()) {
+ save=world.getWorldSave();world.getConnection().waitForChunksDownload(2400);
+ c.waitFor(m -> m.player!=null && m.gui.screen()==null && ProgressionClient.active(),1200);
+ require(observed.get() && count.get()==1,"Real first arrival plays one complete cinematic");
+ require(sawWorldUnderFlash.get(),"White exposure bridges the real spawn");
+ c.waitFor(m -> !IntroSpawnFlash.active(),400);
+ c.takeScreenshot("sanctuary-beta031-spawn-after-white");
+ world.getServer().runOnServer(server -> {
+ var player=server.getPlayerList().getPlayers().getFirst();
+ require(ProgressionService.session(server).inhabitants.get(player.getUUID()).arrived(),"Join consumes first arrival");
+ require(!ProgressionService.session(server).inhabitants.get(player.getUUID()).bio().equals("duplicate"),"Repeated form ignored");
+ server.saveEverything(true,true,true);
+ });
+ }
+ try(var world=save.open()) {
+ world.getConnection().waitForChunksDownload(2400);
+ c.waitFor(m -> m.player!=null && ProgressionClient.active(),1200);
+ require(count.get()==1,"Reconnection bypasses cinematic");
+ c.waitFor(m -> m.gui.screen()==null,1200);
+ c.runOnClient(m -> require(!(m.gui.screen() instanceof IntroScreen),"Existing inhabitant goes directly to play"));
+ armed.set(false);
+ c.runOnClient(m -> {
+ m.gui.setScreen(new net.minecraft.client.gui.screens.ChatScreen("/sanctuary intro",false));
+ m.gui.screen().keyPressed(ENTER);
+ });
+ c.waitForScreen(IntroScreen.class);
+ c.runOnClient(m -> {
+ require(((IntroScreen)m.gui.screen()).uuid().equals(m.player.getUUID()),"Chat command replays the current UUID");
+ require(!m.gui.screen().isPauseScreen(),"The audio score also advances in replay");
+ });
+ c.waitFor(m -> m.gui.screen()==null,1200);
+ c.waitFor(m -> !IntroSpawnFlash.active(),400);
+ c.runOnClient(m -> require(ProgressionClient.active(),"Preview returns to play without changing identity"));
+ }
+ } finally {
+ armed.set(false);
+ c.runOnClient(m -> {
+ m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
+ m.options.guiScale().set(scale);m.resizeGui();
+ });
+ }
+ System.out.println("INTRO031_CLIENT_PASS: renders, resize, finish, removal, pre-spawn handshake, duplicates, reconnect");
+ }
+ private static void require(boolean value,String message) { if(!value)throw new AssertionError(message); }
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Accessory018ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Accessory018ClientChecks.java
new file mode 100644
index 0000000..6f1c3b6
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Accessory018ClientChecks.java
@@ -0,0 +1,148 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import fr.koka.sanctuary.progression.*;
+import fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.*;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.client.renderer.entity.state.AvatarRenderState;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.entity.Pose;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.phys.Vec3;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+public final class Accessory018ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m -> {
+ if(armed.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ button(screen,"sanctuary.progression.enter").onPress(enter());armed.set(false);
+ }
+ });
+ int scale=c.computeOnClient(m -> m.options.guiScale().get());var camera=c.computeOnClient(m -> m.options.getCameraType());
+ String language=c.computeOnClient(m -> m.getLanguageManager().getSelected());
+ var postureKey=c.computeOnClient(m -> InputConstants.getKey(KeyMapping.get("sanctuary.progression.lying_key").saveString()));
+ var forward=c.computeOnClient(m -> InputConstants.getKey(m.options.keyUp.saveString()));
+ int letterW=c.computeOnClient(m -> KeyMapping.get("sanctuary.progression.lying_key").getDefaultKey().getValue());
+ try(var world=c.worldBuilder().adjustSettings(s -> {
+ s.setName("Sanctuary beta018 accessories "+UUID.randomUUID());
+ s.setWorldType(s.getNormalPresetList().stream().filter(e -> e.preset()!=null && e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
+ }).create()) {
+ world.getConnection().waitForChunksDownload(2400);
+ c.waitFor(m -> m.player!=null && ProgressionClient.active() && m.gui.screen()==null,1200);
+ var server=world.getServer();
+ BlockPos floor=server.computeOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(3);
+ for(int x=-10;x<=10;x++)for(int z=-10;z<=10;z++)for(int y=-1;y<5;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1 ? Blocks.STONE : Blocks.AIR).defaultBlockState());
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()+.5);p.getInventory().clearContent();
+ p.getInventory().setItem(0,new ItemStack(Items.WOLF_SPAWN_EGG,3));p.getInventory().setItem(1,new ItemStack(AccessoryService.ZERO_CAPE));p.getInventory().setItem(2,new ItemStack(Items.PISTON,10));
+ p.inventoryMenu.broadcastChanges();return at;
+ });
+ c.waitFor(m -> m.player.onGround() && m.player.getInventory().getItem(0).is(Items.WOLF_SPAWN_EGG),200);
+ c.runOnClient(m -> {m.options.guiScale().set(2);m.resizeGui();m.options.keyUp.setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_UP));KeyMapping.get("sanctuary.progression.lying_key").setKey(InputConstants.Type.KEYBOARD.getOrCreate(letterW));KeyMapping.resetMapping();m.gui.setScreen(new InventoryScreen(m.player));});c.waitTick();
+ c.runOnClient(m -> {
+ require(m.player.inventoryMenu.slots.size()==67,"Client receives three actual equipment slots after overflow");
+ require(SanctuaryInventoryClient.header().getString().equals(net.minecraft.network.chat.Component.translatable("container.inventory").getString()),"Inventory heading has no rows suffix");
+ for(int i=64;i<67;i++)require(m.player.inventoryMenu.getSlot(i).x==78 && m.player.inventoryMenu.getSlot(i).y==24+18*(66-i),"Visual order: head, cape, egg; storage indices are unchanged");
+ require(m.player.inventoryMenu.getSlot(45).x==78 && m.player.inventoryMenu.getSlot(45).y==78,"Offhand is fourth and last");
+ });c.takeScreenshot("sanctuary-beta018-empty-slots");
+ click(c,36,true);click(c,37,true);
+ c.waitFor(m -> AccessoryAccess.of(m.player).getItem(0).is(Items.WOLF_SPAWN_EGG) && AccessoryAccess.of(m.player).getItem(1).is(AccessoryService.ZERO_CAPE),200);
+ click(c,38,false);click(c,66,false);click(c,38,false);
+ c.waitFor(m -> AccessoryAccess.of(m.player).getItem(2).is(Items.PISTON) && m.player.inventoryMenu.getCarried().isEmpty(),200);
+ c.waitFor(m -> m.player.getAttachedOrElse(AccessoryService.APPEARANCE,AccessoryService.Appearance.EMPTY).head().is(Items.PISTON),200);
+ c.runOnClient(m -> {
+ var state=avatar(m);var head=((HeadCosmeticState.Access)state).sanctuary$headCosmetic();
+ require(!head.block.isEmpty() && head.pistonHead.isEmpty(),"Piston uses block model in resting state");
+ require(state.skin.cape()!=null && state.skin.cape().texturePath().toString().contains("zero_cape"),"Cape attachment selects native cape texture");
+ });c.takeScreenshot("sanctuary-beta018-equipped-slots");
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();p.setInvulnerableTime(0);p.hurtServer(p.level(),p.damageSources().generic(),1);});
+ c.waitFor(m -> m.player.hurtTime>0 && !((HeadCosmeticState.Access)avatar(m)).sanctuary$headCosmetic().pistonHead.isEmpty(),100);
+ for(Item cosmetic:List.of(Items.DIAMOND_SWORD,Items.PLAYER_HEAD,Items.CHEST,Items.PISTON)) {
+ server.runOnServer(s -> AccessoryAccess.of(s.getPlayerList().getPlayers().getFirst()).setItem(2,new ItemStack(cosmetic)));
+ c.waitFor(m -> m.player.getAttachedOrElse(AccessoryService.APPEARANCE,AccessoryService.Appearance.EMPTY).head().is(cosmetic),200);
+ c.runOnClient(m -> {var head=((HeadCosmeticState.Access)avatar(m)).sanctuary$headCosmetic();require(!head.item.isEmpty() || !head.block.isEmpty(),"Renderable cosmetic: "+cosmetic);});
+ }
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();var r=ProgressEffects.get(p);for(int i=0;i<5;i++)r=r.buy("inventory",1);p.setAttached(ProgressionService.RECORD,r.json());ProgressEffects.apply(p,r);ProgressionService.send(p,"");});
+ c.waitFor(m -> ProgressionClient.progress.inventoryRows()==6,200);
+ for(String locale:List.of("en_us","fr_fr")) {
+ c.runOnClient(m -> {m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new InventoryScreen(m.player));});c.waitTick();
+ c.takeScreenshot("sanctuary-beta018-six-rows-"+locale);
+ }
+ c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ c.waitFor(m -> m.level.entitiesForRendering().iterator().hasNext() && clientPet(m)!=null,200);
+ var before=server.computeOnServer(s -> FamiliarService.active(s.getPlayerList().getPlayers().getFirst()).position());
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(floor.getX()+6.5,floor.getY(),floor.getZ()+.5);});
+ c.waitTicks(30);
+ require(server.computeOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);return pet!=null && pet.position().distanceToSqr(before)>.1 && pet.distanceToSqr(p)<20;}),"Familiar really walks toward moving owner");
+ for(Item egg:List.of(Items.WOLF_SPAWN_EGG,Items.COW_SPAWN_EGG,Items.PARROT_SPAWN_EGG,Items.CREEPER_SPAWN_EGG,Items.COD_SPAWN_EGG,Items.GHAST_SPAWN_EGG)) {
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(0,new ItemStack(egg));});
+ c.waitFor(m -> clientPet(m)!=null && clientPet(m).egg().is(egg),200);
+ c.runOnClient(m -> {var state=(FamiliarRenderer.State)m.getEntityRenderDispatcher().extractEntity(clientPet(m),0);require(state.renderer!=null && state.appearance!=null && state.size>0 && state.size<=.65,"Native appearance resolves for "+egg);});
+ }
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(0,new ItemStack(Items.WOLF_SPAWN_EGG));});
+ c.waitFor(m -> clientPet(m)!=null && clientPet(m).egg().is(Items.WOLF_SPAWN_EGG),200);
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);pet.teleportNearOwner();});c.waitTicks(5);
+ int particles=c.computeOnClient(m -> particleCount(m));
+ c.runOnClient(m -> {var pet=clientPet(m);m.gameMode.interact(m.player,pet,new net.minecraft.world.phys.EntityHitResult(pet,pet.position().add(0,.4,0)),net.minecraft.world.InteractionHand.MAIN_HAND);});
+ c.waitFor(m -> particleCount(m)>particles,100);
+ c.runOnClient(m -> {m.options.setCameraType(CameraType.THIRD_PERSON_BACK);m.player.setXRot(15);});c.waitTicks(3);c.takeScreenshot("sanctuary-beta018-cape-and-companion");
+ c.getInput().pressKey(letterW);c.waitFor(m -> LyingService.posture(m.player)==1 && m.player.getPose()==Pose.SWIMMING,200);
+ c.runOnClient(m -> require(avatar(m).pose==Pose.SWIMMING && !ProgressionClient.progress.lying(),"Free W gesture uses mobile low pose without purchase"));
+ c.waitTicks(15);c.takeScreenshot("sanctuary-beta018-free-lying");c.getInput().pressKey(letterW);c.waitFor(m -> LyingService.posture(m.player)==0,200);
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();p.setExperienceLevels(4);ProgressionService.send(p,"");});
+ c.waitFor(m -> m.player.experienceLevel==4,200);
+ c.runOnClient(m -> {m.options.guiScale().set(1);m.resizeGui();ProgressionClient.open(null);});c.waitTick();
+ c.runOnClient(m -> {
+ require(widgets(m.gui.screen()).filter(AbstractWidget.class::isInstance).map(AbstractWidget.class::cast).noneMatch(w -> key(w,"sanctuary.progression.lying")),"Free gesture is absent from paid aptitudes");
+ var name=widgets(m.gui.screen()).filter(AbstractWidget.class::isInstance).map(AbstractWidget.class::cast).filter(w -> key(w,"sanctuary.progression.resting")).findFirst().orElseThrow();
+ widgets(m.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast).filter(b -> b.getX()==name.getX() && b.getY()>name.getY()).min(Comparator.comparingInt(Button::getY)).orElseThrow().onPress(enter());
+ });c.waitFor(m -> ProgressionClient.progress.resting() && m.player.experienceLevel==0,200);
+ c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);c.waitTicks(3);
+ c.getInput().holdKey(o -> o.keyShift);c.getInput().pressKey(letterW);c.getInput().releaseKey(o -> o.keyShift);
+ c.waitFor(m -> LyingService.posture(m.player)==2,200);
+ c.runOnClient(m -> {var state=avatar(m);require(state.pose==Pose.SLEEPING && state.bedOrientation!=null && state.swimAmount==0 && !m.player.isSleeping(),"Rest uses bed-sleep animation without initiating server sleep");});
+ c.waitTicks(15);c.takeScreenshot("sanctuary-beta018-rest-sleep-animation");
+ c.getInput().holdKey(o -> o.keyUp);c.waitFor(m -> LyingService.posture(m.player)==0,200);c.getInput().releaseKey(o -> o.keyUp);
+ InventoryControls018Checks.run(c,server);
+ GroupWork018Checks.run(c,server);
+ System.out.println("Accessory018 native client passed: exact slots, native clicks, synchronized cape/piston, 1/6 rows FR/EN, living follower, six mob renderers, free W and paid bed-sleep rest");
+ } finally {
+ armed.set(false);c.getInput().releaseKey(o -> o.keyUp);c.getInput().releaseKey(o -> o.keyShift);
+ c.runOnClient(m -> {m.options.keyUp.setKey(forward);KeyMapping.get("sanctuary.progression.lying_key").setKey(postureKey);KeyMapping.resetMapping();m.options.setCameraType(camera);m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();});
+ }
+ }
+ private static int particleCount(Minecraft m) {String count=m.particleEngine.countParticles();return Integer.parseInt(count.substring(count.lastIndexOf(' ')+1));}
+ private static FamiliarEntity clientPet(Minecraft m) {for(var e:m.level.entitiesForRendering())if(e instanceof FamiliarEntity pet && !pet.isRemoved())return pet;return null;}
+ private static AvatarRenderState avatar(Minecraft m) {return (AvatarRenderState)m.getEntityRenderDispatcher().extractEntity(m.player,0);}
+ private static void click(ClientGameTestContext c,int index,boolean shift) {
+ c.runOnClient(m -> {var screen=(AbstractContainerScreen>)m.gui.screen();var a=(AbstractContainerScreenAccessor)screen;var slot=screen.getMenu().getSlot(index);var w=m.getWindow();
+ m.mouseHandler.onMove(w.handle(),(a.sanctuary$getLeftPos()+slot.x+8.0)*w.getScreenWidth()/w.getGuiScaledWidth(),(a.sanctuary$getTopPos()+slot.y+8.0)*w.getScreenHeight()/w.getGuiScaledHeight(),0,0);m.mouseHandler.handleAccumulatedMovement();
+ });c.waitTick();if(shift)c.getInput().holdKey(o -> o.keyShift);
+ c.runOnClient(m -> {var w=m.getWindow();m.mouseHandler.onButton(w.handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,shift ? InputConstants.MOD_SHIFT : 0),1);m.mouseHandler.onButton(w.handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,shift ? InputConstants.MOD_SHIFT : 0),0);});
+ if(shift)c.getInput().releaseKey(o -> o.keyShift);c.waitTicks(2);
+ }
+ private static Stream widgets(GuiEventListener widget) {return Stream.concat(Stream.of(widget),widget instanceof ContainerEventHandler p ? p.children().stream().flatMap(Accessory018ClientChecks::widgets) : Stream.empty());}
+ private static boolean key(AbstractWidget widget,String key) {return widget.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals(key);}
+ private static Button button(Screen screen,String key) {return widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b -> key(b,key)).findFirst().orElseThrow();}
+ private static KeyEvent enter() {return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean ok,String message) {if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas005ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas005ClientChecks.java
new file mode 100644
index 0000000..5fb4f75
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas005ClientChecks.java
@@ -0,0 +1,188 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.atlas.AtlasService;
+import fr.koka.sanctuary.client.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.components.events.ContainerEventHandler;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonInfo;
+import net.minecraft.client.server.IntegratedServer;
+import net.minecraft.network.chat.contents.TranslatableContents;
+
+/** Real packets, operator transitions and map interaction, inside the existing progression scenario. */
+final class Atlas005ClientChecks {
+ static void run(ClientGameTestContext context,TestServerContext server) {
+ server.runOnServer(s -> {
+ ((IntegratedServer)s).setWorldAllowCommands(false);
+ var player=s.getPlayerList().getPlayers().getFirst();
+ fr.koka.demeure.DemeureService.record(player,player.blockPosition(),2);
+ });
+ int[] transparencySample=server.computeOnServer(s -> {
+ var player=s.getPlayerList().getPlayers().getFirst();var chunk=player.chunkPosition();
+ int x=chunk.x()+4,z=chunk.z();
+ // Deterministic network fixture outside the passive survey radius; no world block is changed.
+ byte[] colors=new byte[256];java.util.Arrays.fill(colors,(byte)1);
+ colors[0]=0;colors[2]=net.minecraft.world.level.material.MapColor.GOLD.getPackedId(net.minecraft.world.level.material.MapColor.Brightness.NORMAL);
+ AtlasService.observations(player).observe(new fr.koka.sanctuary.atlas.AtlasStore.Tile(x,z,colors));
+ return new int[]{x*16,z*16};
+ });
+ context.runOnClient(c -> ProgressionClient.open(null,"history"));
+
+ context.waitForScreen(SanctuaryAtlasScreen.class);
+ context.waitFor(c -> c.gui.screen() instanceof SanctuaryAtlasScreen a && a.status().equals("locked"),400);
+ context.runOnClient(c -> require(((SanctuaryAtlasScreen)c.gui.screen()).observedTiles()==0,"Locked map sends no terrain to the real client"));
+ server.runOnServer(s -> require(AtlasService.observations(s.getPlayerList().getPlayers().getFirst()).size()>0,"Surveys predate the map purchase"));
+ context.takeScreenshot("sanctuary-beta007-map-locked");
+ context.runOnClient(c -> ProgressionClient.open(null,"progress"));
+ context.runOnClient(c -> descendants(c.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals("sanctuary.progression.unlock_cost"))
+ .findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0)));
+ context.waitFor(c -> ProgressionClient.progress.atlas(),400);
+ context.runOnClient(c -> require(c.player.experienceLevel==1,"Real atlas purchase costs exactly four levels"));
+ context.runOnClient(c -> ProgressionClient.open(null,"history"));
+ context.waitFor(c -> c.gui.screen() instanceof SanctuaryAtlasScreen a && a.observedTiles()>0 && a.isComplete(),600);
+ context.waitTicks(3);context.takeScreenshot("sanctuary-beta007-map-inhabitant");
+ context.waitFor(c -> ((SanctuaryAtlasScreen)c.gui.screen()).footprintCount()>0,400);
+ context.runOnClient(c -> {
+ var map=(SanctuaryAtlasScreen)c.gui.screen();
+ require(map.knownPixels()>>24)==255,"Surveyed terrain retains its opaque native map color");
+ double oldZoom=map.zoom();move(c,map.width/2.,map.height/2.);
+ c.mouseHandler.onScroll(c.getWindow().handle(),0,1);
+ require(map.zoom()>oldZoom,"Native mouse wheel zooms map");
+ drag(c,40,0,false);drag(c,-30,15,true);
+ map.recenter(); // A new request must not lock subsequent navigation.
+ drag(c,20,0,false);
+ });
+ context.waitTicks(12);
+ context.runOnClient(c -> {drag(c,-20,0,false);((SanctuaryAtlasScreen)c.gui.screen()).recenter();});
+ context.waitFor(c -> ((SanctuaryAtlasScreen)c.gui.screen()).isComplete(),600);
+ nativeClick(context,"sanctuary.atlas.grid_off");nativeClick(context,"sanctuary.atlas.grid_on");
+ context.runOnClient(c -> {
+ var map=(SanctuaryAtlasScreen)c.gui.screen();
+ dragFrom(c,2,map.height/2.,25,0); // Dark left edge outside the old frame.
+ dragFrom(c,map.width-2,map.height/2.,-25,0);
+ dragFrom(c,2,2,20,10); // Behind the header, away from widgets.
+ dragFrom(c,2,map.height-2,20,-10); // Behind the footer.
+ });
+ nativeClick(context,"sanctuary.atlas.recenter");
+ context.runOnClient(c -> {
+ var map=(SanctuaryAtlasScreen)c.gui.screen();
+ require(Math.abs(map.centerX()-c.player.getX())<.01,"Overlay button receives the click before the full-screen map");
+ var mode=button(c.gui.screen(),"sanctuary.atlas.inhabitant");
+ double oldX=map.centerX(),oldZoom=map.zoom();
+ move(c,mode.getX()+4,mode.getY()+4);
+ c.mouseHandler.onScroll(c.getWindow().handle(),0,1);
+ require(map.zoom()==oldZoom,"Wheel over a button does not zoom the terrain underneath");
+ press(c,true);move(c,mode.getX()+8,mode.getY()+8);press(c,false);
+ require(map.centerX()==oldX,"Disabled operator button cannot start map dragging");
+ });
+ context.runOnClient(c -> move(c,c.gui.screen().width/2.,c.gui.screen().height/2.+12));
+ context.waitTicks(3);context.takeScreenshot("sanctuary-beta007-demeure-fog");
+ int oldWidth=context.computeOnClient(c -> c.getWindow().getScreenWidth()),oldHeight=context.computeOnClient(c -> c.getWindow().getScreenHeight());
+ int oldScale=context.computeOnClient(c -> c.options.guiScale().get());
+ for (int[] size:new int[][]{{640,480,0},{1280,720,0},{1920,1080,1},{1920,1080,3}}) {
+ context.runOnClient(c -> {c.getWindow().setWindowed(size[0],size[1]);c.options.guiScale().set(size[2]);c.resizeGui();});
+ context.waitFor(c -> c.gui.screen() instanceof SanctuaryAtlasScreen a && a.isComplete() && a.observedTiles()>0,600);
+ context.waitTicks(3);
+ context.runOnClient(c -> {
+ Blocodex004ClientChecks.frame(c.gui.screen(),"history");
+ var widgets=c.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast).toList();
+ for (int i=0;i {c.getWindow().setWindowed(oldWidth,oldHeight);c.options.guiScale().set(oldScale);c.resizeGui();});
+ context.waitTicks(5);
+ Blocodex004ClientChecks.language(context,"fr_fr");
+ context.waitTicks(3);context.takeScreenshot("sanctuary-beta007-carte-fr");
+ Blocodex004ClientChecks.language(context,"en_us");
+ int personal=server.computeOnServer(s -> AtlasService.observations(s.getPlayerList().getPlayers().getFirst()).size());
+ server.runOnServer(s -> {((IntegratedServer)s).setWorldAllowCommands(true);require(AtlasService.canOperate(s.getPlayerList().getPlayers().getFirst()),"Allow commands grants actual operator inspection");});
+ context.runOnClient(c -> ((SanctuaryAtlasScreen)c.gui.screen()).recenter());
+ context.waitFor(c -> descendants(c.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast)
+ .anyMatch(b -> b.active && b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals("sanctuary.atlas.inhabitant")),400);
+ nativeClick(context,"sanctuary.atlas.inhabitant");
+ try {
+ context.waitFor(c -> c.gui.screen() instanceof SanctuaryAtlasScreen a && a.observedTiles()>personal && a.isComplete(),1600);
+ } catch (AssertionError error) {
+ context.takeScreenshot("sanctuary-beta007-operator-timeout");
+ context.runOnClient(c -> {if (!(c.gui.screen() instanceof SanctuaryAtlasScreen a)) {System.out.println("Unexpected screen: "+c.gui.screen());return;}System.out.println("Atlas timeout: status="+a.status()+" tiles="+a.observedTiles()+" complete="+a.isComplete()+" personal="+personal);});
+ server.runOnServer(s -> System.out.println("Atlas timeout server: loaded="+fr.koka.sanctuary.knowledge.census.LoadedChunks.full(s.overworld()).size()+" op="+AtlasService.canOperate(s.getPlayerList().getPlayers().getFirst())));
+ throw error;
+ }
+ context.waitTicks(3);context.takeScreenshot("sanctuary-beta007-map-operator");
+ server.runOnServer(s -> {
+ require(AtlasService.observations(s.getPlayerList().getPlayers().getFirst()).size()==personal,"Inspecting the loaded world does not add personal discoveries");
+ ((IntegratedServer)s).setWorldAllowCommands(false);
+ });
+ context.runOnClient(c -> ((SanctuaryAtlasScreen)c.gui.screen()).recenter());
+ context.waitFor(c -> c.gui.screen() instanceof SanctuaryAtlasScreen a && a.status().equals("denied") && a.observedTiles()==0,400);
+ nativeClick(context,"sanctuary.atlas.operator");
+ context.waitFor(c -> c.gui.screen() instanceof SanctuaryAtlasScreen a && a.observedTiles()>0 && a.isComplete(),600);
+ context.runOnClient(c -> require(((SanctuaryAtlasScreen)c.gui.screen()).observedTiles()<=personal,"Returning from operator mode discards operator terrain"));
+ nativeClick(context,"gui.done");context.waitForScreen(null);
+ context.getInput().pressKey(InputConstants.KEY_B);context.waitForScreen(ProgressionScreen.class);
+ context.runOnClient(c -> ProgressionClient.open(null,"progress"));
+ System.out.println("Atlas beta007 native client passed: locked/pre-recorded, purchase, zoom/pan, GUI sizes, explicit operator mode, revocation and isolation.");
+ }
+ private static Button button(net.minecraft.client.gui.screens.Screen screen,String key) {
+ return descendants(screen).filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals(key)).findFirst().orElseThrow();
+ }
+ private static void nativeClick(ClientGameTestContext context,String key) {
+ context.runOnClient(c -> {
+ var b=button(c.gui.screen(),key);require(b.active,"Native button is active: "+key);
+ move(c,b.getX()+b.getWidth()/2.,b.getY()+b.getHeight()/2.);press(c,true);press(c,false);
+ });
+ }
+ private static void press(net.minecraft.client.Minecraft c,boolean down) {
+ c.mouseHandler.onButton(c.getWindow().handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0),down ? InputConstants.PRESS : InputConstants.RELEASE);
+ }
+ private static void dragFrom(net.minecraft.client.Minecraft c,double x,double y,double dx,double dy) {
+ var map=(SanctuaryAtlasScreen)c.gui.screen();move(c,x,y);move(c,x,y);press(c,true);
+ double before=map.centerX();move(c,x+dx,y+dy);press(c,false);
+ require(Math.abs(map.centerX()-(before-dx/map.zoom()))<.01,"Map can be dragged in the dark screen margins");
+ }
+ private static void move(net.minecraft.client.Minecraft c,double x,double y) {
+ var window=c.getWindow();double sx=x*window.getScreenWidth()/window.getGuiScaledWidth(),sy=y*window.getScreenHeight()/window.getGuiScaledHeight();
+ c.mouseHandler.onMove(window.handle(),sx,sy,0,0);c.mouseHandler.handleAccumulatedMovement();
+ }
+ private static void drag(net.minecraft.client.Minecraft c,double dx,double dy,boolean releaseOutside) {
+ var map=(SanctuaryAtlasScreen)c.gui.screen();double x=map.width/2.,y=map.height/2.;
+ move(c,x,y);move(c,x,y);
+ var button=new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0);
+ c.mouseHandler.onButton(c.getWindow().handle(),button,InputConstants.PRESS);
+ double before=map.centerX();move(c,x+dx,y+dy);
+ require(Math.abs(map.centerX()-(before-dx/map.zoom()))<.01,"Native repeated drag tracks pointer displacement");
+ if (releaseOutside) move(c,1,1);
+ c.mouseHandler.onButton(c.getWindow().handle(),button,InputConstants.RELEASE);
+ double released=map.centerX();move(c,x,y);
+ require(Math.abs(map.centerX()-released)<.01,"Mouse release outside map ends the gesture");
+ }
+ private static void clickNested(ClientGameTestContext context,String key) {
+ context.runOnClient(c -> descendants(c.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.active && b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals(key))
+ .findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0)));
+ }
+ private static java.util.stream.Stream descendants(ContainerEventHandler container) {
+ return container.children().stream().flatMap(child -> java.util.stream.Stream.concat(java.util.stream.Stream.of(child),
+ child instanceof ContainerEventHandler nested ? descendants(nested) : java.util.stream.Stream.empty()));
+ }
+ private static void require(boolean value,String message) {if (!value) throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas013ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas013ClientChecks.java
new file mode 100644
index 0000000..0c35bfc
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas013ClientChecks.java
@@ -0,0 +1,84 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.atlas.*;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonEvent;
+import net.minecraft.client.input.MouseButtonInfo;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.network.protocol.game.ClientboundTrackedWaypointPacket;
+import net.minecraft.world.level.material.MapColor;
+import net.minecraft.world.waypoints.Waypoint;
+
+/** Stable rendered terrain across real refresh packets, and native shared locator sprites. */
+final class Atlas013ClientChecks {
+ static void run(ClientGameTestContext c,TestServerContext server) {
+ int[] sample=server.computeOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();p.setExperienceLevels(20);p.giveExperiencePoints(7);
+ require(ProgressionService.purchase(p,"atlas",ProgressEffects.get(p).revision()),"Unlock map after prestige");ProgressionService.send(p,"");
+ require(p.waypointIcon().color.orElseThrow()==(0xFF000000|ProgressionService.color(p)),"Server locator uses the inhabitant colour");
+ int x=p.chunkPosition().x()+4,z=p.chunkPosition().z();
+ byte[] colors=new byte[256];java.util.Arrays.fill(colors,MapColor.GOLD.getPackedId(MapColor.Brightness.NORMAL));
+ AtlasService.observations(p).observe(new AtlasStore.Tile(x,z,colors));return new int[]{x*16,z*16};
+ });
+ c.waitFor(m -> ProgressionClient.progress.atlas(),200);
+ c.runOnClient(m -> {m.gui.setScreen(new SanctuaryAtlasScreen(null));Inventory012RenderProbe.reset();});
+ c.waitFor(m -> m.gui.screen() instanceof SanctuaryAtlasScreen a && a.isComplete() && a.renderedPixelAt(sample[0],sample[1])!=0,600);
+ int pixel=c.computeOnClient(m -> ((SanctuaryAtlasScreen)m.gui.screen()).renderedPixelAt(sample[0],sample[1]));
+ var armed=new AtomicBoolean(true);var stable=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m -> {
+ if(armed.get() && m.gui.screen() instanceof SanctuaryAtlasScreen a && a.renderedPixelAt(sample[0],sample[1])!=pixel)stable.set(false);
+ });
+ double released=c.computeOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();double x=a.width/2.,y=a.height/2.;
+ var mouse=new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0);
+ require(a.mouseClicked(new MouseButtonEvent(x,y,mouse),false),"Map starts a drag");
+ require(a.mouseDragged(new MouseButtonEvent(x+40,y,mouse),40,0),"Map pans");
+ a.mouseReleased(new MouseButtonEvent(x+40,y,mouse));return a.centerX();
+ });
+ c.waitTicks(150);
+ c.runOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();
+ require(a.centerX()==released && stable.get(),"Drag release and periodic refresh never erase known terrain or recenter");
+ require(Inventory012RenderProbe.sprites.stream().anyMatch(v -> v.id().equals("minecraft:hud/locator_bar_dot/default_0")),"Map uses the native locator texture");
+ });
+ c.takeScreenshot("sanctuary-beta013-map-stable");armed.set(false);
+ c.runOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();
+ a.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals("sanctuary.atlas.inhabitant"))
+ .findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0));
+ });
+ c.waitFor(m -> ((SanctuaryAtlasScreen)m.gui.screen()).observedTiles()>0,600);
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();s.getPlayerList().deop(p.nameAndId());});
+ c.runOnClient(m -> ((SanctuaryAtlasScreen)m.gui.screen()).recenter());
+ c.waitFor(m -> ((SanctuaryAtlasScreen)m.gui.screen()).status().equals("denied"),600);
+ c.runOnClient(m -> {require(((SanctuaryAtlasScreen)m.gui.screen()).observedTiles()==0,"Permission revocation clears retained operator terrain");m.gui.screen().onClose();});
+ UUID marker=UUID.randomUUID();
+ server.runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();var icon=new Waypoint.Icon();icon.color=java.util.Optional.of(0xFF36CEAA);
+ p.connection.send(ClientboundTrackedWaypointPacket.addWaypointPosition(marker,icon,p.blockPosition().offset(0,0,20)));
+ });
+ c.waitFor(m -> m.player.connection.getWaypointManager().hasWaypoints(),200);
+ c.runOnClient(m -> {m.player.setYRot(0);m.player.setXRot(0);Inventory012RenderProbe.reset();});
+ c.waitTicks(150);c.takeScreenshot("sanctuary-beta013-locator-and-xp");
+ c.runOnClient(m -> {
+ var sprites=Inventory012RenderProbe.sprites;
+ var markerDraw=sprites.stream().filter(v -> v.id().startsWith("minecraft:hud/locator_bar_dot/") && v.color()==0xFF36CEAA).findFirst().orElseThrow();
+ require(sprites.stream().anyMatch(v -> v.id().equals("minecraft:hud/experience_bar_background")),"Experience background remains drawn beyond the native temporary XP priority");
+ require(sprites.stream().noneMatch(v -> v.id().equals("minecraft:hud/locator_bar_background")),"Locator never replaces the experience background");
+ var xp=sprites.stream().filter(v -> v.id().equals("minecraft:hud/experience_bar_background")).findFirst().orElseThrow();
+ require(markerDraw.y()==xp.y()-2 && markerDraw.y()+markerDraw.height()>xp.y()+xp.height(),"Player marker overlays the XP bar at its native position");
+ });
+ server.runOnServer(s -> s.getPlayerList().getPlayers().getFirst().connection.send(ClientboundTrackedWaypointPacket.removeWaypoint(marker)));
+ }
+ private static void require(boolean ok,String message) {if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas026ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas026ClientChecks.java
new file mode 100644
index 0000000..3e10706
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas026ClientChecks.java
@@ -0,0 +1,166 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.network.chat.Component;
+
+/** Real widgets, fonts and purchase packets, in a fresh ignored development world. */
+public final class Atlas026ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m -> {
+ if (armed.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ button(screen,"sanctuary.progression.enter").onPress(enter());armed.set(false);
+ }
+ });
+ int scale=c.computeOnClient(m -> m.options.guiScale().get());
+ String language=c.computeOnClient(m -> m.getLanguageManager().getSelected());
+ try (var world=c.worldBuilder().adjustSettings(s -> {
+ s.setName("Sanctuary beta026 atlas "+UUID.randomUUID());
+ s.setWorldType(s.getNormalPresetList().stream().filter(e -> e.preset()!=null && e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
+ }).create()) {
+ world.getConnection().waitForChunksDownload(2400);
+ c.waitFor(m -> m.player!=null && ProgressionClient.active() && CycleClient.view!=null && m.gui.screen()==null,1200);
+ world.getServer().runOnServer(server -> {
+ var player=server.getPlayerList().getPlayers().getFirst();player.setExperienceLevels(20);
+ require(ProgressionService.purchase(player,"atlas",ProgressEffects.get(player).revision()),"Buy atlas");
+ int cx=player.chunkPosition().x(),cz=player.chunkPosition().z();
+ for(int z=cz-8;z ProgressionClient.progress.atlas(),200);
+ c.runOnClient(m -> {m.getWindow().setWindowed(1280,800);m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new SanctuaryAtlasScreen(null));});
+ c.runOnClient(m -> button(m.gui.screen(),"sanctuary.atlas.demeure_off").onPress(enter()));
+ c.waitFor(m -> m.gui.screen() instanceof SanctuaryAtlasScreen a && a.isComplete() && a.footprintCount()>=256,1200);
+ c.runOnClient(m -> {var a=(SanctuaryAtlasScreen)m.gui.screen();for(int i=0;i<4;i++)a.mouseScrolled(a.width/2.,a.height/2.,0,-1);});
+ c.waitTicks(45);
+ c.runOnClient(m -> hold((SanctuaryAtlasScreen)m.gui.screen()));c.waitTicks(3);
+ measure(c,"stationary");
+ c.runOnClient(m -> {var a=(SanctuaryAtlasScreen)m.gui.screen();a.mouseDragged(mouse(a,10,0),10,0);});
+ measure(c,"pan-held");
+ c.takeScreenshot("sanctuary-beta026-demeure");
+ c.runOnClient(m -> {Atlas026RenderProbe.active=false;button(m.gui.screen(),"sanctuary.atlas.demeure_on").onPress(enter());});
+ measure(c,"disabled");
+ c.runOnClient(m -> button(m.gui.screen(),"sanctuary.atlas.demeure_off").onPress(enter()));
+ measure(c,"enabled-again");
+ if(!Boolean.getBoolean("sanctuary.test.atlasBaseline"))verifyUpdates(c,world.getServer());
+ System.out.println("ATLAS026 client checks passed");
+ } finally {
+ Atlas026RenderProbe.reset();armed.set(false);c.runOnClient(m -> {
+ m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
+ m.options.guiScale().set(scale);m.resizeGui();
+ });
+ }
+ }
+ private static void measure(ClientGameTestContext c,String phase) {
+ c.runOnClient(m -> {Atlas026RenderProbe.reset();Atlas026RenderProbe.active=true;});
+ c.waitFor(m -> Atlas026RenderProbe.nanos.size()>=30,1200);
+ c.runOnClient(m -> {
+ Atlas026RenderProbe.active=false;System.out.println(Atlas026RenderProbe.report(phase));
+ if(!Boolean.getBoolean("sanctuary.test.atlasBaseline")) {
+ require(Atlas026RenderProbe.blits==(phase.equals("disabled") ? 0 : Atlas026RenderProbe.nanos.size()),"One cached layer per visible frame: "+phase);
+ require(Atlas026RenderProbe.fills==0,"Overlay never submits per-block rectangles: "+phase);
+ require(Atlas026RenderProbe.uploads==0,"Stable data never reuploads map textures: "+phase);
+ }
+ });
+ }
+ private static net.minecraft.client.input.MouseButtonEvent mouse(SanctuaryAtlasScreen a,double dx,double dy) {
+ return new net.minecraft.client.input.MouseButtonEvent(a.width/2.+dx,a.height/2.+dy,new net.minecraft.client.input.MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0));
+ }
+ private static void hold(SanctuaryAtlasScreen a) {require(a.mouseClicked(mouse(a,0,0),false),"Hold map gesture");}
+ private record Fixture(int token,fr.koka.sanctuary.atlas.AtlasStore.Key key,byte[] colors,fr.koka.sanctuary.atlas.AtlasDemeurePayload.Footprint home) {}
+ private static void verifyUpdates(ClientGameTestContext c,net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext server) {
+ var sample=c.computeOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();
+ var key=new fr.koka.sanctuary.atlas.AtlasStore.Key(m.player.chunkPosition().x()+5,m.player.chunkPosition().z()+5);
+ var surveys=(java.util.Map)field(a,"surveyed");
+ var homes=(java.util.Map)field(a,"homes");
+ return new Fixture((int)field(a,"token"),key,surveys.get(key).clone(),homes.get(key));
+ });
+ c.runOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();int x=sample.key.x()*16,z=sample.key.z()*16;
+ require(pixel(a,x,z)==(0xA0000000|sample.home.color()),"Original border colour and opacity");
+ require(pixel(a,x+3,z+1)==(0x25000000|sample.home.color()),"Original interior opacity");
+ require(pixel(a,x+1,z+1)==0 && pixel(a,x+2,z+1)==0,"Unknown pixels and observed void have no overlay");
+ Atlas026RenderProbe.reset();Atlas026RenderProbe.active=true;
+ });
+ send(server,sample,java.util.List.of(sample.home));c.waitTicks(5);
+ c.runOnClient(m -> {require(Atlas026RenderProbe.uploads==0,"Identical network batches do not upload either texture");Atlas026RenderProbe.active=false;});
+ var changed=new fr.koka.sanctuary.atlas.AtlasDemeurePayload.Footprint(sample.key.x(),sample.key.z(),"dominated","player","Updated",99,0x36CEAA);
+ send(server,sample,java.util.List.of(changed));
+ c.waitFor(m -> pixel((SanctuaryAtlasScreen)m.gui.screen(),sample.key.x()*16,sample.key.z()*16)==0xA036CEAA,200);
+ c.runOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();require(pixel(a,sample.key.x()*16+1,sample.key.z()*16+1)==0,"Colour changes never reveal the fog");
+ button(a,"sanctuary.atlas.demeure_on").onPress(enter());Atlas026RenderProbe.reset();Atlas026RenderProbe.active=true;
+ });
+ send(server,sample,java.util.List.of(sample.home));c.waitTicks(5);
+ c.runOnClient(m -> {require(Atlas026RenderProbe.uploads==0,"Hidden changes wait until the layer is enabled");Atlas026RenderProbe.active=false;button(m.gui.screen(),"sanctuary.atlas.demeure_off").onPress(enter());});
+ c.waitFor(m -> pixel((SanctuaryAtlasScreen)m.gui.screen(),sample.key.x()*16,sample.key.z()*16)==(0xA0000000|sample.home.color()),200);
+ byte[] revealed=sample.colors.clone();revealed[17]=sample.colors[0];
+ send(server,new Fixture(sample.token,sample.key,revealed,sample.home),java.util.List.of(sample.home));
+ c.waitFor(m -> pixel((SanctuaryAtlasScreen)m.gui.screen(),sample.key.x()*16+1,sample.key.z()*16+1)!=0,200);
+ send(server,sample,java.util.List.of());
+ c.waitFor(m -> pixel((SanctuaryAtlasScreen)m.gui.screen(),sample.key.x()*16,sample.key.z()*16)==0,200);
+ c.runOnClient(m -> require(pixel((SanctuaryAtlasScreen)m.gui.screen(),sample.key.x()*16+1,sample.key.z()*16+1)==0,"Removed footprint and restored fog clear stale pixels"));
+ // Reprojection and real refresh restore authoritative data from the same saved observations.
+ c.runOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();a.mouseReleased(mouse(a,0,0));a.recenter();
+ m.getWindow().setWindowed(640,480);m.options.guiScale().set(0);m.resizeGui();
+ });c.waitTicks(45);
+ c.waitFor(m -> ((SanctuaryAtlasScreen)m.gui.screen()).isComplete() && pixel((SanctuaryAtlasScreen)m.gui.screen(),sample.key.x()*16,sample.key.z()*16)==(0xA0000000|sample.home.color()),600);
+ c.takeScreenshot("sanctuary-beta026-demeure-compact");
+ // Removing the map aptitude uses the actual server permission check on the next request.
+ server.runOnServer(s -> {var p=s.getPlayerList().getPlayers().getFirst();p.setAttached(ProgressionService.RECORD,ProgressRecord.INITIAL.json());ProgressEffects.apply(p,ProgressRecord.INITIAL);ProgressionService.send(p,"");});
+ c.runOnClient(m -> ((SanctuaryAtlasScreen)m.gui.screen()).recenter());
+ c.waitFor(m -> ((SanctuaryAtlasScreen)m.gui.screen()).status().equals("locked"),600);
+ c.runOnClient(m -> {
+ var a=(SanctuaryAtlasScreen)m.gui.screen();require(a.footprintCount()==0 && pixel(a,sample.key.x()*16,sample.key.z()*16)==0,"Revocation clears both map layers");
+ var image=((net.minecraft.client.renderer.texture.DynamicTexture)field(a,"footprintTexture")).getPixels();
+ a.onClose();require(image.isClosed(),"Closing releases the overlay image");
+ });
+ System.out.println("ATLAS026 updates passed: identical batches, colour, fog, removal, hidden updates, reprojection, resize, revocation, disposal");
+ }
+ private static void send(net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext server,Fixture f,java.util.List homes) {
+ server.runOnServer(s -> {
+ var player=s.getPlayerList().getPlayers().getFirst();
+ net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player,new fr.koka.sanctuary.atlas.AtlasPayloads.Response(f.token,false,true,false,false,"ready",java.util.List.of(new fr.koka.sanctuary.atlas.AtlasStore.Tile(f.key.x(),f.key.z(),f.colors))));
+ net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking.send(player,new fr.koka.sanctuary.atlas.AtlasDemeurePayload(f.token,homes));
+ });
+ }
+ private static Object field(Object target,String name) {
+ try {var f=target.getClass().getDeclaredField(name);f.setAccessible(true);return f.get(target);}catch(ReflectiveOperationException e){throw new AssertionError(e);}
+ }
+ private static int pixel(SanctuaryAtlasScreen a,int x,int z) {
+ int localX=x-(int)field(a,"originX"),localZ=z-(int)field(a,"originZ");
+ return ((net.minecraft.client.renderer.texture.DynamicTexture)field(a,"footprintTexture")).getPixels().getPixel(localX,localZ);
+ }
+ private static Stream widgets(GuiEventListener widget) {
+ return Stream.concat(Stream.of(widget),widget instanceof ContainerEventHandler parent
+ ? parent.children().stream().flatMap(Atlas026ClientChecks::widgets) : Stream.empty());
+ }
+ private static boolean key(AbstractWidget widget,String key) {return widget.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals(key);}
+ private static Button button(Screen screen,String key) {return widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b -> key(b,key)).findFirst().orElseThrow();}
+ private static KeyEvent enter() {return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean ok,String message) {if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas058ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas058ClientChecks.java
new file mode 100644
index 0000000..0093a5f
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas058ClientChecks.java
@@ -0,0 +1,115 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.atlas.*;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.progression.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.storage.LevelResource;
+
+/** Real 640-high world, sampling, packets, cached rendering and native controls. */
+public final class Atlas058ClientChecks implements FabricClientGameTest {
+ private static final net.minecraft.world.level.block.Block[] BLOCKS={Blocks.STONE,Blocks.DIAMOND_BLOCK,Blocks.GOLD_BLOCK,Blocks.LAPIS_BLOCK,Blocks.EMERALD_BLOCK,Blocks.REDSTONE_BLOCK};
+ public void runTest(ClientGameTestContext c) {
+ var intro=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen s){
+ Starter039TestSupport.selectFirst(s);
+ s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());intro.set(false);
+ }});
+ int scale=c.computeOnClient(m->m.options.guiScale().get());String language=c.computeOnClient(m->m.getLanguageManager().getSelected());
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Atlas058 "+UUID.randomUUID());s.setSeed("58");s.setAllowCommands(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());}).create()) {
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ var server=world.getServer();
+ int[] fixture=server.computeOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;
+ var level=p.level();check(level.getMaxY()==639,"Real Sanctuary dimension reaches 640 blocks");
+ int x=p.chunkPosition().x()*16+4,z=p.chunkPosition().z()*16+4;
+ for(int dx=0;dx<4;dx++)for(int dz=0;dz<4;dz++) {
+ for(int y=level.getMinY();y<=level.getMaxY();y++)level.setBlock(new BlockPos(x+dx,y,z+dz),Blocks.AIR.defaultBlockState(),2);
+ if(dx==3&&dz==3)continue;
+ for(int i=0;i<6;i++)level.setBlock(new BlockPos(x+dx,Math.min(639,AtlasAltitude.LEVELS.get(i)),z+dz),BLOCKS[i].defaultBlockState(),2);
+ }
+ int cx=Math.floorDiv(x,16),cz=Math.floorDiv(z,16),pixel=Math.floorMod(z,16)*16+Math.floorMod(x,16);
+ var all=AtlasSampler.sampleLayers(level,cx,cz);check(all.size()==6,"Six layers in one scan");
+ for(int i=0;i<6;i++) {
+ int altitude=AtlasAltitude.LEVELS.get(i);var tile=all.get(altitude);
+ int color=Byte.toUnsignedInt(tile.colors()[pixel])/4;
+ check(color==BLOCKS[i].defaultMapColor().id,"Topmost block below inclusive ceiling "+altitude);
+ check(Arrays.equals(tile.colors(),AtlasSampler.sample(level,cx,cz,altitude).colors()),"Single-layer and shared descent agree "+altitude);
+ check(tile.colors()[pixel+51]==1,"Empty column stays transparent void "+altitude);
+ AtlasService.observations(p,altitude).observe(AtlasFog.reveal(tile,p.getX(),p.getZ()));
+ }
+ check(AtlasSampler.sample(level,1000000,1000000,280)==null&&AtlasSampler.sampleLayers(level,1000000,1000000).isEmpty()&&level.getChunkSource().getChunkNow(1000000,1000000)==null,"Sampling never loads unknown distant chunks");
+ // A transparent block at the cut must not hide the colour just underneath.
+ level.setBlock(new BlockPos(x,140,z),Blocks.GLASS.defaultBlockState(),2);level.setBlock(new BlockPos(x,139,z),Blocks.DIAMOND_BLOCK.defaultBlockState(),2);
+ check(Byte.toUnsignedInt(AtlasSampler.sample(level,cx,cz,140).colors()[pixel])/4==Blocks.DIAMOND_BLOCK.defaultMapColor().id,"Transparent cut reveals support");
+ p.setExperienceLevels(20);check(ProgressionService.purchase(p,"atlas",ProgressEffects.get(p).revision()),"Atlas purchased after recording lower layers");
+ s.getWorldData().setAllowCommands(false);ProgressionService.send(p,"");
+ return new int[]{x,z,cx,cz};
+ });
+ c.waitFor(m->ProgressionClient.progress.atlas()&&!ProgressionClient.operator,200);
+ c.runOnClient(m->{m.getWindow().setWindowed(1280,800);m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new SanctuaryAtlasScreen(null));});
+ waitLayer(c,640,fixture);
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();check(!(boolean)field(a,"grid")&&!(boolean)field(a,"footprints"),"Chunk grid and Demeure off by default");
+ for(String key:List.of("sanctuary.atlas.grid_off","sanctuary.atlas.demeure_off"))a.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow().onPress(enter());
+ check((boolean)field(a,"grid")&&(boolean)field(a,"footprints"),"Optional overlays can be enabled");
+ for(String key:List.of("sanctuary.atlas.grid_on","sanctuary.atlas.demeure_on"))a.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow().onPress(enter());
+ });
+ double[] position=c.computeOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();a.mouseScrolled(a.width/2.,a.height/2.,0,1);var info=new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0);a.mouseClicked(new MouseButtonEvent(a.width/2.,a.height/2.,info),false);a.mouseDragged(new MouseButtonEvent(a.width/2.+28,a.height/2.,info),28,0);a.mouseReleased(new MouseButtonEvent(a.width/2.+28,a.height/2.,info));return new double[]{a.centerX(),a.zoom()};});
+ c.waitTicks(25);
+ for(int i=5;i>=0;i--) {
+ int ceiling=AtlasAltitude.LEVELS.get(i);
+ if(i<5)c.runOnClient(m->click((SanctuaryAtlasScreen)m.gui.screen(),"↓"));
+ waitLayer(c,ceiling,fixture);
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();check(a.centerX()==position[0]&&a.zoom()==position[1],"Layer change preserves pan and zoom");check(button(a,"↑").active==(ceiling<640)&&button(a,"↓").active==(ceiling>0),"Buttons clamp both ends");});
+ if(i==5||i==2||i==0)c.takeScreenshot("sanctuary-beta058-altitude-"+ceiling);
+ }
+ c.runOnClient(m->{
+ var a=(SanctuaryAtlasScreen)m.gui.screen();int oldToken=(int)field(a,"token");
+ for(int i=0;i<5;i++)click(a,"↑");for(int i=0;i<2;i++)click(a,"↓");
+ check(a.altitude()==420,"Rapid clicks end at requested layer");
+ byte[] bad=new byte[256];Arrays.fill(bad,(byte)100);
+ a.accept(new AtlasPayloads.Response(oldToken,false,true,true,false,"ready",List.of(new AtlasStore.Tile(fixture[2],fixture[3],bad))));
+ a.accept(new AtlasDemeurePayload(oldToken,List.of(new AtlasDemeurePayload.Footprint(fixture[2],fixture[3],"inhabited","nature","",50,0xFF00FF))));
+ check(a.observedTiles()==0&&a.footprintCount()==0,"Old terrain and Demeure packets are ignored after layer switch");
+ });waitLayer(c,420,fixture);
+ int pixel=c.computeOnClient(m->((SanctuaryAtlasScreen)m.gui.screen()).renderedPixelAt(fixture[0],fixture[1]));
+ c.waitTicks(130);c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();check(a.renderedPixelAt(fixture[0],fixture[1])==pixel&&a.centerX()==position[0],"Periodic refresh keeps displayed terrain and position");});
+ // FR/EN and minimum GUI scale retain clickable controls without footer overlap.
+ for(String lang:List.of("fr_fr","en_us"))c.runOnClient(m->{m.getLanguageManager().setSelected(lang);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(4);m.resizeGui();var a=(SanctuaryAtlasScreen)m.gui.screen();check(button(a,"↓").getBottom(){var p=s.getPlayerList().getPlayers().getFirst();var root=s.getWorldPath(LevelResource.ROOT).resolve("data/sanctuary-atlas");try{
+ for(int altitude:AtlasAltitude.LEVELS){var survey=AtlasService.observations(p,altitude);survey.save();var file=altitude==640?root.resolve(p.getUUID()+".json"):root.resolve("layers/"+p.getUUID()+"/"+altitude+".json");var bytes=Files.readAllBytes(file);var read=new AtlasStore(file,p.getUUID(),s.overworld().getSeed());check(Arrays.equals(read.get(fixture[2],fixture[3]).colors(),survey.get(fixture[2],fixture[3]).colors()),"Layer survives disk reload "+altitude);read.save();check(Arrays.equals(bytes,Files.readAllBytes(file)),"Layer reload is idempotent");}
+ check(!AtlasService.canRead(p,true)&&AtlasService.canRead(p,false),"Normal mode cannot inspect operator chunks");
+ }catch(Exception e){throw new RuntimeException(e);}});
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();int next=AtlasClient.nextToken();setField(a,"token",next);AtlasClient.send(new AtlasPayloads.Request(next,fixture[2],fixture[3],true,420));});
+ c.waitFor(m->((SanctuaryAtlasScreen)m.gui.screen()).status().equals("denied"),200);
+ c.runOnClient(m->{check(((SanctuaryAtlasScreen)m.gui.screen()).observedTiles()==0,"Denied operator response clears all pixels");m.gui.screen().onClose();});
+ protocol();System.out.println("ATLAS058_PASS: ceilings, actual colours, fog, persistence, packets, controls, optional overlays, pan, permissions, FR/EN");
+ } finally {intro.set(false);c.runOnClient(m->{m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();});}
+ }
+ private static void waitLayer(ClientGameTestContext c,int altitude,int[] fixture) {
+ c.waitFor(m->m.gui.screen() instanceof SanctuaryAtlasScreen a&&a.altitude()==altitude&&a.isComplete()&&a.renderedPixelAt(fixture[0],fixture[1])!=0,800);
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();int index=AtlasAltitude.LEVELS.indexOf(altitude);int color=a.renderedPixelAt(fixture[0],fixture[1]);boolean match=false;for(var shade:net.minecraft.world.level.material.MapColor.Brightness.values())match|=color==net.minecraft.world.level.material.MapColor.getColorFromPackedId(BLOCKS[index].defaultMapColor().getPackedId(shade));check(match,"Actual rendered pixel belongs to layer "+altitude);check(a.renderedPixelAt(fixture[0]+3,fixture[1]+3)==0,"Void pixels remain transparent");check(a.renderedPixelAt(fixture[0]+250,fixture[1]+250)==0,"Unknown territory remains hidden");});
+ }
+ private static void protocol(){for(int altitude:AtlasAltitude.LEVELS){var b=new net.minecraft.network.RegistryFriendlyByteBuf(io.netty.buffer.Unpooled.buffer(),net.minecraft.core.RegistryAccess.EMPTY);try{var r=new AtlasPayloads.Request(4,-2,3,false,altitude);AtlasPayloads.Request.CODEC.encode(b,r);check(AtlasPayloads.Request.CODEC.decode(b).equals(r),"Altitude round trip");}finally{b.release();}}for(int invalid:new int[]{-1,1,139,141,641,Integer.MAX_VALUE}){boolean failed=false;try{new AtlasPayloads.Request(1,0,0,false,invalid);}catch(IllegalArgumentException expected){failed=true;}check(failed,"Reject invalid altitude");}check(AtlasAltitude.step(640,1)==640&&AtlasAltitude.step(0,-1)==0&&AtlasAltitude.step(560,1)==640,"Bounded final 80-block step");}
+ private static Button button(SanctuaryAtlasScreen a,String text){return a.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getString().equals(text)).findFirst().orElseThrow();}
+ private static void click(SanctuaryAtlasScreen a,String text){var b=button(a,text);check(b.active,"Enabled altitude control");a.mouseClicked(new MouseButtonEvent(b.getX()+5,b.getY()+5,new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0)),false);a.mouseReleased(new MouseButtonEvent(b.getX()+5,b.getY()+5,new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0)));}
+ private static Object field(Object o,String name){try{var f=o.getClass().getDeclaredField(name);f.setAccessible(true);return f.get(o);}catch(Exception e){throw new RuntimeException(e);}}
+ private static void setField(Object o,String name,Object value){try{var f=o.getClass().getDeclaredField(name);f.setAccessible(true);f.set(o,value);}catch(Exception e){throw new RuntimeException(e);}}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas059ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas059ClientChecks.java
new file mode 100644
index 0000000..541f3f5
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Atlas059ClientChecks.java
@@ -0,0 +1,105 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.atlas.*;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.progression.*;
+import fr.koka.sanctuary.worldgen.aerial.*;
+import java.nio.file.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.*;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.core.*;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.resources.*;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.level.saveddata.maps.*;
+import net.minecraft.world.level.storage.LevelResource;
+import net.minecraft.world.level.storage.loot.*;
+import net.minecraft.world.level.storage.loot.parameters.*;
+import net.minecraft.world.phys.*;
+
+/** Native banner interactions, actual loot-generated maps, packets and no operator knowledge leak. */
+public final class Atlas059ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var intro=new AtomicBoolean(true);ClientTickEvents.END_CLIENT_TICK.register(m->{
+ if(m.gui.screen() instanceof IntroScreen s)s.onClose();
+ if(intro.get()&&m.gui.screen() instanceof HelloWorldScreen s){Starter039TestSupport.selectFirst(s);s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());intro.set(false);}
+ });
+ int scale=c.computeOnClient(m->m.options.guiScale().get());String lang=c.computeOnClient(m->m.getLanguageManager().getSelected());
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Atlas059 "+UUID.randomUUID());s.setSeed("42");s.setAllowCommands(true);s.setGenerateStructures(true);s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());}).create()) {
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);var server=world.getServer();
+ var banner=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;p.setExperienceLevels(20);check(ProgressionService.purchase(p,"atlas",ProgressEffects.get(p).revision()),"Unlock atlas");p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);p.inventoryMenu.broadcastChanges();
+ var pos=p.blockPosition().offset(1,0,1);p.level().setBlock(pos.below(),Blocks.STONE.defaultBlockState(),2);p.level().setBlock(pos,Blocks.BANNER.red().defaultBlockState(),2);rename(p.level(),pos,"Port de Zoé");
+ var sites=AtlasMarkers.treasureSites(p.level());check(sites.size()==2,"Existing medium-world plan contains both mining treasures; structures="+p.level().structureManager().shouldGenerateStructures()+" generator="+p.level().getChunkSource().getGenerator());
+ check(AtlasMarkers.discoveries(p).isEmpty(),"No treasure known at arrival");s.getWorldData().setAllowCommands(false);ProgressionService.send(p,"");return pos;
+ });
+ c.waitFor(m->!ProgressionClient.operator&&m.player.getMainHandItem().isEmpty()&&m.level.getBlockState(banner).is(Blocks.BANNER.red()),200);
+ useBanner(c,banner);c.waitTicks(12);server.runOnServer(s->check(AtlasMarkers.discoveries(s.getPlayerList().getPlayers().getFirst()).stream().anyMatch(m->m.name().equals("Port de Zoé")),"Real right-click records named red banner"));
+ useBanner(c,banner);c.waitTicks(12);server.runOnServer(s->check(AtlasMarkers.discoveries(s.getPlayerList().getPlayers().getFirst()).isEmpty(),"Second right-click removes native-style marker"));
+ useBanner(c,banner);c.waitTicks(12);
+ c.runOnClient(m->{m.getWindow().setWindowed(1280,800);m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new SanctuaryAtlasScreen(null));});
+ c.waitFor(m->m.gui.screen() instanceof SanctuaryAtlasScreen a&&a.isComplete()&&a.markers().size()==1,600);
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();check(a.markers().getFirst().kind().equals("banner"),"Normal atlas receives no undiscovered treasure coordinates");spriteCheck(m,a);});
+ c.takeScreenshot("sanctuary-beta059-named-banner");
+ server.runOnServer(s->rename(s.overworld(),banner,"Quai des îles"));c.runOnClient(m->((SanctuaryAtlasScreen)m.gui.screen()).recenter());
+ c.waitFor(m->((SanctuaryAtlasScreen)m.gui.screen()).markers().stream().anyMatch(v->v.name().equals("Quai des îles")),300);
+ c.runOnClient(m->m.gui.screen().onClose());
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var map=MapItem.create(p.level(),banner.getX(),banner.getZ(),(byte)1,true,true);MapItemSavedData.addTargetDecoration(map,banner.offset(80,0,80),"mansion",MapDecorationTypes.WOODLAND_MANSION);p.setItemInHand(InteractionHand.OFF_HAND,map);p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->m.player.getOffhandItem().has(DataComponents.MAP_ID),200);
+ c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.OFF_HAND,new BlockHitResult(Vec3.atCenterOf(banner),Direction.UP,banner,false)));c.waitTicks(12);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(MapItem.getSavedData(p.getOffhandItem(),p.level()).getBanners().size()==1,"Offhand physical map keeps native banner recording");});
+ c.runOnClient(m->m.gameMode.useItem(m.player,InteractionHand.OFF_HAND));c.waitTicks(12);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(AtlasMarkers.discoveries(p).stream().anyMatch(v->v.kind().equals("point")&&v.y()==null),"Native exploration point imported without invented altitude");check(AtlasMarkers.discoveries(p).stream().anyMatch(v->v.name().equals("Quai des îles")),"Named banner imported from the native physical map");});
+ // Generate actual loot from both unmodified site identities; no synthetic clue grants.
+ var target=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var sites=AtlasMarkers.treasureSites(p.level());var charts=new ArrayList();
+ for(int i=0;i<2;i++) {
+ String table=i==0?"merchant_provisions":"merchant_tools";var loot=loot(p.level(),table,p.position());
+ check(loot.stream().anyMatch(v->v.is(iItem(table))),"Original merchant loot remains");if(i==0)check(loot.stream().filter(v->v.is(Items.BREAD)).mapToInt(ItemStack::getCount).sum()>=24,"Declared provision quantities apply in the native modifier format");
+ var map=loot.stream().filter(v->v.has(DataComponents.MAP_ID)).findFirst().orElseThrow();var expected=AtlasMarkers.treasure(sites.get(i));
+ check(map.get(DataComponents.MAP_DECORATIONS).decorations().values().stream().anyMatch(d->d.x()==expected.x()&&d.z()==expected.z()&&d.type().equals(MapDecorationTypes.RED_X)),"Loot map targets the saved island chest "+i);charts.add(map);
+ }
+ var mineral=loot(p.level(),"mineral_islet",Vec3.atCenterOf(AerialGeometry24.chest(sites.getFirst())));var next=mineral.stream().filter(v->v.has(DataComponents.MAP_ID)).findFirst().orElseThrow();var nextPoint=AtlasMarkers.treasure(sites.get(1));check(next.get(DataComponents.MAP_DECORATIONS).decorations().values().stream().anyMatch(d->d.x()==nextPoint.x()&&d.z()==nextPoint.z()),"Mining chest leads to the next island");
+ p.setItemInHand(InteractionHand.MAIN_HAND,charts.getFirst());p.inventoryMenu.broadcastChanges();return AtlasMarkers.treasure(sites.getFirst());
+ });
+ c.waitFor(m->m.player.getMainHandItem().has(DataComponents.MAP_ID),200);
+ c.runOnClient(m->m.gameMode.useItem(m.player,InteractionHand.MAIN_HAND));c.waitTicks(15);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(AtlasMarkers.discoveries(p).stream().filter(m->m.kind().equals("treasure")).count()==1,"Using physical map unlocks exactly its own treasure");check(p.getMainHandItem().has(DataComponents.MAP_ID)&&p.getMainHandItem().getCount()==1,"Chart remains shareable");check(AtlasMarkers.importMap(p,p.getMainHandItem())==0,"Repeated use does not duplicate markers");});
+ c.runOnClient(m->m.gui.setScreen(new SanctuaryAtlasScreen(null)));c.waitFor(m->((SanctuaryAtlasScreen)m.gui.screen()).markers().stream().anyMatch(v->v.id().equals(target.id())),600);
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();setField(a,"centerX",(double)target.x());setField(a,"centerZ",(double)target.z());spriteCheck(m,a);});c.takeScreenshot("sanctuary-beta059-treasure-chart");
+ // The full operator list never changes normal discoveries, and revocation clears it.
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();s.getWorldData().setAllowCommands(true);ProgressionService.send(p,"");});
+ c.waitFor(m->((SanctuaryAtlasScreen)m.gui.screen()).markers().stream().filter(v->v.kind().equals("treasure")).count()==2,600);
+ c.takeScreenshot("sanctuary-beta059-operator-treasures");int opToken=c.computeOnClient(m->(int)field(m.gui.screen(),"token"));
+ var opMarkers=c.computeOnClient(m->((SanctuaryAtlasScreen)m.gui.screen()).markers());
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();check(AtlasMarkers.discoveries(p).stream().filter(v->v.kind().equals("treasure")).count()==1,"Operator inspection never grants knowledge");s.getWorldData().setAllowCommands(false);ProgressionService.send(p,"");});
+ c.waitFor(m->!ProgressionClient.operator&&((SanctuaryAtlasScreen)m.gui.screen()).isComplete()&&((SanctuaryAtlasScreen)m.gui.screen()).markers().stream().filter(v->v.kind().equals("treasure")).count()==1,600);
+ c.runOnClient(m->{var a=(SanctuaryAtlasScreen)m.gui.screen();a.accept(new AtlasMarkerPayload(opToken,opMarkers));check(a.markers().stream().filter(v->v.kind().equals("treasure")).count()==1,"Delayed operator payload cannot restore hidden treasure");});
+ server.runOnServer(s->s.overworld().setBlock(banner,Blocks.AIR.defaultBlockState(),2));c.runOnClient(m->((SanctuaryAtlasScreen)m.gui.screen()).recenter());
+ c.waitFor(m->((SanctuaryAtlasScreen)m.gui.screen()).markers().stream().noneMatch(v->v.kind().equals("banner")),300);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();try{var file=s.getWorldPath(LevelResource.ROOT).resolve("data/sanctuary-atlas/markers/"+p.getUUID()+".json");var read=new AtlasMarkerStore(file,p.getUUID(),s.overworld().getSeed());check(read.all().equals(AtlasMarkers.discoveries(p)),"Discovery and banner deletion already survive disk reload");}catch(Exception e){throw new RuntimeException(e);}});
+ for(String language:List.of("fr_fr","en_us"))c.runOnClient(m->{m.getLanguageManager().setSelected(language);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());check(!AtlasMarkers.label("treasure_chart").getString().contains("sanctuary."),"Localized chart and markers");});
+ protocol(target);System.out.println("ATLAS059_PASS: native banners, treasure loot/maps, physical use, server filtering, operator revocation, native sprites and persistence");
+ }finally{intro.set(false);c.runOnClient(m->{m.getLanguageManager().setSelected(lang);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.options.guiScale().set(scale);m.resizeGui();});}
+ }
+ private static Item iItem(String table){return table.equals("merchant_tools")?Items.IRON_PICKAXE:Items.BREAD;}
+ private static List loot(net.minecraft.server.level.ServerLevel level,String table,Vec3 origin){var key=ResourceKey.create(Registries.LOOT_TABLE,Identifier.fromNamespaceAndPath("sanctuary","chests/aerial24/"+table));return level.getServer().reloadableRegistries().getLootTable(key).getRandomItems(new LootParams.Builder(level).withParameter(LootContextParams.ORIGIN,origin).create(LootContextParamSets.CHEST),59L);}
+ private static void rename(net.minecraft.server.level.ServerLevel level,BlockPos pos,String name){var item=new ItemStack(Items.BANNER.red());item.set(DataComponents.CUSTOM_NAME,Component.literal(name));var be=level.getBlockEntity(pos);be.applyComponentsFromItemStack(item);be.setChanged();level.sendBlockUpdated(pos,level.getBlockState(pos),level.getBlockState(pos),3);}
+ private static void useBanner(ClientGameTestContext c,BlockPos pos){c.runOnClient(m->m.gameMode.useItemOn(m.player,InteractionHand.MAIN_HAND,new BlockHitResult(Vec3.atCenterOf(pos),Direction.UP,pos,false)));}
+ private static void spriteCheck(net.minecraft.client.Minecraft m,SanctuaryAtlasScreen a){for(var marker:a.markers()){var id=Identifier.parse(marker.sprite());check(m.getAtlasManager().getAtlasOrThrow(net.minecraft.data.AtlasIds.MAP_DECORATIONS).getSprite(id).contents().name().equals(id),"Real native decoration sprite exists: "+id);}}
+ private static void protocol(AtlasMarker target){var b=new net.minecraft.network.RegistryFriendlyByteBuf(io.netty.buffer.Unpooled.buffer(),RegistryAccess.EMPTY);try{var p=new AtlasMarkerPayload(17,List.of(target,new AtlasMarker("point",0,null,0,"point","minecraft:mansion","")));AtlasMarkerPayload.CODEC.encode(b,p);check(AtlasMarkerPayload.CODEC.decode(b).equals(p),"Marker codec preserves unknown Y and identities");}finally{b.release();}}
+ private static Object field(Object o,String name){try{var f=o.getClass().getDeclaredField(name);f.setAccessible(true);return f.get(o);}catch(Exception e){throw new RuntimeException(e);}}
+ private static void setField(Object o,String name,Object v){try{var f=o.getClass().getDeclaredField(name);f.setAccessible(true);f.set(o,v);}catch(Exception e){throw new RuntimeException(e);}}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void check(boolean ok,String message){if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Blocodex004ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Blocodex004ClientChecks.java
new file mode 100644
index 0000000..fbe18d5
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Blocodex004ClientChecks.java
@@ -0,0 +1,78 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.ProgressionScreen;
+import fr.koka.sanctuary.client.SanctuaryBlocodexScreen;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.components.EditBox;
+import net.minecraft.client.gui.components.ObjectSelectionList;
+import net.minecraft.client.gui.components.StringWidget;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+
+/** Exercises real GUI resizes and the live discovery catalogue inside the progression test world. */
+final class Blocodex004ClientChecks {
+ static void run(ClientGameTestContext context) {
+ int originalWidth=context.computeOnClient(c -> c.getWindow().getScreenWidth());
+ int originalHeight=context.computeOnClient(c -> c.getWindow().getScreenHeight());
+ int originalScale=context.computeOnClient(c -> c.options.guiScale().get());
+ try {
+ for(int[] size:new int[][]{{854,480},{960,720}}){
+ context.runOnClient(c -> c.getWindow().setWindowed(size[0],size[1]));context.waitTicks(3);
+ for(int scale:new int[]{1,2,3,0}){
+ context.runOnClient(c -> {c.options.guiScale().set(scale);c.resizeGui();});
+ for(String page:new String[]{"blocks","progress","history","identity"}){
+ context.runOnClient(c -> fr.koka.sanctuary.client.ProgressionClient.open(null,page));context.waitTick();
+ context.runOnClient(c -> frame(c.gui.screen(),page));
+ }
+ }
+ }
+ context.runOnClient(c -> fr.koka.sanctuary.client.ProgressionClient.open(null,"progress"));
+ System.out.println("Native compact page headers and footers fit both window sizes and four GUI scales.");
+ }finally{
+ context.runOnClient(c -> {c.getWindow().setWindowed(originalWidth,originalHeight);c.options.guiScale().set(originalScale);c.resizeGui();});context.waitTicks(3);
+ }
+ }
+ static void language(ClientGameTestContext context,String language) {
+ if (context.computeOnClient(client -> client.getLanguageManager().getSelected().equals(language))) return;
+ var reload=context.computeOnClient(client -> {
+ client.getLanguageManager().setSelected(language);
+ return client.reloadResourcePacks();
+ });
+ context.waitFor(client -> reload.isDone(),600);
+ reload.join();
+ context.waitFor(client -> client.gui.overlay()==null,600);
+ context.runOnClient(client -> client.resizeGui());
+ context.waitTick();
+ }
+ static void frame(Screen screen,String tab) {
+ Button done=button(screen,"gui.done");
+ require(done.getBottom()<=screen.height-5 && Math.abs(done.getX()+done.getWidth()/2-screen.width/2)<=1,"Native Done footer fits and stays centered");
+ var title=screen.children().stream().filter(StringWidget.class::isInstance).map(StringWidget.class::cast)
+ .filter(w -> key(w).equals("sanctuary.progression."+tab)).findFirst().orElseThrow();
+ require(title.getY()>=0 && title.getBottom()<=24,"Compact page title fits its header");
+ require(screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .noneMatch(b -> List.of("blocks","progress","history","identity").stream().anyMatch(k -> key(b).equals("sanctuary.progression."+k))),"Pages do not duplicate pause navigation");
+ }
+ private static String key(AbstractWidget widget) {
+ return widget.getMessage().getContents() instanceof TranslatableContents t ? t.getKey() : "";
+ }
+ private static Button button(Screen screen,String key) {
+ return screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> key(b).equals(key)).findFirst().orElseThrow();
+ }
+ private static ObjectSelectionList> list(Screen screen) {
+ return screen.children().stream().filter(ObjectSelectionList.class::isInstance).map(ObjectSelectionList.class::cast)
+ .findFirst().orElseThrow();
+ }
+ private static EditBox search(Screen screen) {
+ return screen.children().stream().filter(EditBox.class::isInstance).map(EditBox.class::cast).findFirst().orElseThrow();
+ }
+ private static void require(boolean condition,String message) { if (!condition) throw new AssertionError(message); }
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Blueprint036ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Blueprint036ClientChecks.java
new file mode 100644
index 0000000..eb1b726
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Blueprint036ClientChecks.java
@@ -0,0 +1,55 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.recipes.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import mezz.jei.api.constants.*;
+import mezz.jei.api.recipe.RecipeIngredientRole;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.InventoryScreen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.resources.Identifier;
+import net.minecraft.world.item.*;
+
+/** End-to-end catalogue lookup, not just presence in the server recipe set. */
+public final class Blueprint036ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(armed.get()&&m.gui.screen() instanceof HelloWorldScreen screen){widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());button(screen,"sanctuary.progression.enter").onPress(enter());armed.set(false);}});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary blueprint036 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&RecipeClient.ready&&SanctuaryJeiPlugin.runtime()!=null&&m.gui.screen()==null,1200);var server=world.getServer();
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().clearContent();p.giveExperienceLevels(4-p.experienceLevel);RecipeService.refresh(p);});c.waitFor(m->RecipeClient.xp>=4,150);
+ c.runOnClient(m->RecipeClient.request("buy"));c.waitFor(m->RecipeClient.allowed()&&RecipeClient.knowledge.learned(),150);
+ c.runOnClient(m->{var filter=SanctuaryJeiPlugin.runtime().getIngredientFilter();filter.setFilterText("");require(filter.getFilteredItemStacks().stream().noneMatch(i->i.is(Items.BOW)),"Undiscovered bow blueprint is initially hidden");});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(String id:List.of("adventure/kill_a_mob","story/root","husbandry/fishy_business","adventure/trade")){var a=s.getAdvancements().get(Identifier.withDefaultNamespace(id));for(String criterion:a.value().criteria().keySet())p.getAdvancements().award(a,criterion);}RecipeService.refresh(p);});
+ c.waitFor(m->RecipeClient.knowledge.collections.containsAll(List.of("sanctuary:archery","sanctuary:basic_wood","sanctuary:boats_and_rafts","sanctuary:books_and_storage")),200);
+ c.runOnClient(m->{var r=SanctuaryJeiPlugin.runtime();r.getIngredientFilter().setFilterText("");var shown=r.getIngredientFilter().getFilteredItemStacks();
+ for(Item item:List.of(Items.BOW,Items.CROSSBOW,Items.CRAFTING_TABLE,Items.OAK_BOAT,Items.BAMBOO_RAFT,Items.BOOK,Items.BOOKSHELF)){
+ String id=net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(item).toString();require(!RecipeClient.knowledge.items.contains(id),"Blueprint does not invent possession: "+id);require(shown.stream().anyMatch(i->i.is(item)),"Known recipe output searchable in actual JEI catalogue: "+id);
+ require(r.getRecipeManager().createRecipeLookup(RecipeTypes.CRAFTING).get().anyMatch(v->v.id().identifier().toString().equals(id)),"Corresponding recipe really visible: "+id);
+ }
+ require(shown.stream().noneMatch(i->i.is(Items.DIAMOND_BLOCK)||i.is(Items.STRING)),"Unrelated item and unknown input remain hidden");
+ require(!RecipeClient.operator,"All checks use inhabitant mode");});
+ for(Item item:List.of(Items.BOW,Items.CRAFTING_TABLE,Items.OAK_BOAT,Items.BOOK)){
+ c.runOnClient(m->{m.gui.setScreen(new InventoryScreen(m.player));var r=SanctuaryJeiPlugin.runtime();r.getRecipesGui().show(r.getJeiHelpers().getFocusFactory().createFocus(RecipeIngredientRole.OUTPUT,VanillaTypes.ITEM_STACK,item.getDefaultInstance()));});
+ c.waitFor(m->m.gui.screen() instanceof mezz.jei.gui.recipes.RecipesGui,100);c.waitTicks(3);c.takeScreenshot("sanctuary-beta036-blueprint-"+net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(item).getPath());c.runOnClient(m->m.gui.setScreen(null));
+ }
+ System.out.println("BLUEPRINT036 passed: Archery, Woodworking, Boats and Rafts, Books and Storage; real JEI searchable outputs + focused recipe screens; undiscovered inputs/other objects hidden; no invented possessions; inhabitant permissions.");
+ }
+ }
+ private static Stream widgets(ContainerEventHandler c){return c.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static Button button(Screen s,String key){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow();}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean ok,String why){if(!ok)throw new AssertionError(why);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/BoatsHead049ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/BoatsHead049ClientChecks.java
new file mode 100644
index 0000000..6474882
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/BoatsHead049ClientChecks.java
@@ -0,0 +1,115 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.boats.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.entity.CampfireBlockEntity;
+import net.minecraft.client.renderer.blockentity.state.*;
+import net.minecraft.client.renderer.entity.state.AvatarRenderState;
+import fr.koka.sanctuary.client.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.CameraType;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.phys.Vec3;
+
+public final class BoatsHead049ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen s){
+ Starter039TestSupport.selectFirst(s);
+ widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b,"sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ try(var world=c.worldBuilder().adjustSettings(s->{
+ s.setName("BoatsHead049 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setAllowCommands(true);
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());
+ }).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ for(String locale:new String[]{"en_us","fr_fr"})c.runOnClient(m->{
+ m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
+ for(var v:CrewBoats.variants()){
+ var boat=v.type().create(m.level,EntitySpawnReason.COMMAND);
+ require(m.getEntityRenderDispatcher().getRenderer(boat)!=null,"Native renderer for "+v.wood());
+ var mesh=TiledBoatModel.hull(v.columns(),v.rows(),v.raft()).bakeRoot();
+ require(mesh.getAllParts().size()==v.tiles()+3+(v.raft()?0:2*(v.columns()+v.rows())+4),"One floor per cell, only exterior rails and two paddles");
+ require(mesh.getAllParts().stream().allMatch(part->part.xScale==1&&part.yScale==1&&part.zScale==1),"No stretched model part");
+ require(mesh.hasChild("left_paddle")&&mesh.hasChild("right_paddle"),"Exactly one native pair of paddles");
+ require(TiledBoatModel.waterMask(v.columns(),v.rows()).bakeRoot().getAllParts().size()==v.tiles()+1,"Water mask covers every floor tile");
+ require(!new ItemStack(v.item()).getHoverName().getString().startsWith("item.sanctuary"),"Localized item name "+locale);
+ }
+ });
+ var server=world.getServer();
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;p.getInventory().clearContent();
+ s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");
+ s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon");
+ p.setPos(p.position().add(0,2,0));
+ AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.CAMPFIRE));
+ var session=HeadBlockService.session(p);
+ HeadBlockService.scoped(session,()->{((CampfireBlockEntity)session.entity).placeFood(p.level(),p,new ItemStack(Items.BEEF));return null;});
+ });
+ c.runOnClient(m->m.options.setCameraType(CameraType.THIRD_PERSON_FRONT));c.getInput().pressKey(InputConstants.KEY_F1);c.getInput().lookAt(0,0);c.waitTicks(15);
+ c.runOnClient(m->{var h=head(m);require(h.blockEntity instanceof CampfireRenderState fire&&fire.items.stream().anyMatch(i->!i.isEmpty()),"Actual campfire renderer receives network food");});
+ c.takeScreenshot("sanctuary-beta049-campfire-food");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.CHEST));HeadBlockService.session(p).event(1,1);});
+ c.waitTicks(15);
+ c.runOnClient(m->{var h=head(m);require(h.blockEntity instanceof ChestRenderState chest&&chest.open>.7,"Native lid animation opens on client from server event");});
+ c.takeScreenshot("sanctuary-beta049-chest-open");
+ server.runOnServer(s->HeadBlockService.session(s.getPlayerList().getPlayers().getFirst()).event(1,0));c.waitTicks(15);
+ c.runOnClient(m->{var h=head(m);require(h.blockEntity instanceof ChestRenderState chest&&chest.open<.05,"Native lid closes without recreating the proxy");});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.SHULKER_BOX));HeadBlockService.session(p).event(1,1);});
+ c.waitTicks(15);
+ c.runOnClient(m->require(head(m).blockEntity instanceof ShulkerBoxRenderState box&&box.progress>.9,"Shulker opens visually without running client block collisions"));
+ c.takeScreenshot("sanctuary-beta049-shulker-open");
+ server.runOnServer(s->HeadBlockService.session(s.getPlayerList().getPlayers().getFirst()).event(1,0));c.waitTicks(15);
+ c.runOnClient(m->require(head(m).blockEntity instanceof ShulkerBoxRenderState box&&box.progress<.05,"Shulker lid closes"));
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.BELL));HeadBlockService.session(p).event(1,net.minecraft.core.Direction.NORTH.get3DDataValue());});
+ c.waitTicks(12);c.runOnClient(m->require(head(m).blockEntity instanceof BellRenderState bell&&bell.ticks>1,"Bell ring animates over successive ticks"));
+ c.waitTicks(50);c.runOnClient(m->require(head(m).blockEntity instanceof BellRenderState bell&&bell.shakeDirection==null,"Bell ringing stops"));
+ for(var item:List.of(Items.FURNACE,Items.PLAYER_HEAD,Items.OAK_TRAPDOOR,CrewBoats.variant("oak",3,1).item())){
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(item));HeadBlockService.session(p);});
+ c.waitTicks(12);
+ c.runOnClient(m->{var h=head(m);if(item instanceof net.minecraft.world.item.BoatItem)require(h.entity instanceof net.minecraft.client.renderer.entity.state.BoatRenderState,"Boat hat uses its entity model");else require(h.blockEntity!=null||!h.block.isEmpty(),"Native head visual exists");});
+ c.takeScreenshot("sanctuary-beta049-head-"+net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(item).getPath());
+ }
+ server.runOnServer(s->AccessoryAccess.of(s.getPlayerList().getPlayers().getFirst()).clearContent());
+ c.runOnClient(m->m.options.setCameraType(CameraType.THIRD_PERSON_BACK));
+ for(var shape:List.of(new CrewBoats.Shape(3,1),new CrewBoats.Shape(1,3),new CrewBoats.Shape(2,3),new CrewBoats.Shape(3,2))){
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var old=p.getVehicle();p.stopRiding();if(old!=null)old.getSelfAndPassengers().toList().reversed().forEach(Entity::discard);
+ var boat=CrewBoats.variant("oak",shape.columns(),shape.rows()).type().create(s.overworld(),EntitySpawnReason.COMMAND);boat.setPos(p.position().add(0,8,0));s.overworld().addFreshEntity(boat);
+ var motor=EntityTypes.ALLAY.create(s.overworld(),EntitySpawnReason.COMMAND);motor.setPos(boat.position());motor.setNoAi(true);s.overworld().addFreshEntity(motor);motor.startRiding(boat);p.startRiding(boat,true,true);
+ });
+ c.waitFor(m->m.player.getVehicle() instanceof CrewBoat b&&b.columns()==shape.columns()&&b.rows()==shape.rows(),200);
+ c.getInput().holdKey(o->o.keyJump);c.getInput().holdKey(o->o.keyUp);c.waitTicks(20);
+ Vec3 from=c.computeOnClient(m->m.player.getVehicle().position());c.waitTicks(40);Vec3 to=c.computeOnClient(m->m.player.getVehicle().position());
+ require(to.y>from.y+2&&to.subtract(from).horizontalDistance()>5,"Each rectangular boat flies from native pilot inputs");
+ c.getInput().lookAt(25,20);c.waitTicks(5);c.takeScreenshot("sanctuary-beta049-boat-"+shape.columns()+"x"+shape.rows());
+ c.getInput().releaseKey(o->o.keyUp);c.getInput().releaseKey(o->o.keyJump);
+ }
+ System.out.println("BOATS_HEAD049_CLIENT_PASS: 66 tiled models FR/EN, actual campfire food, chest and Shulker lids open/close, head models, four rectangular flights");
+ }
+ }
+ private static HeadCosmeticState head(net.minecraft.client.Minecraft m){
+ var renderer=m.getEntityRenderDispatcher().getRenderer(m.player);
+ var state=renderer.createRenderState(m.player,1);
+ return ((HeadCosmeticState.Access)state).sanctuary$headCosmetic();
+ }
+ private static Stream widgets(ContainerEventHandler root){return root.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static boolean key(Button button,String key){return button.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key);}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean yes,String message){if(!yes)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Building009ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Building009ClientChecks.java
new file mode 100644
index 0000000..489f8c8
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Building009ClientChecks.java
@@ -0,0 +1,111 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.building.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.*;
+import net.minecraft.client.KeyMapping;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.input.MouseButtonInfo;
+import net.minecraft.core.BlockPos;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.stats.Stats;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+
+/** Real native input and network packets, with a fresh development-only wall. */
+final class Building009ClientChecks {
+ static void run(ClientGameTestContext c,TestServerContext server) {
+ server.runOnServer(s -> s.getPlayerList().getPlayers().getFirst().giveExperienceLevels(3));
+ c.waitFor(m -> m.player.experienceLevel==3,400);
+ for (int rank=1;rank<=2;rank++) {
+ c.runOnClient(m -> {
+ var button=m.gui.screen().children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals("sanctuary.progression.upgrade_cost"))
+ .sorted(java.util.Comparator.comparingInt(Button::getY)).skip(3).findFirst().orElseThrow();
+ require(button.active,"Construction is purchasable in the skill column");
+ double x=(button.getX()+button.getWidth()/2.)*m.getWindow().getScreenWidth()/m.getWindow().getGuiScaledWidth();
+ double y=(button.getY()+button.getHeight()/2.)*m.getWindow().getScreenHeight()/m.getWindow().getGuiScaledHeight();
+ m.mouseHandler.onMove(m.getWindow().handle(),x,y,0,0);m.mouseHandler.handleAccumulatedMovement();
+ var mouse=new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0);
+ m.mouseHandler.onButton(m.getWindow().handle(),mouse,InputConstants.PRESS);
+ m.mouseHandler.onButton(m.getWindow().handle(),mouse,InputConstants.RELEASE);
+ });
+ int expected=rank;c.waitFor(m -> ProgressionClient.progress.building()==expected,400);
+ }
+ c.waitFor(m -> m.player.experienceLevel==0,400);
+ Blocodex004ClientChecks.language(c,"fr_fr");c.takeScreenshot("sanctuary-beta009-construction-fr");
+ Blocodex004ClientChecks.language(c,"en_us");c.clickScreenButton("gui.done");c.waitForScreen(null);
+ var origin=server.computeOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();var base=p.blockPosition().above(14);
+ for(int x=-3;x<=3;x++) for(int z=-1;z<=5;z++) {
+ p.level().setBlockAndUpdate(base.offset(x,-1,z),Blocks.BEDROCK.defaultBlockState());
+ for(int y=0;y<=6;y++) p.level().setBlockAndUpdate(base.offset(x,y,z),Blocks.AIR.defaultBlockState());
+ }
+ var target=base.offset(0,2,3);
+ for(int x=-2;x<=2;x++) for(int y=-2;y<=2;y++) p.level().setBlockAndUpdate(target.offset(x,y,0),Blocks.STONE.defaultBlockState());
+ p.teleportTo(base.getX()+.5,base.getY(),base.getZ()+.5);
+ p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.COBBLESTONE,32));p.inventoryMenu.broadcastChanges();
+ return target;
+ });
+ c.waitFor(m -> m.level.getBlockState(origin).is(Blocks.STONE) && m.player.getMainHandItem().is(Items.COBBLESTONE)
+ && Math.abs(m.player.getY()-(origin.getY()-2))<.1,600);
+ c.getInput().lookAt(origin);c.waitTicks(3);c.getInput().holdKey(InputConstants.KEY_R);
+ c.waitFor(m -> BuildingClient.previewSize()==25 && MiningClient.previewSize()==0,200);
+ c.takeScreenshot("sanctuary-beta009-building-preview-25");
+ c.runOnClient(m -> {
+ int slot=m.player.getInventory().getSelectedSlot();m.mouseHandler.onScroll(m.getWindow().handle(),0,-1);
+ require(BuildingClient.selectedRank()==1 && m.player.getInventory().getSelectedSlot()==slot,"Shared modifier wheel selects a 3x3 surface without changing material");
+ });
+ c.waitFor(m -> BuildingClient.previewSize()==9,100);
+ c.runOnClient(m -> m.mouseHandler.onScroll(m.getWindow().handle(),0,1));
+ c.waitFor(m -> BuildingClient.previewSize()==25,100);
+ c.getInput().holdKey(o -> o.keyUse);
+ c.waitFor(m -> m.level.getBlockState(origin.north()).is(Blocks.COBBLESTONE),300);
+ c.getInput().releaseKey(o -> o.keyUse);
+ server.waitFor(s -> !BuildingManager.active(s.getPlayerList().getPlayers().getFirst()),200);
+ int first=server.computeOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();int used=32-p.getMainHandItem().getCount();
+ require(used>0 && used<25,"Releasing right click keeps only the paid partial surface");
+ return used;
+ });
+ c.getInput().releaseKey(InputConstants.KEY_R);resetWall(c,server,origin);
+ c.getInput().lookAt(origin);c.waitTicks(3);c.getInput().holdKey(InputConstants.KEY_R);
+ c.waitFor(m -> BuildingClient.previewSize()==25,200);c.getInput().holdKey(o -> o.keyUse);
+ server.waitFor(s -> s.getPlayerList().getPlayers().getFirst().getMainHandItem().getCount()==7,600);
+ c.waitTicks(10); // Holding beyond completion must not create a second layer.
+ server.runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();
+ require(p.getMainHandItem().getCount()==7 && !BuildingManager.active(p),"One gesture creates exactly one 25-block surface");
+ require(p.getStats().getValue(Stats.ITEM_USED.get(Items.COBBLESTONE))==25+first,"Native placement statistics count every paid block once");
+ for(int x=-2;x<=2;x++) for(int y=-2;y<=2;y++) require(p.level().getBlockState(origin.offset(x,y,-1)).is(Blocks.COBBLESTONE),"Full surface matches the preview");
+ });
+ c.getInput().releaseKey(o -> o.keyUse);c.getInput().releaseKey(InputConstants.KEY_R);c.waitTicks(3);
+ c.takeScreenshot("sanctuary-beta009-built-wall");resetWall(c,server,origin);c.getInput().lookAt(origin);c.waitTicks(3);
+ c.runOnClient(m -> {sharedKey(m).setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_G));KeyMapping.resetMapping();});
+ try {
+ c.getInput().holdKey(InputConstants.KEY_G);c.waitFor(m -> BuildingClient.previewSize()==25,200);
+ c.getInput().holdKey(o -> o.keyUse);
+ server.waitFor(s -> BuildingManager.active(s.getPlayerList().getPlayers().getFirst()),200);
+ c.getInput().pressKey(InputConstants.KEY_B);c.waitForScreen(ProgressionScreen.class);
+ server.waitFor(s -> !BuildingManager.active(s.getPlayerList().getPlayers().getFirst()),200);
+ } finally {
+ c.getInput().releaseKey(o -> o.keyUse);c.getInput().releaseKey(InputConstants.KEY_R);c.getInput().releaseKey(InputConstants.KEY_G);
+ c.runOnClient(m -> {sharedKey(m).setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_R));KeyMapping.resetMapping();});
+ }
+ System.out.println("Building beta009 native client passed: two real purchases, 25/9 previews, wheel, release, exact consumption, one frozen layer, key rebind and Blocodex interruption.");
+ }
+ private static void resetWall(ClientGameTestContext c,TestServerContext server,BlockPos origin) {
+ server.runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();
+ for(int x=-2;x<=2;x++) for(int y=-2;y<=2;y++) p.level().setBlockAndUpdate(origin.offset(x,y,-1),Blocks.AIR.defaultBlockState());
+ p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.COBBLESTONE,32));p.inventoryMenu.broadcastChanges();
+ });
+ c.waitFor(m -> m.level.getBlockState(origin.north()).isAir() && m.player.getMainHandItem().getCount()==32,300);
+ }
+ private static KeyMapping sharedKey(net.minecraft.client.Minecraft m) {
+ return java.util.Arrays.stream(m.options.keyMappings).filter(k -> k.getName().equals("sanctuary.mining.key")).findFirst().orElseThrow();
+ }
+ private static void require(boolean value,String message) {if(!value) throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Collections035ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Collections035ClientChecks.java
new file mode 100644
index 0000000..8337c53
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Collections035ClientChecks.java
@@ -0,0 +1,121 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.recipes.*;
+import fr.koka.sanctuary.progression.*;
+import fr.koka.sanctuary.inventory.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import mezz.jei.api.constants.RecipeTypes;
+import mezz.jei.api.sanctuary.CatalogueAccess;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.InventoryScreen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.item.*;
+
+public final class Collections035ClientChecks implements FabricClientGameTest {
+ @Override public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ button(screen,"sanctuary.progression.enter").onPress(enter());onboarding.set(false);
+ }});
+ net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved;
+ int scale=c.computeOnClient(m->m.options.guiScale().get());
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary beta035 recipes "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ saved=world.getWorldSave();
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&RecipeClient.ready&&SanctuaryJeiPlugin.runtime()!=null&&m.gui.screen()==null,1200);
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new InventoryScreen(m.player));});c.waitTicks(6);
+ c.runOnClient(m->{require(!RecipeClient.allowed(),"Catalogue initially locked");require(!SanctuaryJeiPlugin.runtime().getIngredientListOverlay().isListDisplayed(),"Locked catalogue has no sidebar");SanctuaryJeiPlugin.runtime().getRecipesGui().showTypes(List.of(RecipeTypes.CRAFTING));require(m.gui.screen() instanceof InventoryScreen,"JEI cannot open while locked");});
+ c.takeScreenshot("sanctuary-beta035-locked-inventory");
+ var server=world.getServer();
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().clearContent();p.getInventory().setItem(0,new ItemStack(Items.OAK_PLANKS,8));p.giveExperienceLevels(4-p.experienceLevel);RecipeService.refresh(p);});
+ c.waitFor(m->RecipeClient.knowledge.recipes.contains("minecraft:crafting_table")&&RecipeClient.xp>=4,120);
+ c.runOnClient(m->{require(!RecipeClient.knowledge.recipes.contains("minecraft:shield"),"Plank discovery does not teach shield");m.gui.setScreen(new RecipeScreen(new net.minecraft.client.gui.screens.PauseScreen(true)));});c.waitTicks(3);c.takeScreenshot("sanctuary-beta035-collections-locked");
+ c.runOnClient(m->button(m.gui.screen(),"sanctuary.recipes.buy").onPress(enter()));c.waitFor(m->RecipeClient.allowed()&&RecipeClient.knowledge.learned(),120);
+ c.runOnClient(m->{var r=SanctuaryJeiPlugin.runtime();require(r.getIngredientFilter().getFilteredItemStacks().stream().noneMatch(i->i.is(Items.DIAMOND_BLOCK)),"Unknown item hidden");
+ require(r.getIngredientFilter().getFilteredIngredients(mezz.jei.api.fabric.constants.FabricTypes.FLUID_STACK).isEmpty(),"Unknown fluids hidden too");
+ require(r.getRecipeManager().createRecipeCategoryLookup().get().anyMatch(cat->cat.getRecipeType().equals(RecipeTypes.CRAFTING)),"Learned blueprints appear before discovering their crafting station");
+ require(r.getRecipeManager().createRecipeLookup(RecipeTypes.CRAFTING).get().anyMatch(v->v.id().identifier().toString().equals("minecraft:crafting_table")),"Learned wood recipe visible");
+ require(r.getRecipeManager().createRecipeLookup(RecipeTypes.CRAFTING).get().noneMatch(v->v.id().identifier().toString().equals("minecraft:shield")),"Unknown recipe hidden");
+ m.gui.setScreen(new InventoryScreen(m.player));});c.waitTicks(5);c.takeScreenshot("sanctuary-beta035-personal-catalogue");
+ hover(c,36);c.getInput().pressKey(InputConstants.KEY_U);c.waitFor(m->m.gui.screen() instanceof mezz.jei.gui.recipes.RecipesGui,60);
+ c.runOnClient(m->{m.gui.setScreen(new RecipeScreen(null));button(m.gui.screen(),"sanctuary.recipes.open").onPress(enter());});c.waitFor(m->m.gui.screen() instanceof mezz.jei.gui.recipes.RecipesGui,60);c.waitTicks(5);c.takeScreenshot("sanctuary-beta035-wood-recipes");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();s.getPlayerList().op(p.nameAndId());RecipeService.refresh(p);});c.waitFor(m->RecipeClient.operatorAllowed,120);
+ c.runOnClient(m->{require(!RecipeClient.operator,"Operator status alone does not enable full view");m.gui.setScreen(new RecipeScreen(null));RecipeClient.request("operator");});
+ c.waitFor(m->RecipeClient.operator,120);c.waitTicks(3);c.takeScreenshot("sanctuary-beta035-operator-view");
+ c.runOnClient(m->require(SanctuaryJeiPlugin.runtime().getIngredientFilter().getFilteredItemStacks().stream().anyMatch(i->i.is(Items.DIAMOND_BLOCK)),"Explicit operator sees unknown items"));
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();s.getPlayerList().deop(p.nameAndId());RecipeService.refresh(p);});
+ c.waitFor(m->!RecipeClient.operatorAllowed&&!RecipeClient.operator,120);c.runOnClient(m->require(SanctuaryJeiPlugin.runtime().getIngredientFilter().getFilteredItemStacks().stream().noneMatch(i->i.is(Items.DIAMOND_BLOCK)),"Permission revocation removes unknown items"));
+ // Use JEI's real transfer packet with ingredients available only in the final added row.
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var progress=ProgressEffects.get(p);for(int i=progress.inventory();i<5;i++)progress=progress.buy("inventory",1);
+ p.setAttached(ProgressionService.RECORD,progress.json());ProgressEffects.apply(p,progress);p.getInventory().clearContent();p.getInventory().setItem(SanctuaryInventoryCapacity.EXTRA_START+17,new ItemStack(Items.OAK_PLANKS,8));p.inventoryMenu.broadcastChanges();ProgressionService.send(p,"");});
+ c.waitFor(m->ProgressionClient.progress.inventoryRows()==6&&m.player.getInventory().getItem(SanctuaryInventoryCapacity.EXTRA_START+17).getCount()==8,120);
+ c.runOnClient(m->{m.gui.setScreen(new InventoryScreen(m.player));var menu=m.player.inventoryMenu;var slots=CatalogueAccess.inventory.apply(menu,List.of());require(slots.size()==54,"JEI sees all six unlocked storage rows");
+ var r=SanctuaryJeiPlugin.runtime();var manager=r.getRecipeManager();var category=manager.getRecipeCategory(RecipeTypes.CRAFTING);
+ var recipe=manager.createRecipeLookup(RecipeTypes.CRAFTING).get().filter(v->v.id().identifier().toString().equals("minecraft:crafting_table")).findFirst().orElseThrow();
+ var layout=manager.createRecipeLayoutDrawable(category,recipe,r.getJeiHelpers().getFocusFactory().createFocusGroup(List.of())).orElseThrow();
+ var handler=r.getRecipeTransferManager().getRecipeTransferHandler(menu,category).orElseThrow();
+ require(handler.transferRecipe(menu,recipe,layout.getRecipeSlotsView(),m.player,false,true)==null,"Real JEI transfer accepts overflow ingredients");});
+ c.waitFor(m->m.player.inventoryMenu.getSlot(0).getItem().is(Items.CRAFTING_TABLE),120);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();require(p.getInventory().getItem(SanctuaryInventoryCapacity.EXTRA_START+17).getCount()==4,"Exactly four planks consumed from row six");
+ for(int i=1;i<=4;i++)require(p.inventoryMenu.getSlot(i).getItem().is(Items.OAK_PLANKS)&&p.inventoryMenu.getSlot(i).getItem().getCount()==1,"Grid receives one plank per slot");});
+ c.takeScreenshot("sanctuary-beta035-overflow-transfer");
+ c.runOnClient(m->{m.player.closeContainer();NotificationClient.clearFeed();});c.waitTicks(3);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();
+ for(String id:List.of("minecraft:story/smelt_iron","minecraft:nether/brew_potion","minecraft:end/find_end_city")){
+ var a=s.getAdvancements().get(net.minecraft.resources.Identifier.parse(id));for(String criterion:a.value().criteria().keySet())p.getAdvancements().award(a,criterion);
+ }
+ RecipeService.refresh(p);
+ });
+ c.waitFor(m->RecipeClient.knowledge.collections.containsAll(List.of("sanctuary:ironworking","sanctuary:brewing","sanctuary:shulker_storage")),200);
+ c.runOnClient(m->{
+ require(!RecipeClient.knowledge.items.contains("minecraft:iron_ingot")&&!RecipeClient.knowledge.items.contains("minecraft:shulker_shell"),"Advancement unlocks plans without revealing their ingredients as discovered");
+ require(RecipeClient.knowledge.recipes.contains("minecraft:shield")&&RecipeClient.knowledge.recipes.contains("minecraft:shulker_box"),"Full families arrive over the actual personal network");
+ require(SanctuaryJeiPlugin.runtime().getRecipeManager().createRecipeLookup(RecipeTypes.CRAFTING).get().anyMatch(r->r.id().identifier().toString().equals("minecraft:shulker_box")),"Shulker blueprint visible without shell possession");
+ require(NotificationClient.messages().stream().anyMatch(e->e.value().text().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.notifications.collection_batch")),"New collection batch is announced");
+ });
+ c.takeScreenshot("sanctuary-beta035-collection-notifications");
+ for(String language:List.of("fr_fr","en_us")){
+ var reload=c.computeOnClient(m->{m.getLanguageManager().setSelected(language);m.options.languageCode=language;return m.reloadResourcePacks();});c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();
+ c.runOnClient(m->{m.gui.setScreen(new RecipeScreen(null));require(RecipeClient.collectionName("sanctuary:ironworking").getString().equals(language.equals("fr_fr")?"Fer et forge de base":"Ironworking"),"Selected language is rendered");for(String id:RecipeClient.knowledge.collections)require(!RecipeClient.collectionName(id).getString().startsWith("sanctuary"),"Collections are translated");});
+ c.waitTicks(5);c.takeScreenshot("sanctuary-beta035-collections-"+language);
+ }
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var progress=ProgressEffects.get(p);
+ for(String skill:ProgressRecord.SKILLS)while(progress.rank(skill)ProgressionClient.progress.inventoryRows()==1&&ProgressionClient.progress.cycle()==1,120);
+ c.runOnClient(m->{require(CatalogueAccess.inventory.apply(m.player.inventoryMenu,List.of()).size()==9,"After reset, locked recovery rows excluded from JEI autofill");require(RecipeClient.knowledge.learned(),"Catalogue persists after skill reset");
+ m.options.guiScale().set(4);m.resizeGui();m.gui.setScreen(new RecipeScreen(null));});c.waitTicks(4);c.takeScreenshot("sanctuary-beta035-compact-blocodex");
+ c.runOnClient(m->m.gui.setScreen(null));
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.hurtServer(p.level(),p.damageSources().fellOutOfWorld(),Float.MAX_VALUE);});
+ c.waitForScreen(net.minecraft.client.gui.screens.DeathScreen.class);c.waitTicks(45);c.clickScreenButton("deathScreen.respawn");
+ c.waitFor(m->m.player!=null&&m.player.isAlive()&&m.gui.screen()==null&&ProgressionClient.active(),1200);c.waitTicks(25);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();require(RecipeKnowledge.parse(p.getAttached(RecipeService.RECORD)).learned(),"Real death preserves catalogue attachment");s.saveEverything(true,true,true);});
+ System.out.println("COLLECTIONS035: locked UI, personal discoveries, recipes, operator revocation and real six-row transfer passed.");
+ }finally{onboarding.set(false);c.runOnClient(m->{m.options.guiScale().set(scale);m.resizeGui();});}
+ c.runOnClient(m->require(!RecipeClient.ready&&!RecipeClient.allowed(),"Recipe state cleared on world exit"));
+ try(var reopened=saved.open()){
+ reopened.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&RecipeClient.ready&&SanctuaryJeiPlugin.runtime()!=null,1200);
+ c.runOnClient(m->{require(RecipeClient.knowledge.learned()&&RecipeClient.knowledge.recipes.contains("minecraft:crafting_table"),"Catalogue and recipes survive server restart");require(!RecipeClient.operator,"Operator inspection is not persisted");require(RecipeClient.knowledge.collections.containsAll(List.of("sanctuary:ironworking","sanctuary:brewing","sanctuary:shulker_storage"))&&RecipeClient.knowledge.recipes.contains("minecraft:shulker_box"),"New collections survive actual prestige, death and world restart");});
+ System.out.println("COLLECTIONS035: actual prestige, death and server restart preserve recipe knowledge.");
+ }
+ }
+ private static void hover(ClientGameTestContext c,int index){c.runOnClient(m->{var screen=(net.minecraft.client.gui.screens.inventory.AbstractContainerScreen>)m.gui.screen();var a=(fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor)screen;var slot=screen.getMenu().getSlot(index);var w=m.getWindow();double x=a.sanctuary$getLeftPos()+slot.x+8.0,y=a.sanctuary$getTopPos()+slot.y+8.0;double sx=(double)w.getScreenWidth()/w.getGuiScaledWidth(),sy=(double)w.getScreenHeight()/w.getGuiScaledHeight();m.mouseHandler.onMove(w.handle(),x*sx,y*sy,(x-m.mouseHandler.getScaledXPos(w))*sx,(y-m.mouseHandler.getScaledYPos(w))*sy);m.mouseHandler.handleAccumulatedMovement();});c.waitTicks(2);}
+ private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static Stream widgets(ContainerEventHandler c){return c.children().stream().flatMap(w->w instanceof ContainerEventHandler nested?Stream.concat(Stream.of(w),widgets(nested)):Stream.of(w));}
+ private static Button button(Screen screen,String key){return widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(w->w.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow(()->new AssertionError("Missing "+key));}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Combat054ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Combat054ClientChecks.java
new file mode 100644
index 0000000..24d26ee
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Combat054ClientChecks.java
@@ -0,0 +1,82 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.familiar.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.companion.*;
+import fr.koka.sanctuary.inventory.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.item.*;
+
+/** Integrated development world: native AI, networking and persistence, without a dedicated server EULA. */
+public final class Combat054ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen s){
+ Starter039TestSupport.selectFirst(s);
+ s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave saved;
+ var individual=new java.util.concurrent.atomic.AtomicReference();
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Combat054 "+UUID.randomUUID());s.setSeed("54");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setAllowCommands(true);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ saved=world.getWorldSave();world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ world.getServer().runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;
+ require(FamiliarBattle.world(s)!=null&&FamiliarBattle.world(s).catalog.profiles().size()==88,"All 88 server profiles loaded");
+ s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set night");
+ equip(p,"wolf");
+ });
+ c.waitTicks(25);
+ world.getServer().runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);
+ require(pet!=null,"Physical familiar spawns");individual.set(FamiliarBattle.bond(p).id());
+ var zombie=EntityTypes.ZOMBIE.create(p.level(),EntitySpawnReason.COMMAND);zombie.setNoAi(true);zombie.setPos(pet.position().add(0,0,2));p.level().addFreshEntity(zombie);
+ FamiliarBattle.engage(p,zombie,false);
+ require(FamiliarBattle.state(p).target.equals(zombie.getUUID()),"Engage binds an enemy");
+ });
+ c.waitTicks(100);
+ world.getServer().runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var target=FamiliarBattle.target(p);
+ require(target!=null&&target.getHealth(){m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());FamiliarClient.open(null);});
+ c.waitTicks(6);c.takeScreenshot("sanctuary-beta054-familiar-"+locale);c.runOnClient(m->m.gui.setScreen(null));
+ }
+ }
+ try(var world=saved.open()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();require(individual.get().equals(FamiliarBattle.bond(p).id()),"Same individual after reconnect");require(FamiliarBattle.bond(p).health()m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ world.getServer().runOnServer(s->Experience054Checks.reconnect(s.getPlayerList().getPlayers().getFirst()));
+ }
+ System.out.println("COMBAT054_CORE_PASS: physical autonomous attack, wound persistence, 88 profiles, FR/EN interface and real reconnect");
+ }
+ static void equip(ServerPlayer p,String id){
+ FamiliarBattle.release(p);var type=net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getValue(net.minecraft.resources.Identifier.withDefaultNamespace(id));
+ var egg=new ItemStack(SpawnEggItem.byId(type).orElseThrow());AccessoryAccess.of(p).setItem(AccessoryContainer.FAMILIAR,egg);
+ CompanionService.tick(p);p.tickCount+=10;FamiliarService.tick(p);
+ }
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ static void require(boolean value,String text){if(!value)throw new AssertionError(text);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Companion019ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Companion019ClientChecks.java
new file mode 100644
index 0000000..76e0279
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Companion019ClientChecks.java
@@ -0,0 +1,143 @@
+package fr.koka.sanctuary.gametest;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.companion.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.resources.Identifier;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.phys.Vec3;
+
+public final class Companion019ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{
+ if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ button(screen,"sanctuary.progression.enter").onPress(enter());onboarding.set(false);
+ }
+ });
+ int scale=c.computeOnClient(m->m.options.guiScale().get());
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary beta019 familiars "+UUID.randomUUID());s.setSeed("42");
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&CompanionClient.record!=null&&m.gui.screen()==null,1200);
+ var server=world.getServer();server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(5);
+ for(int x=-9;x<=9;x++)for(int z=-9;z<=9;z++)for(int y=-1;y<8;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1?Blocks.STONE:Blocks.AIR).defaultBlockState());
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()+.5);p.setYRot(0);p.setXRot(-8);
+ p.getInventory().clearContent();p.giveExperienceLevels(10-p.experienceLevel);AccessoryAccess.of(p).setItem(0,new ItemStack(Items.CAT_SPAWN_EGG));CompanionService.send(p,"");
+ });
+ c.waitFor(m->CompanionClient.species==CompanionType.CAT,200);
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new ProgressionScreen(null));});c.waitTick();
+ c.runOnClient(m->{require(!CompanionClient.record.unlocked(),"First session starts with active bond locked");button(m.gui.screen(),"sanctuary.companion.buy").onPress(enter());});
+ c.waitFor(m->CompanionClient.record!=null&&CompanionClient.record.unlocked(),200);
+ require(server.computeOnServer(s->s.getPlayerList().getPlayers().getFirst().experienceLevel)==6,"Purchase is synchronized and costs four levels");
+ c.runOnClient(m->CompanionClient.openInhabitant(null));c.waitTick();
+ c.runOnClient(m->button(m.gui.screen(),"sanctuary.companion.consent_on").onPress(enter()));c.waitFor(m->!CompanionClient.record.acceptAllies(),200);
+ c.runOnClient(m->button(m.gui.screen(),"sanctuary.companion.consent_off").onPress(enter()));c.waitFor(m->CompanionClient.record.acceptAllies(),200);
+ for(int gui:new int[]{2,3,4}){
+ c.runOnClient(m->{m.options.guiScale().set(gui);m.resizeGui();});c.waitTick();
+ c.runOnClient(m->{for(var widget:widgets(m.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast).toList())
+ require(widget.getWidth()>0&&widget.getX()>=0&&widget.getRight()<=m.gui.screen().width,"Familiar buttons fit GUI width");});
+ c.takeScreenshot("sanctuary-beta019-familiar-gui-"+gui);
+ }
+ c.runOnClient(m->{m.gui.setScreen(null);m.options.guiScale().set(2);m.resizeGui();});c.waitTick();
+ // Extract all native render states at an actual nonzero world position, at two animation times.
+ c.runOnClient(m->{
+ try{
+ var field=FamiliarEntity.class.getDeclaredField("EGG");field.setAccessible(true);
+ @SuppressWarnings("unchecked") var eggData=(net.minecraft.network.syncher.EntityDataAccessor)field.get(null);
+ var pet=FamiliarService.TYPE.create(m.level,EntitySpawnReason.COMMAND);pet.setId(900000);pet.setPos(m.player.position().add(2,1,3));
+ pet.xo=pet.getX();pet.yo=pet.getY();pet.zo=pet.getZ();pet.xOld=pet.getX();pet.yOld=pet.getY();pet.zOld=pet.getZ();
+ var renderer=(FamiliarRenderer)m.getEntityRenderDispatcher().getRenderer(pet);
+ for(var type:CompanionType.values()){
+ pet.getEntityData().set(eggData,new ItemStack(BuiltInRegistries.ITEM.getValue(Identifier.withDefaultNamespace(type.id()+"_spawn_egg"))));
+ pet.tickCount=100;pet.setDeltaMovement(.05,0,.1);pet.setYRot(30);
+ var state=renderer.createRenderState();renderer.extractRenderState(pet,state,.5F);
+ require(state.appearance!=null&&state.renderer!=null,"Native visual exists for "+type);
+ require(net.minecraft.util.LightCoordsUtil.sky(state.appearance.lightCoords)==m.level.getBrightness(net.minecraft.world.level.LightLayer.SKY,pet.blockPosition()),"Native light follows familiar world position for "+type);
+ pet.tickCount=110;pet.setYRot(65);renderer.extractRenderState(pet,state,.5F);
+ require(Float.isFinite(state.size)&&state.size>0,"Native visual keeps finite scale for "+type);
+ if(pet.motion().flies()){
+ var camera=new net.minecraft.client.renderer.state.level.CameraRenderState();camera.pos=pet.position().add(1.5,.35,0);camera.orientation=new org.joml.Quaternionf();
+ var collector=new net.minecraft.client.renderer.SubmitNodeStorage();
+ renderer.submit(state,new com.mojang.blaze3d.vertex.PoseStack(),collector,camera);
+ var submits=new ArrayList>();
+ collector.drainPhases(phase->phase.sortInto((node,sorted)->{if(node instanceof net.minecraft.client.renderer.feature.ModelFeatureRenderer.Submit> model)submits.add(model);}));
+ require(!submits.isEmpty(),"Flying model submits translucent geometry: "+type);
+ for(var model:submits)require(model.renderType().hasBlending()&&(model.tintedColor()>>>24)<100,"Body and eye layers both fade: "+type);
+ require(FamiliarFade.current()==1,"Transparency never leaks into the next entity");
+ }
+ float ordinarySize=state.size;
+ for(int perMille:new int[]{120,6000}){
+ var sized=pet.egg().copy();net.minecraft.world.item.component.CustomData.update(net.minecraft.core.component.DataComponents.CUSTOM_DATA,sized,tag->{
+ var trait=new net.minecraft.nbt.CompoundTag();trait.putInt("schema",1);trait.putInt("per_mille",perMille);tag.put(fr.koka.sanctuary.cosmetics.FamiliarSize.KEY,trait);
+ });pet.getEntityData().set(eggData,sized);renderer.extractRenderState(pet,state,.5F);
+ require(Math.abs(state.size-ordinarySize*perMille/1000F)<.0001,"Smallest and largest egg scale applies to native model "+type);
+ }
+ }
+ }catch(ReflectiveOperationException error){throw new AssertionError(error);}
+ });
+ for(var species:List.of(CompanionType.ENDER_DRAGON,CompanionType.WITHER,CompanionType.GHAST)){
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(0,new ItemStack(BuiltInRegistries.ITEM.getValue(Identifier.withDefaultNamespace(species.id()+"_spawn_egg"))));});
+ c.waitFor(m->CompanionClient.species==species,200);c.waitTicks(60);
+ var geometry=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);return new double[]{pet.getY()-p.getY(),pet.getX(),pet.getZ()};});
+ require(geometry[0]>.3,"Airborne familiar stays airborne: "+species);
+ c.runOnClient(m->{var toward=new Vec3(geometry[1],m.player.getY()+geometry[0]+.35,geometry[2]).subtract(m.player.getEyePosition());
+ m.player.setYRot((float)Math.toDegrees(Math.atan2(-toward.x,toward.z)));m.player.setXRot((float)-Math.toDegrees(Math.atan2(toward.y,toward.horizontalDistance())));
+ });c.waitTick();c.takeScreenshot("sanctuary-beta019-"+species.id());
+ }
+ for(double distance:new double[]{4,2,.8}){
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);pet.setNoAi(true);pet.getNavigation().stop();pet.setDeltaMovement(Vec3.ZERO);pet.setPos(p.getEyePosition().add(0,-.35,distance));});
+ c.runOnClient(m->{m.player.setYRot(0);m.player.setXRot(0);});c.waitTicks(5);
+ c.takeScreenshot("sanctuary-beta019-fade-"+distance);
+ }
+ // Real client key -> server activation -> durable cooldown, followed by an egg swap.
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(0,new ItemStack(Items.SLIME_SPAWN_EGG));});
+ c.waitFor(m->CompanionClient.species==CompanionType.SLIME&&CompanionClient.attunement()==0,400);
+ c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_G);
+ c.waitFor(m->CompanionClient.seconds()>0,100);
+ require(server.computeOnServer(s->CompanionEffects.has(s.getPlayerList().getPlayers().getFirst(),CompanionEffects.Kind.REBOUND)),"Configured key activates the actual equipped power");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(0,new ItemStack(Items.CAT_SPAWN_EGG));});
+ c.waitFor(m->CompanionClient.species==CompanionType.CAT,100);
+ require(CompanionClient.seconds()>0,"Changing egg does not reset the client cooldown");
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().clearContent();AccessoryAccess.of(p).setItem(0,new ItemStack(Items.VILLAGER_SPAWN_EGG));CompanionService.tick(p);CompanionService.runtime(p).equippedAt=p.tickCount-200;
+ var old=CompanionService.record(p);CompanionService.save(p,new CompanionRecord(1,old.unlocked(),old.paid(),old.purchasedAt(),0,0,0,old.acceptAllies(),old.notes()));
+ p.getInventory().setItem(0,new ItemStack(Items.EMERALD,10));p.setYRot(0);p.setXRot(0);
+ var v=EntityTypes.VILLAGER.create(p.level(),EntitySpawnReason.COMMAND);v.setNoAi(true);v.setPos(p.position().add(0,0,2));
+ var offers=new net.minecraft.world.item.trading.MerchantOffers();offers.add(new net.minecraft.world.item.trading.MerchantOffer(new net.minecraft.world.item.trading.ItemCost(Items.EMERALD,2),new ItemStack(Items.BREAD),2,1,0));v.setOffers(offers);p.level().addFreshEntity(v);CompanionService.send(p,"");
+ });
+ c.runOnClient(m->{m.player.setYRot(0);m.player.setXRot(0);});
+ c.waitFor(m->CompanionClient.species==CompanionType.VILLAGER&&CompanionClient.attunement()==0,100);c.waitTicks(15);
+ c.getInput().pressKey(com.mojang.blaze3d.platform.InputConstants.KEY_G);
+ c.waitFor(m->m.gui.screen() instanceof CompanionChoiceScreen||!CompanionClient.message.isEmpty(),100);
+ require(c.computeOnClient(m->m.gui.screen() instanceof CompanionChoiceScreen),"Quote opens; server response: "+CompanionClient.message);
+ c.takeScreenshot("sanctuary-beta019-trade-quote");
+ c.runOnClient(m->button(m.gui.screen(),"sanctuary.companion.choose").onPress(enter()));
+ c.waitFor(m->m.gui.screen()==null,100);
+ require(server.computeOnServer(s->new CompanionInventory.Plan(s.getPlayerList().getPlayers().getFirst()).count(i->i.is(Items.BREAD)))==2,"The reviewed client quote produces two paid trades");
+ require(!CompanionClient.hasChoice(),"A successful quote disappears from the menu");
+
+ }finally{c.runOnClient(m->{m.options.guiScale().set(scale);m.resizeGui();});}
+ }
+ private static Stream widgets(ContainerEventHandler container){return container.children().stream().flatMap(w->w instanceof ContainerEventHandler nested?Stream.concat(Stream.of(w),widgets(nested)):Stream.of(w));}
+ private static Button button(Screen screen,String key){return widgets(screen).filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow(()->new AssertionError("Missing button "+key));}
+ private static KeyEvent enter(){return new KeyEvent(com.mojang.blaze3d.platform.InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Companion032ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Companion032ClientChecks.java
new file mode 100644
index 0000000..2a522ed
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Companion032ClientChecks.java
@@ -0,0 +1,81 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.companion.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.core.*;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.resources.Identifier;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.effect.*;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.phys.*;
+
+/** Fresh-world real key, network and rendering checks for the v2 abilities. */
+public final class Companion032ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary refonte032 "+UUID.randomUUID());s.setSeed("42");
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);var server=world.getServer();
+ BlockPos origin=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(80);
+ for(int x=-12;x<=12;x++)for(int z=-12;z<=12;z++)for(int y=-1;y<7;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1?Blocks.STONE:Blocks.AIR).defaultBlockState());
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()+.5);p.getInventory().clearContent();p.setYRot(0);p.setXRot(55);return at;});
+ c.waitFor(m->m.player.getY()>origin.getY()-.2,200);c.runOnClient(m->{m.player.setYRot(0);m.player.setXRot(55);});c.waitTicks(20);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();equip(p,CompanionType.SHEEP);p.getInventory().setItem(0,new ItemStack(Items.WOOL.white(),2));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->CompanionClient.species==CompanionType.SHEEP&&CompanionClient.attunement()==0,200);
+ c.getInput().pressKey(InputConstants.KEY_G);c.waitFor(m->CompanionClient.seconds()>0,100);
+ require(server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();return p.getMainHandItem().getCount()==1&&CompanionZones.active(p.level()).stream().anyMatch(z->z.kind==CompanionZones.Kind.CUSHION);}),"Real G input spends one wool and creates the world cushion");
+ c.waitTicks(10);c.takeScreenshot("sanctuary-beta032-wool-cushion");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();equip(p,CompanionType.WITCH);
+ var potion=new ItemStack(Items.POTION);potion.set(DataComponents.POTION_CONTENTS,new net.minecraft.world.item.alchemy.PotionContents(Optional.empty(),Optional.empty(),List.of(new MobEffectInstance(MobEffects.SPEED,600,0)),Optional.empty()));p.setItemInHand(InteractionHand.MAIN_HAND,potion);p.setYRot(0);p.setXRot(0);p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->CompanionClient.species==CompanionType.WITCH&&CompanionClient.attunement()==0,100);c.runOnClient(m->{m.player.setYRot(0);m.player.setXRot(0);});c.waitTicks(10);
+ c.getInput().pressKey(InputConstants.KEY_G);c.waitFor(m->CompanionClient.seconds()>0,100);c.waitTicks(10);c.takeScreenshot("sanctuary-beta032-potion-mist");
+ require(server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();return p.getMainHandItem().is(Items.GLASS_BOTTLE)&&CompanionZones.active(p.level()).stream().anyMatch(z->z.kind==CompanionZones.Kind.MIST&&!z.spent);}),"A real dose becomes a visible unused mist and returns one bottle");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var mist=CompanionZones.active(p.level()).stream().filter(z->z.kind==CompanionZones.Kind.MIST).findFirst().orElseThrow();p.teleportTo(mist.center.x,mist.center.y-.8,mist.center.z);});
+ c.waitFor(m->m.player.hasEffect(MobEffects.SPEED),100);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();equip(p,CompanionType.RABBIT);p.teleportTo(origin.getX()+.5,origin.getY(),origin.getZ()+.5);});
+ c.waitFor(m->CompanionClient.species==CompanionType.RABBIT&&CompanionClient.attunement()==0,100);c.runOnClient(m->{m.player.setYRot(0);m.player.setXRot(-70);});c.waitTicks(20);
+ c.getInput().pressKey(InputConstants.KEY_G);c.waitFor(m->m.player.getY()>origin.getY()+.2,100);c.takeScreenshot("sanctuary-beta032-rabbit-hop");
+ c.waitTicks(45);require(server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();return p.getY(){m.getLanguageManager().setSelected(locale);m.options.languageCode=locale;m.reloadResourcePacks();});c.waitTicks(60);
+ c.runOnClient(m->{for(var type:CompanionType.values()){
+ String passive=type.passive().getString(),active=type.active().getString();
+ require(!passive.contains("sanctuary.companion")&&!active.contains("sanctuary.companion"),"Native translation resolves both powers: "+locale+"/"+type);
+ }});
+ }
+ c.takeScreenshot("sanctuary-beta032-native-world");
+ }
+ }
+ private static void equip(net.minecraft.server.level.ServerPlayer p,CompanionType type){
+ AccessoryAccess.of(p).setItem(0,new ItemStack(BuiltInRegistries.ITEM.getValue(Identifier.withDefaultNamespace(type.id()+"_spawn_egg"))));
+ FamiliarService.tick(p);CompanionService.tick(p);CompanionService.request(p,new CompanionPayloads.Request("operator_ready",-1));CompanionService.send(p,"");
+ }
+ private static Stream widgets(ContainerEventHandler container){return container.children().stream().flatMap(w->w instanceof ContainerEventHandler nested?Stream.concat(Stream.of(w),widgets(nested)):Stream.of(w));}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Corrections036ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Corrections036ClientChecks.java
new file mode 100644
index 0000000..73d4685
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Corrections036ClientChecks.java
@@ -0,0 +1,89 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.knowledge.BlockKnowledgeService;
+import fr.koka.sanctuary.notifications.NotificationLayout.*;
+import fr.koka.sanctuary.mixin.client.NotificationAdvancementTabAccess;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.*;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.*;
+import net.minecraft.client.gui.screens.advancements.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.network.chat.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.resources.Identifier;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.level.block.Blocks;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+public final class Corrections036ClientChecks implements FabricClientGameTest {
+ private static final Identifier DIET=Identifier.withDefaultNamespace("husbandry/balanced_diet");
+ public void runTest(ClientGameTestContext c){
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(armed.get()&&m.gui.screen() instanceof HelloWorldScreen screen){widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());button(screen,"sanctuary.progression.enter").onPress(enter());armed.set(false);}});
+ c.runOnClient(m->{require(new NotificationOptions().position==Position.TOP_RIGHT,"Fresh installs default top right");var o=NotificationClient.options();o.position=Position.TOP_RIGHT;o.seconds=3;o.pinnedAdvancements=true;o.advancements=true;o.discoveries=true;o.collections=true;});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary corrections036 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&RecipeClient.ready&&m.gui.screen()==null,1200);var server=world.getServer();
+ c.runOnClient(m->{m.player.setXRot(-90);m.options.guiScale().set(2);m.resizeGui();});c.waitTicks(30);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(String id:List.of("husbandry/root","husbandry/plant_seed")){var a=s.getAdvancements().get(Identifier.withDefaultNamespace(id));for(String k:a.value().criteria().keySet())p.getAdvancements().award(a,k);}var a=s.getAdvancements().get(DIET);p.getAdvancements().award(a,"rotten_flesh");});
+ c.waitFor(m->!NotificationClient.tracked().isEmpty(),200);c.waitTicks(130);
+ c.runOnClient(m->{require(NotificationClient.tracked().isEmpty(),"A Balanced Diet expires after rotten flesh without becoming an automatic permanent tracker");require(NotificationClient.messages().isEmpty(),"Messages expire as well");});
+ openDiet(c);clickDiet(c,false);c.runOnClient(m->require(NotificationClient.isPinned(DIET),"Actual native advancement click pins objective"));c.takeScreenshot("sanctuary-beta036-pin-advancement");
+ clickDiet(c,true);c.runOnClient(m->require(NotificationClient.isPinned(DIET),"Dragging tree never toggles tracking"));
+ openDiet(c);clickDiet(c,false);c.runOnClient(m->require(!NotificationClient.isPinned(DIET),"Second click unpins"));clickDiet(c,false);
+ c.runOnClient(m->m.gui.setScreen(null));c.waitTicks(125);c.runOnClient(m->require(NotificationClient.isPinned(DIET)&&NotificationClient.tracked().size()==1,"Explicit follow survives transient expiry"));
+ server.runOnServer(s->BlockKnowledgeService.recordSeen(s.getPlayerList().getPlayers().getFirst(),Blocks.AMETHYST_BLOCK));c.waitFor(m->!NotificationClient.messages().isEmpty(),100);c.takeScreenshot("sanctuary-beta036-top-right");
+ c.runOnClient(m->{m.gui.toastManager().clear();m.debugEntries.setOverlayVisible(true);});c.waitTicks(5);c.runOnClient(m->{require(!NotificationObstacles.debug.isEmpty(),"Native F3 text captured");noOverlap();});c.takeScreenshot("sanctuary-beta036-f3");
+ for(int scale:List.of(1,2,3,4))for(Position position:Position.values()){
+ c.runOnClient(m->{m.options.guiScale().set(scale);m.resizeGui();NotificationClient.options().position=position;});c.waitTicks(2);c.runOnClient(m->noOverlap());
+ }
+ c.runOnClient(m->{m.options.guiScale().set(3);m.resizeGui();NotificationClient.options().position=Position.TOP_RIGHT;m.options.debugGuiScale().set(1);});c.waitTicks(3);c.runOnClient(m->noOverlap());c.takeScreenshot("sanctuary-beta036-f3-separate-scale");
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.options.debugGuiScale().set(-1);m.debugEntries.setOverlayVisible(false);});
+ server.runOnServer(s->{var b=s.getCustomBossEvents().create(s.overworld().getRandom(),Identifier.withDefaultNamespace("sanctuary_test_boss"),Component.literal("HUD layout test"));b.addPlayer(s.getPlayerList().getPlayers().getFirst());b.setValue(50);});
+ c.runOnClient(m->NotificationClient.options().position=Position.TOP_CENTER);c.waitTicks(5);c.runOnClient(m->{var b=NotificationObstacles.lastLayout;require(b.y()>20,"Top center reserves native boss bar");});c.takeScreenshot("sanctuary-beta036-boss");
+ c.runOnClient(m->NotificationClient.options().position=Position.BOTTOM_CENTER);c.waitTicks(3);c.takeScreenshot("sanctuary-beta036-bottom-center");
+ server.runOnServer(s->{var b=s.getCustomBossEvents().get(Identifier.withDefaultNamespace("sanctuary_test_boss"));b.removeAllPlayers();s.getCustomBossEvents().remove(b);});
+ for(String locale:List.of("fr_fr","en_us")){
+ var reload=c.computeOnClient(m->{m.getLanguageManager().setSelected(locale);m.options.languageCode=locale;return m.reloadResourcePacks();});c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();
+ c.runOnClient(m->{var o=NotificationClient.options();o.position=Position.TOP_RIGHT;o.save();m.gui.setScreen(new NotificationOptionsScreen(null));require(NotificationOptions.load().position==Position.TOP_RIGHT,"Anchor saved");require(NotificationOptions.load().trackedByWorld.values().stream().anyMatch(ids->ids.contains(DIET.toString())),"Explicit tracks saved per world");
+ require(NotificationClient.label("position").getString().equals("Position"),"Actual translated position label");});c.waitTicks(3);c.takeScreenshot("sanctuary-beta036-options-"+locale);c.runOnClient(m->m.gui.setScreen(null));
+ }
+ server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().hurtServer(s.overworld(),s.overworld().damageSources().fellOutOfWorld(),Float.MAX_VALUE));
+ c.waitFor(m->m.gui.screen() instanceof DeathScreen,200);c.waitTicks(45);c.runOnClient(m->button(m.gui.screen(),"deathScreen.respawn").onPress(enter()));c.waitFor(m->m.player!=null&&m.player.isAlive()&&m.gui.screen()==null,300);c.waitTicks(30);
+ c.runOnClient(m->{require(NotificationClient.isPinned(DIET)&&NotificationClient.tracked().size()==1,"Selected follow survives respawn");require(NotificationClient.messages().isEmpty(),"Respawn does not replay old notifications");});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var a=s.getAdvancements().get(DIET);for(String k:a.value().criteria().keySet())p.getAdvancements().award(a,k);});c.waitFor(m->!NotificationClient.isPinned(DIET),200);c.runOnClient(m->require(NotificationClient.tracked().isEmpty(),"Completed objective automatically leaves tracker"));
+ gliding(c,server);
+ System.out.println("HUD036 client passed: rotten-flesh expiry; real click/unclick/drag follow; persistent per-world selection; respawn; completion; F3 collision at 36 layouts + independent scale; boss/hotbar; FR/EN; live carried-parrot descent and landing.");
+ }finally{c.runOnClient(m->{m.debugEntries.setOverlayVisible(false);var o=NotificationClient.options();o.position=Position.TOP_RIGHT;o.seconds=5;o.save();});}
+ }
+ private static void gliding(ClientGameTestContext c,TestServerContext server){
+ var landing=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var ground=p.blockPosition().above(3);for(int x=-3;x<=3;x++)for(int z=-3;z<=3;z++){p.level().setBlockAndUpdate(ground.offset(x,0,z),Blocks.STONE.defaultBlockState());for(int y=1;y<=15;y++)p.level().setBlockAndUpdate(ground.offset(x,y,z),Blocks.AIR.defaultBlockState());}p.teleportTo(ground.getX()+.5,ground.getY()+1,ground.getZ()+.5);p.setHealth(p.getMaxHealth());return ground;});
+ c.waitFor(m->m.player.onGround()&&m.player.getY()>=landing.getY()+1,200);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var bird=EntityTypes.PARROT.create(p.level(),EntitySpawnReason.COMMAND);bird.setPos(p.position().add(0,0,1));p.level().addFreshEntity(bird);require(CarryService.interact(p,bird),"Native parrot carry");p.teleportTo(p.getX(),landing.getY()+10,p.getZ());p.positionRider(bird);});
+ c.waitFor(m->CarryGlide.active(m.player)&&m.player.getY()>landing.getY()+4,200);c.waitTicks(12);
+ c.runOnClient(m->{require(m.player.getDeltaMovement().y>=-.121&&m.player.getDeltaMovement().y<0,"Native LocalPlayer physics glides");require(m.player.fallDistance<.2,"No stored fall damage");});c.takeScreenshot("sanctuary-beta036-parrot-gliding");
+ c.waitFor(m->m.player.onGround(),300);c.runOnClient(m->require(m.player.getHealth()==m.player.getMaxHealth(),"Real landing survives without fall damage"));
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var bird=p.getFirstPassenger();CarryService.release(bird,true);bird.discard();});c.waitFor(m->!CarryGlide.supported(m.player),100);
+ }
+ private static void noOverlap(){var b=NotificationObstacles.lastLayout;for(var obstacle:NotificationObstacles.boxes())require(!b.intersects(obstacle),"Notification overlap "+b+" / "+obstacle);}
+ private static void openDiet(ClientGameTestContext c){
+ c.runOnClient(m->{var adv=m.getConnection().getAdvancements();m.gui.setScreen(new AdvancementsScreen(adv));var root=adv.progress().keySet().stream().filter(a->a.id().toString().equals("minecraft:husbandry/root")).findFirst().orElseThrow();adv.setSelectedTab(root,true);});c.waitTicks(2);
+ c.runOnClient(m->{var tab=tab((AdvancementsScreen)m.gui.screen());var access=(NotificationAdvancementTabAccess)tab;var w=access.sanctuary$widgets().values().stream().filter(a->a.getAdvancement().id().equals(DIET)).findFirst().orElseThrow();tab.scroll(100-w.getX()-access.sanctuary$scrollX(),45-w.getY()-access.sanctuary$scrollY());});c.waitTicks(2);
+ }
+ private static void clickDiet(ClientGameTestContext c,boolean drag){c.runOnClient(m->{var screen=(AdvancementsScreen)m.gui.screen();var tab=(NotificationAdvancementTabAccess)tab(screen);var w=tab.sanctuary$widgets().values().stream().filter(a->a.getAdvancement().id().equals(DIET)).findFirst().orElseThrow();int x=(int)Math.floor(tab.sanctuary$scrollX())+w.getX()+12,y=(int)Math.floor(tab.sanctuary$scrollY())+w.getY()+12;require(x>0&&x<234&&y>0&&y<113,"Advancement icon reachable in native viewport");var e=new MouseButtonEvent(number(screen,"leftPos")+9+x,number(screen,"topPos")+18+y,new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,0));screen.mouseClicked(e,false);if(drag)screen.mouseDragged(e,8,0);screen.mouseReleased(e);});c.waitTicks(2);}
+ private static AdvancementTab tab(AdvancementsScreen screen){try{var f=AdvancementsScreen.class.getDeclaredField("selectedTab");f.setAccessible(true);return (AdvancementTab)f.get(screen);}catch(Exception e){throw new AssertionError(e);}}
+ private static int number(AdvancementsScreen screen,String key){try{var f=AdvancementsScreen.class.getDeclaredField(key);f.setAccessible(true);return f.getInt(screen);}catch(Exception e){throw new AssertionError(e);}}
+ private static Stream widgets(ContainerEventHandler c){return c.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static Button button(Screen s,String key){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow(()->new AssertionError(key));}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean ok,String message){if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Cycle011ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Cycle011ClientChecks.java
new file mode 100644
index 0000000..95ea31a
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Cycle011ClientChecks.java
@@ -0,0 +1,76 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+
+/** Creates only an ignored development world; validates confirmation through real packets. */
+public final class Cycle011ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m -> {
+ if (armed.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
+ screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ press(screen,"sanctuary.progression.enter");armed.set(false);
+ }
+ });
+ int scale=c.computeOnClient(m -> m.options.guiScale().get());
+ try (var world=c.worldBuilder().adjustSettings(s -> {
+ s.setName("Sanctuary beta011 cycles "+UUID.randomUUID());
+ s.setWorldType(s.getNormalPresetList().stream().filter(e -> e.preset()!=null && e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
+ }).create()) {
+ world.getConnection().waitForChunksDownload(2400);
+ c.waitFor(m -> m.player!=null && ProgressionClient.active() && CycleClient.view!=null && m.gui.screen()==null,1200);
+ for(int gui:new int[]{2,0}) {
+ c.runOnClient(m -> {m.options.guiScale().set(gui);m.resizeGui();CycleClient.open(new net.minecraft.client.gui.screens.PauseScreen(true),"cycle");});
+ c.waitTick();
+ c.runOnClient(m -> require(!button(m.gui.screen(),"sanctuary.cycle.ngp").active,"New character cannot begin NGP"));
+ c.takeScreenshot("sanctuary-beta011-cycle-locked-gui-"+gui);
+ }
+ world.getServer().runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();var progress=ProgressRecord.INITIAL.buy("names",4).buy("atlas",4);
+ for (String skill:ProgressRecord.SKILLS) while(progress.rank(skill) ProgressionClient.progress.complete(),200);
+ c.runOnClient(m -> {press(m.gui.screen(),"sanctuary.cycle.ngp");require(CycleClient.view.prestige()==0,"Opening preview makes no server change");});
+ c.takeScreenshot("sanctuary-beta011-ngp-confirmation");
+ c.runOnClient(m -> {press(m.gui.screen(),"sanctuary.cycle.cancel");require(CycleClient.view.prestige()==0,"Cancel keeps cycle");press(m.gui.screen(),"sanctuary.cycle.ngp");press(m.gui.screen(),"sanctuary.cycle.confirm");});
+ c.waitFor(m -> CycleClient.view.prestige()==1 && ProgressionClient.progress.cycle()==1 && m.player.experienceLevel==0,200);
+ c.runOnClient(m -> require(ProgressionClient.progress.names() && ProgressionClient.progress.atlas() && CycleClient.view.charges()==1,"Server confirmation preserves aptitudes and grants one charge"));
+ for (int i=0;i<6;i++) c.waitTick();
+ world.getServer().runOnServer(server -> {var p=server.getPlayerList().getPlayers().getFirst();p.giveExperienceLevels(10);ProgressionService.send(p,"");});
+ c.waitFor(m->m.player.experienceLevel==10,100);
+ c.runOnClient(m -> {CycleClient.open(new net.minecraft.client.gui.screens.PauseScreen(true),"faction");});c.waitTick();
+ c.runOnClient(m -> {
+ var screen=m.gui.screen();screen.children().stream().filter(EditBox.class::isInstance).map(EditBox.class::cast).findFirst().orElseThrow().setValue("Les Veilleurs");
+ press(screen,"sanctuary.cycle.create");press(screen,"sanctuary.cycle.confirm");
+ });
+ c.waitFor(m -> CycleClient.view.faction()!=null && CycleClient.view.charges()==1 && m.player.experienceLevel==0,200);
+ c.takeScreenshot("sanctuary-beta011-faction-created");
+ for(int gui:new int[]{2,0}) {
+ c.runOnClient(m -> {m.options.guiScale().set(gui);m.resizeGui();CycleClient.open(new net.minecraft.client.gui.screens.PauseScreen(true),"log");});
+ c.waitTick();c.takeScreenshot("sanctuary-beta011-log-gui-"+gui);
+ }
+ } finally {armed.set(false);c.runOnClient(m -> {m.options.guiScale().set(scale);m.resizeGui();});}
+ }
+ private static KeyEvent enter() {return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static Button button(Screen screen,String key) {
+ return screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b -> b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals(key)).findFirst().orElseThrow();
+ }
+ private static void press(Screen screen,String key) {var button=button(screen,key);require(button.active,"Active button: "+key);button.onPress(enter());}
+ private static void require(boolean ok,String message) {if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Duel054Checks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Duel054Checks.java
new file mode 100644
index 0000000..8399ae7
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Duel054Checks.java
@@ -0,0 +1,142 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.authlib.GameProfile;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.familiar.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.companion.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
+import net.minecraft.network.Connection;
+import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.game.*;
+import net.minecraft.server.level.*;
+import net.minecraft.server.network.*;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+
+/** Two actual ServerPlayer entities, native AI/projectiles, persistent escrow and client screens. */
+public final class Duel054Checks {
+ private static void check(boolean value,String message){Combat054ClientChecks.require(value,message);}
+ // The context API type is obtained from the public Fabric test world rather than a dedicated server.
+ public static void run(ClientGameTestContext c,net.fabricmc.fabric.api.client.gametest.v1.context.TestServerContext server){
+ var remote=new java.util.concurrent.atomic.AtomicReference();
+ ServerTickEvents.END_SERVER_TICK.register(s->{var p=remote.get();if(p!=null&&p.level().getServer()==s){p.tickCount++;CompanionService.tick(p);FamiliarService.tick(p);FamiliarBattle.tick(p);}});
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition();
+ p.level().getGameRules().set(net.minecraft.world.level.gamerules.GameRules.PVP,false,s);
+ for(int x=-10;x<=10;x++)for(int z=-10;z<=10;z++)for(int y=-1;y<8;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1?Blocks.STONE:Blocks.AIR).defaultBlockState());
+ var profile=new GameProfile(UUID.randomUUID(),"Rival054");var other=new ServerPlayer(s,p.level(),profile,ClientInformation.createDefault());
+ var connection=new Connection(PacketFlow.SERVERBOUND);new io.netty.channel.embedded.EmbeddedChannel(connection);
+ other.connection=new ServerGamePacketListenerImpl(s,connection,other,CommonListenerCookie.createInitial(profile,false));
+ other.setPos(p.position().add(0,0,5));other.setYRot(180);other.setYHeadRot(180);
+ other.setAttached(ProgressionService.RECORD,ProgressRecord.INITIAL.json());ProgressEffects.apply(other,ProgressRecord.INITIAL);other.setHealth(6);
+ other.connection.handleAcceptPlayerLoad(new ServerboundPlayerLoadedPacket());p.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(other)));p.level().addNewPlayer(other);
+ remote.set(other);p.getInventory().clearContent();other.getInventory().clearContent();fresh(p,"wolf");fresh(other,"wolf");
+ p.getInventory().setItem(0,new ItemStack(Items.EMERALD,3));other.getInventory().setItem(0,new ItemStack(Items.DIAMOND,2));
+ });
+ c.waitTicks(20);
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var q=remote.get();
+ var invited=FamiliarDuels.invite(p,q);check(invited.equals("duel.sent"),"Invitation between available players: "+invited+" ready="+FamiliarDuels.ready(p)+"/"+FamiliarDuels.ready(q)+" combat="+FamiliarBattle.fighting(p)+"/"+FamiliarBattle.fighting(q)+" distance="+p.distanceTo(q)+" sight="+p.hasLineOfSight(q));var m=FamiliarDuels.match(p);
+ check(Experience054Checks.transfer(p,"deposit_all").equals("xp.locked"),"XP transfers lock from the invitation, before stake consent");
+ request(q,"accept",-1,0);request(p,"stake",0,3);request(q,"stake",0,2);
+ request(p,"ready",-1,0);check(m.ready[0]&&!m.ready[1]&&!m.running(),"One confirmation cannot start combat");
+ int old=m.revision;request(q,"stake",0,1);check(!m.ready[0]&&!m.ready[1],"Changing a stake invalidates both confirmations");
+ p.tickCount+=4;FamiliarDuels.request(p,m.id.toString(),old,"ready",-1,0);check(!m.ready[0],"Stale consent cannot accept a changed offer");
+ request(q,"stake",9,1);check(m.stakes[1].getCount()==1,"Locked inventory slots cannot be wagered");
+ check(p.getInventory().getItem(0).getCount()==3&&q.getInventory().getItem(0).getCount()==2,"Offers do not remove items before mutual agreement");
+ request(q,"stake",0,2);FamiliarDuels.open(p);
+ });
+ c.waitFor(m->m.gui.screen() instanceof FamiliarDuelScreen,100);
+ for(String locale:List.of("fr_fr","en_us")){
+ c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new FamiliarDuelScreen());});
+ c.waitTicks(4);c.takeScreenshot("sanctuary-beta054-duel-stakes-"+locale);
+ }
+ c.runOnClient(m->{
+ var slot=widgets(m.gui.screen()).filter(net.minecraft.client.gui.components.AbstractWidget.class::isInstance).map(net.minecraft.client.gui.components.AbstractWidget.class::cast)
+ .filter(w->w.getWidth()==20&&w.getHeight()==20)
+ .filter(w->w.getMessage().getContents() instanceof net.minecraft.network.chat.contents.TranslatableContents t&&t.getKey().equals("sanctuary.familiar.duel.own_stake")).findFirst().orElseThrow();
+ boolean clicked=m.gui.screen().mouseClicked(new net.minecraft.client.input.MouseButtonEvent(slot.getX()+10,slot.getY()+10,new net.minecraft.client.input.MouseButtonInfo(com.mojang.blaze3d.platform.InputConstants.MOUSE_BUTTON_LEFT,0)),false);
+ System.out.println("DUEL054_CLICK "+clicked+" x="+slot.getX()+" y="+slot.getY()+" phase="+FamiliarClient.duel.phase()+" revision="+FamiliarClient.duel.revision());
+ });
+ c.waitFor(m->FamiliarClient.duel!=null&&FamiliarClient.duel.ownStake().isEmpty(),100);
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var q=remote.get();check(FamiliarDuels.match(p).stakes[0].isEmpty(),"Native slot click reaches the authoritative offer");request(p,"stake",0,3);request(p,"ready",-1,0);request(q,"ready",-1,0);var m=FamiliarDuels.match(p);
+ check(m!=null&&m.phase==FamiliarDuels.Phase.COUNTDOWN,"Two confirmations establish a durable escrow");
+ check(p.getInventory().getItem(0).isEmpty()&&q.getInventory().getItem(0).isEmpty(),"Both real stakes removed exactly once");
+ check(!FamiliarBattle.enemy(p,FamiliarService.active(q)),"Countdown blocks attacks");m.until=FamiliarBattle.now();
+ });
+ c.runOnClient(m->m.gui.setScreen(null));c.waitTicks(25);
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var q=remote.get();var a=FamiliarService.active(p);var b=FamiliarService.active(q);
+ check(FamiliarBattle.enemy(p,b)&&!FamiliarBattle.enemy(p,q),"The agreement targets the rival familiar, never its player");
+ check(!CompanionWorld.enemy(p,q),"The ordinary PvP prohibition remains in effect");
+ check(!FamiliarTechniques.ally(q,a),"Opponent cannot supply healing assistance");
+ check(!b.hurtServer(p.level(),b.damageSources().playerAttack(p),3),"Players cannot directly damage duelling familiars");
+ a.setPos(p.position().add(1,0,2));b.setPos(p.position().add(1,0,3.5));FamiliarBattle.engage(p,b,false);FamiliarBattle.engage(q,a,false);
+ });
+ c.waitTicks(80);
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var q=remote.get();var m=FamiliarDuels.match(p);check(m!=null&&m.damage[0]+m.damage[1]>0,"Autonomous duel produces measured physical hits");
+ // A familiar knockout is the actual victory trigger; no /win command or player damage.
+ var a=FamiliarService.active(p);var b=FamiliarService.active(q);var cast=new FamiliarTechniques.Cast(a,FamiliarBattle.profile(p).initiative(),false,b,b.position());
+ FamiliarBattle.state(p).cast=null;FamiliarBattle.state(q).cast=null;
+ for(int i=0;i<12&&FamiliarDuels.match(p)!=null;i++)FamiliarTechniques.hit(cast,b,4);
+ check(FamiliarDuels.match(p)==null&&FamiliarBattle.bond(q).health()==0,"K.O. ends the match");
+ check(count(p,Items.EMERALD)==3&&count(p,Items.DIAMOND)==2&&count(q,Items.DIAMOND)==0,"Winner receives both exact stakes");
+ FamiliarDuels.ledger(s).claim(p);check(count(p,Items.DIAMOND)==2,"Repeated collection cannot duplicate a payout");
+ check(!FamiliarTechniques.hit(cast,b,4),"An old duel cast is invalid after settlement");
+ check(!FamiliarBattle.bond(p).memories().containsKey("signature"),"Duels do not farm familiar mastery");
+ System.out.println("DUEL054_MATCH_PASS: consent, changed stakes, native hits, isolation, knockout and exact payout");
+ });
+ c.waitTicks(25);
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var q=remote.get();fresh(p,"skeleton");fresh(q,"cow");
+ p.getInventory().setItem(1,new ItemStack(Items.ARROW,12));check(FamiliarDuels.invite(p,q).equals("duel.sent"),"A rematch can be offered");
+ request(q,"accept",-1,0);request(p,"ready",-1,0);request(q,"ready",-1,0);var m=FamiliarDuels.match(p);check(m.running(),"Duels may start without a wager");m.until=FamiliarBattle.now();FamiliarDuels.tick(s);
+ var a=FamiliarService.active(p);var b=FamiliarService.active(q);a.setPos(p.position().add(1,0,2));b.setPos(p.position().add(1,0,5));b.setNoAi(true);
+ var cast=new FamiliarTechniques.Cast(a,FamiliarBattle.profile(p).signature(),false,b,b.position());cast.supply=new ItemStack(Items.ARROW);FamiliarProjectiles.launch(cast);
+ var shots=p.level().getEntitiesOfClass(net.minecraft.world.entity.projectile.Projectile.class,a.getBoundingBox().inflate(2),FamiliarProjectiles::managed);check(!shots.isEmpty()&&shots.stream().noneMatch(Entity::shouldBeSaved),"Managed native projectiles cannot resurrect after a world reload");
+ for(var shot:shots)FamiliarProjectiles.impact(shot,new net.minecraft.world.phys.EntityHitResult(b));
+ check(b.getHealth(){
+ var p=s.getPlayerList().getPlayers().getFirst();var q=remote.get();var ledger=FamiliarDuels.ledger(s);
+ p.getInventory().clearContent();q.getInventory().clearContent();fresh(p,"skeleton");fresh(q,"blaze");p.getInventory().setItem(0,new ItemStack(Items.EMERALD,3));q.getInventory().setItem(0,new ItemStack(Items.DIAMOND,2));
+ var id=UUID.randomUUID();check(ledger.reserve(id,p,q,new ItemStack(Items.EMERALD,3),new ItemStack(Items.DIAMOND,2)),"Independent escrow established");
+ for(int slot:CompanionInventory.slots(p,true))p.getInventory().setItem(slot,new ItemStack(Items.STONE,64));ledger.finish(id,0);ledger.claim(p);ledger.claim(q);
+ check(ledger.pending(p)&&count(p,Items.DIAMOND)==0,"Full inventory retains payout without dropping it into the world");
+ for(int slot:CompanionInventory.slots(p,true))p.getInventory().setItem(slot,ItemStack.EMPTY);ledger.claim(p);check(count(p,Items.DIAMOND)==2&&count(p,Items.EMERALD)==3&&!ledger.pending(p),"Freed space collects the complete pending payout");
+ q.getInventory().setItem(0,new ItemStack(Items.DIAMOND,2));var interrupted=UUID.randomUUID();check(ledger.reserve(interrupted,p,q,new ItemStack(Items.EMERALD,3),new ItemStack(Items.DIAMOND,2)),"Interrupted escrow fixture");
+ try{
+ var reloaded=new FamiliarStakes(s);reloaded.claim(p);reloaded.claim(q);
+ check(count(p,Items.EMERALD)==3&&count(q,Items.DIAMOND)==2,"Journal reload refunds both stakes after interruption");
+ reloaded.claim(p);reloaded.claim(q);check(count(q,Items.DIAMOND)==2,"Recovery receipts prevent repeated refunds");
+ var named=new ItemStack(Items.CHEST);named.set(net.minecraft.core.component.DataComponents.CUSTOM_NAME,net.minecraft.network.chat.Component.literal("Archive du duel"));
+ named.set(net.minecraft.core.component.DataComponents.CONTAINER,net.minecraft.world.item.component.ItemContainerContents.fromItems(List.of(new ItemStack(Items.DIAMOND,7))));
+ p.getInventory().setItem(3,named.copy());var custom=UUID.randomUUID();check(reloaded.reserve(custom,p,q,named,ItemStack.EMPTY),"Named container can be wagered");reloaded.finish(custom,0);reloaded.claim(p);reloaded.claim(q);
+ check(CompanionInventory.slots(p,true).stream().anyMatch(slot->ItemStack.matches(named,p.getInventory().getItem(slot))),"All native item components and nested contents survive escrow");
+ }catch(Exception e){throw new AssertionError(e);}
+ System.out.println("DUEL054_ESCROW_PASS: durable receipts, full inventory and interrupted match recovery");
+ // Reload uses a fresh journal owner: discard this fixture before the normal shutdown hook writes.
+ FamiliarDuels.start(s);
+ });
+ c.runOnClient(m->m.gui.setScreen(new FamiliarOrdersScreen()));c.waitTicks(3);c.takeScreenshot("sanctuary-beta054-orders");c.runOnClient(m->m.gui.setScreen(null));
+ server.runOnServer(s->{var q=remote.getAndSet(null);FamiliarDuels.disconnect(q);FamiliarBattle.release(q);var pet=FamiliarService.active(q);if(pet!=null)pet.discard();q.discard();});
+ }
+ private static void request(ServerPlayer p,String action,int slot,int count){var m=FamiliarDuels.match(p);p.tickCount+=4;FamiliarDuels.request(p,m.id.toString(),m.revision,action,slot,count);}
+ private static void fresh(ServerPlayer p,String species){FamiliarBattle.cancel(p);p.setAttached(FamiliarBattle.COMBAT_LOCK,0L);CompanionService.save(p,CompanionService.record(p).cooldown(0));Combat054ClientChecks.equip(p,species);p.tickCount=((p.tickCount/10)+1)*10;FamiliarService.tick(p);}
+ private static int count(ServerPlayer p,Item item){return CompanionInventory.slots(p,true).stream().mapToInt(i->p.getInventory().getItem(i).is(item)?p.getInventory().getItem(i).getCount():0).sum();}
+ private static java.util.stream.Stream widgets(net.minecraft.client.gui.components.events.GuiEventListener root){
+ return java.util.stream.Stream.concat(java.util.stream.Stream.of(root),root instanceof net.minecraft.client.gui.components.events.ContainerEventHandler container?container.children().stream().flatMap(Duel054Checks::widgets):java.util.stream.Stream.empty());
+ }
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Dynamic030ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Dynamic030ClientChecks.java
new file mode 100644
index 0000000..67eedfd
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Dynamic030ClientChecks.java
@@ -0,0 +1,140 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.core.BlockPos;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.nbt.CompoundTag;
+import net.minecraft.network.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.game.*;
+import net.minecraft.server.level.*;
+import net.minecraft.server.network.*;
+import net.minecraft.util.LightCoordsUtil;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.item.ItemEntity;
+import net.minecraft.world.item.*;
+import net.minecraft.world.item.component.*;
+import net.minecraft.world.level.LightLayer;
+import net.minecraft.world.level.block.*;
+
+/** Fresh enclosed room: native network synchronization, meshing and rendered evidence. */
+public final class Dynamic030ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary beta030 lighting "+UUID.randomUUID());s.setSeed("42");
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setGameMode(WorldCreationUiState.SelectedGameMode.CREATIVE);}).create()) {
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&m.gui.screen()==null,1200);
+ var server=world.getServer();
+ BlockPos origin=server.computeOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(80);
+ for(int x=-12;x<=12;x++)for(int z=-12;z<=12;z++)for(int y=-1;y<=7;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),
+ (y==-1||y==7||Math.abs(x)==12||Math.abs(z)==12?Blocks.STONE:Blocks.AIR).defaultBlockState());
+ for(int x=-4;x<=-2;x++)for(int z=3;z<=5;z++)p.level().setBlockAndUpdate(at.offset(x,0,z),Blocks.WATER.defaultBlockState());
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()-5.5);p.setYRot(0);p.setXRot(12);
+ p.getInventory().clearContent();p.setItemInHand(InteractionHand.OFF_HAND,ItemStack.EMPTY);head(p,ItemStack.EMPTY);p.inventoryMenu.broadcastChanges();return at;
+ });
+ c.waitFor(m->m.player.getY()>=origin.getY()-.1,200);
+ c.runOnClient(m->{m.getWindow().setWindowed(1280,800);m.options.guiScale().set(2);m.resizeGui();m.player.setYRot(0);m.player.setXRot(12);});
+ c.waitTicks(100);
+ c.runOnClient(m->{require(DynamicLightClient.snapshot(m.level).size()==0,"Empty room has no dynamic sources");
+ require(m.level.getBrightness(LightLayer.BLOCK,origin.above())==0,"No real block light in test room");});
+ c.takeScreenshot("sanctuary-beta030-room-dark");
+ Dynamic030RenderProbe.active=true;
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.TORCH));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->DynamicLightClient.snapshot(m.level).size()==1,200);c.waitTicks(50);
+ c.runOnClient(m->{var pos=m.player.blockPosition().above();require((LightCoordsUtil.getLightCoords(m.level,pos)&255)>150,"Held torch reaches native light lookup");
+ require(m.level.getBrightness(LightLayer.BLOCK,pos)==0,"World light engine remains untouched");
+ require((m.getEntityRenderDispatcher().getRenderer(m.player).getPackedLightCoords(m.player,1)&255)>150,"Player and held-item extraction receive dynamic light");
+ require(Dynamic030RenderProbe.litTerrainSamples.get()>0,"Actual terrain compilation consumes dynamic light");
+ System.out.println("LIGHT-01 native meshing: "+Dynamic030RenderProbe.litTerrainSamples.get()+" lit samples");});
+ c.takeScreenshot("sanctuary-beta030-hand-torch");
+ c.waitFor(m->DynamicLightClient.pendingSections()==0,200);
+ long samples=Dynamic030RenderProbe.litTerrainSamples.get();
+ for(int i=0;i<20;i++){c.runOnClient(m->m.player.setYRot(m.player.getYRot()+18));c.waitTick();}
+ c.runOnClient(m->require(DynamicLightClient.pendingSections()==0,"Looking around does not invalidate the source"));
+ c.waitTicks(30);System.out.println("LIGHT-01 settled terrain samples before/after camera turn: "+samples+" / "+Dynamic030RenderProbe.litTerrainSamples.get());
+ double oldX=c.computeOnClient(m->m.player.getX()),oldY=c.computeOnClient(m->m.player.getEyeY()),oldZ=c.computeOnClient(m->m.player.getZ());
+ server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().teleportTo(origin.getX()+9.5,origin.getY(),origin.getZ()+9.5));
+ c.waitFor(m->m.player.getZ()>origin.getZ()+9,100);c.waitTicks(40);
+ c.runOnClient(m->{var light=DynamicLightClient.snapshot(m.level);
+ require(light.apply(0,oldX,oldY,oldZ)==0,"Walking away removes the old pool of light");
+ require((light.apply(0,m.player.getX(),m.player.getEyeY(),m.player.getZ())&255)>200,"Light follows the moving player");});
+ server.runOnServer(s->s.getPlayerList().getPlayers().getFirst().teleportTo(oldX,origin.getY(),oldZ));
+ c.waitFor(m->m.player.getZ(){var p=s.getPlayerList().getPlayers().getFirst();p.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);p.setItemInHand(InteractionHand.OFF_HAND,new ItemStack(Items.SOUL_LANTERN));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->m.player.getOffhandItem().is(Items.SOUL_LANTERN),100);c.waitTicks(40);
+ c.runOnClient(m->require((DynamicLightClient.snapshot(m.level).apply(0,m.player.getX(),m.player.getEyeY(),m.player.getZ())&255)>=150,"Offhand light follows native soul intensity"));
+ c.takeScreenshot("sanctuary-beta030-offhand");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setItemInHand(InteractionHand.OFF_HAND,ItemStack.EMPTY);head(p,new ItemStack(Items.GLOWSTONE));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->HeadCosmeticService.appearance(m.player).is(Items.GLOWSTONE),100);c.waitTicks(40);
+ c.runOnClient(m->require((DynamicLightClient.snapshot(m.level).apply(0,m.player.getX(),m.player.getEyeY(),m.player.getZ())&255)>220,"Head block supplies its own light"));
+ c.takeScreenshot("sanctuary-beta030-head-glowstone");
+ server.runOnServer(s->head(s.getPlayerList().getPlayers().getFirst(),cake(true)));c.waitFor(m->HeadCosmeticData.lit(HeadCosmeticService.appearance(m.player)),100);c.waitTicks(40);
+ c.runOnClient(m->require((DynamicLightClient.snapshot(m.level).apply(0,m.player.getX(),m.player.getEyeY(),m.player.getZ())&255)>25,"Lit cake produces its smaller pool of light"));
+ c.takeScreenshot("sanctuary-beta030-head-candle-cake");
+ server.runOnServer(s->head(s.getPlayerList().getPlayers().getFirst(),cake(false)));c.waitFor(m->DynamicLightClient.snapshot(m.level).size()==0,100);c.waitTicks(40);
+ c.runOnClient(m->require((LightCoordsUtil.getLightCoords(m.level,m.player.blockPosition().above())&255)==0,"Extinguishing removes dynamic light"));
+ c.takeScreenshot("sanctuary-beta030-cake-extinguished");
+ UUID remote=server.computeOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();head(p,ItemStack.EMPTY);
+ var profile=new GameProfile(UUID.randomUUID(),"Luciole");var other=new ServerPlayer(s,p.level(),profile,ClientInformation.createDefault());
+ var connection=new Connection(PacketFlow.SERVERBOUND);new io.netty.channel.embedded.EmbeddedChannel(connection);
+ other.connection=new ServerGamePacketListenerImpl(s,connection,other,CommonListenerCookie.createInitial(profile,false));
+ other.setPos(origin.getX()+3.5,origin.getY(),origin.getZ()+3.5);other.setYRot(180);other.setYHeadRot(180);other.setNoGravity(true);
+ other.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.LANTERN));other.connection.handleAcceptPlayerLoad(new ServerboundPlayerLoadedPacket());
+ p.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(other)));p.level().addNewPlayer(other);return other.getUUID();
+ });
+ c.waitFor(m->m.level.getPlayerByUUID(remote)!=null&&DynamicLightClient.snapshot(m.level).size()==1,200);c.waitTicks(40);
+ c.runOnClient(m->{var other=m.level.getPlayerByUUID(remote);require((LightCoordsUtil.getLightCoords(m.level,other.blockPosition().above())&255)>180,"Other player's lantern lights terrain");});
+ c.takeScreenshot("sanctuary-beta030-other-player-lantern");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var other=(ServerPlayer)p.level().getPlayerByUUID(remote);other.setItemInHand(InteractionHand.MAIN_HAND,ItemStack.EMPTY);
+ // The synthetic remote has no live connection ticking its inventory; deliver its normal equipment update.
+ p.connection.send(new ClientboundSetEquipmentPacket(other.getId(),List.of(com.mojang.datafixers.util.Pair.of(net.minecraft.world.entity.EquipmentSlot.MAINHAND,ItemStack.EMPTY))));
+ for(int i=0;i<200;i++){var item=new ItemEntity(p.level(),origin.getX()+.5,origin.getY()+.2,origin.getZ()+2.5,new ItemStack(Items.TORCH));
+ item.setDeltaMovement(net.minecraft.world.phys.Vec3.ZERO);item.setNoGravity(true);item.setDefaultPickUpDelay();p.level().addFreshEntity(item);}});
+ c.waitFor(m->m.level.getPlayerByUUID(remote).getMainHandItem().isEmpty(),200);
+ c.waitTicks(30);
+ c.runOnClient(m->System.out.println("LIGHT-01 dropped field: "+DynamicLightClient.snapshot(m.level).size()+" sources; "+m.level.getEntitiesOfClass(ItemEntity.class,m.player.getBoundingBox().inflate(30)).size()+" item entities"));
+ c.waitFor(m->DynamicLightClient.snapshot(m.level).size()==1,200);c.waitTicks(20);
+ c.runOnClient(m->{require(DynamicLightClient.snapshot(m.level).size()==1,"200 dropped torches form one source even while stacks merge");
+ var dropped=m.level.getEntitiesOfClass(ItemEntity.class,m.player.getBoundingBox().inflate(30)).getFirst();
+ // Flowing water carries the pile: sample where the items are now, not where they were thrown.
+ int light=LightCoordsUtil.getLightCoords(m.level,dropped.blockPosition())&255;
+ System.out.println("LIGHT-01 floating drop at "+dropped.position()+" has light "+light);
+ require(light>180,"Dropped cluster follows the items moving in water");});
+ c.takeScreenshot("sanctuary-beta030-dropped-cluster");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();for(var e:p.level().getEntitiesOfClass(ItemEntity.class,p.getBoundingBox().inflate(30)))e.discard();});
+ c.waitFor(m->DynamicLightClient.snapshot(m.level).size()==0&&DynamicLightClient.pendingSections()==0,200);c.waitTicks(40);
+ c.runOnClient(m->require((LightCoordsUtil.getLightCoords(m.level,origin.offset(0,0,2))&255)==0,"Despawn clears every old source"));
+ c.takeScreenshot("sanctuary-beta030-cluster-removed");
+ }finally{onboarding.set(false);Dynamic030RenderProbe.active=false;}
+ c.runOnClient(m->require(DynamicLightClient.pendingSections()==0,"Disconnect clears queued light updates"));
+ System.out.println("LIGHT-01 client passed: hands, head, cake, remote player, 200 drops, meshing, cleanup and unchanged world light.");
+ }
+ private static ItemStack cake(boolean lit) {var item=new ItemStack(Items.CAKE);var tag=new CompoundTag();tag.putString(HeadCosmeticData.CANDLE,"minecraft:candle");item.set(DataComponents.CUSTOM_DATA,CustomData.of(tag));item.set(DataComponents.BLOCK_STATE,BlockItemStateProperties.EMPTY.with(CandleBlock.LIT,lit));return item;}
+ private static void head(ServerPlayer p,ItemStack stack) {AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,stack);AccessoryService.publish(p);}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static Stream widgets(GuiEventListener element){return Stream.concat(Stream.of(element),element instanceof ContainerEventHandler parent?parent.children().stream().flatMap(Dynamic030ClientChecks::widgets):Stream.empty());}
+ private static void require(boolean ok,String message){if(!ok)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Experience054Checks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Experience054Checks.java
new file mode 100644
index 0000000..e263451
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Experience054Checks.java
@@ -0,0 +1,134 @@
+package fr.koka.sanctuary.gametest;
+
+import fr.koka.sanctuary.familiar.*;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import java.util.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.*;
+import net.minecraft.server.level.ServerPlayer;
+import net.minecraft.world.entity.*;
+
+/** Exact balances, real damage, native button requests and interrupted save recovery. */
+public final class Experience054Checks {
+ private static void check(boolean ok,String message){Combat054ClientChecks.require(ok,message);}
+ public static void run(ClientGameTestContext c,TestServerContext server){
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();peace(p);Combat054ClientChecks.equip(p,"wolf");p.tickCount=((p.tickCount/10)+1)*10;FamiliarService.tick(p);
+ check(FamiliarService.active(p)!=null,"Physical familiar for XP combat test");
+ check(FamiliarLevels.xpAt(16)==352&&FamiliarLevels.xpAt(17)==394&&FamiliarLevels.xpAt(31)==1507&&FamiliarLevels.xpAt(32)==1628&&FamiliarLevels.xpAt(40)==2920,"XP boundaries match Minecraft point costs");
+ check(FamiliarLevels.previous(FamiliarLevels.xpAt(40)+7)==FamiliarLevels.xpAt(40)+7-FamiliarLevels.xpAt(39),"A level withdrawal removes the partial XP and reaches the lower whole level");
+ for(int level:new int[]{0,1,15,16,17,30,31,32,40,120,100000,1000000}){
+ long xp=FamiliarLevels.xpAt(level);check(FamiliarLevels.level(xp)==level,"Native XP boundary "+level);
+ if(level>0)check(FamiliarLevels.level(xp-1)==level-1,"Native XP lower boundary "+level);
+ }
+ p.setExperienceLevels(Integer.MAX_VALUE);p.experienceProgress=.8F;
+ check(FamiliarExperience.playerXp(p)>Integer.MAX_VALUE&&transfer(p,"deposit_all").equals("xp.native_limit"),"Extreme operator levels are refused without integer overflow");
+ long source=FamiliarLevels.xpAt(120)+7;FamiliarExperience.setPlayerXp(p,source);p.totalExperience=1;
+ check(FamiliarExperience.playerXp(p)==source,"Spending levels cannot confuse XP balance with stale totalExperience");
+ var b=FamiliarBattle.bond(p);float health=FamiliarLevels.current(b);
+ check(transfer(p,"deposit_all").equals("xp.transferred"),"Exact XP deposit");
+ b=FamiliarBattle.bond(p);check(b.storedXp()==source&&FamiliarExperience.playerXp(p)==0,"All points debited and banked once");
+ check(FamiliarLevels.level(b.storedXp())==120&&FamiliarLevels.health(b)>1.5F&&FamiliarLevels.damage(b.storedXp())>1.25F,"Power continues beyond the rejected caps");
+ check(Math.abs(FamiliarLevels.current(b)-health)<.001,"Deposit cannot heal");
+ check(FamiliarLevels.rarity("common",b.storedXp()).name().equals("LEGENDARY"),"Rarity rises to legendary");
+ check(FamiliarBattle.world(s).experience.transfer(p,b.id(),0,"deposit").equals("xp.changed"),"Stale request rejected");
+ check(transfer(p,"withdraw_all").equals("xp.transferred")&&FamiliarExperience.playerXp(p)==source,"Full withdrawal restores exactly the original points");
+ check(FamiliarBattle.bond(p).storedXp()==0&&Math.abs(FamiliarLevels.current(FamiliarBattle.bond(p))-health)<.001,"Withdrawal removes bonuses without healing");
+ check(transfer(p,"deposit").equals("xp.transferred")&&FamiliarBattle.bond(p).storedXp()==7,"One level uses the familiar XP curve");
+ check(transfer(p,"withdraw").equals("xp.transferred")&&FamiliarExperience.playerXp(p)==source,"Single level round trip conserves XP");
+ check(transfer(p,"deposit_all").equals("xp.transferred"),"Charge for physical damage check");
+ var pet=FamiliarService.active(p);var target=EntityTypes.ZOMBIE.create(p.level(),EntitySpawnReason.COMMAND);target.setNoAi(true);target.setPos(pet.position().add(0,0,1.5));p.level().addFreshEntity(target);
+ var cast=new FamiliarTechniques.Cast(pet,FamiliarBattle.profile(p).initiative(),true,target,target.position());
+ check(FamiliarTechniques.hit(cast,target,4)&&target.getHealth()<13,"Stored levels increase actual native outgoing damage");
+ float before=FamiliarLevels.current(FamiliarBattle.bond(p));pet.hurtServer(p.level(),pet.damageSources().mobAttack(target),3);
+ check(Math.abs(FamiliarLevels.current(FamiliarBattle.bond(p))-(before-3))<.001,"Incoming damage is applied against the expanded HP reserve");
+ check(transfer(p,"withdraw_all").equals("xp.locked"),"Cannot drain combat strength during a fight");target.discard();peace(p);
+ check(transfer(p,"withdraw_all").equals("xp.transferred"),"XP is recoverable after combat");
+ recovery(p);trade(p);
+ FamiliarExperience.setPlayerXp(p,FamiliarLevels.xpAt(40)+7);FamiliarBattle.send(p,"");
+ });
+ c.waitTicks(3);
+ c.runOnClient(m->m.gui.setScreen(new FamiliarExperienceScreen(null)));
+ c.runOnClient(m->{var button=m.gui.screen().children().stream().filter(net.minecraft.client.gui.components.Button.class::isInstance).map(net.minecraft.client.gui.components.Button.class::cast)
+ .filter(b->b.getMessage().getContents() instanceof net.minecraft.network.chat.contents.TranslatableContents t&&t.getKey().equals("sanctuary.familiar.xp.deposit_all")).findFirst().orElseThrow();
+ check(button.active,"Deposit control is enabled");button.onPress(new net.minecraft.client.input.KeyEvent(com.mojang.blaze3d.platform.InputConstants.KEY_RETURN,0,0));});
+ c.waitFor(m->FamiliarClient.bond!=null&&FamiliarClient.bond.storedXp()==FamiliarLevels.xpAt(40)+7,100);
+ for(String locale:List.of("fr_fr","en_us")){
+ c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new FamiliarExperienceScreen(null));});
+ c.waitTicks(3);c.takeScreenshot("sanctuary-beta054-experience-"+locale);
+ }
+ c.runOnClient(m->{
+ var egg=AccessoryAccess.of(m.player).getItem(AccessoryContainer.FAMILIAR);
+ check(CompanionTooltips.rarity(egg).name().equals("LEGENDARY"),"Item rarity uses server individual balance");
+ check(CompanionTooltips.color(egg)==net.minecraft.ChatFormatting.LIGHT_PURPLE,"Egg name uses the legendary color");m.gui.setScreen(null);
+ });
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var b=FamiliarBattle.bond(p);
+ // A copied object points to one canonical balance, not another reserve of XP.
+ var copy=FamiliarBattle.egg(p).copy();p.getInventory().setItem(2,copy);
+ check(FamiliarIdentity.get(copy).equals(b.id()),"An exchanged/copied egg retains its individual identity");
+ FamiliarBattle.flush(s);s.saveEverything(true,true,true);
+ });
+ System.out.println("XP054_BALANCE_PASS: native XP conservation, uncapped health and damage, no transfer healing, combat lock, receipt recovery and FR/EN controls");
+ }
+ public static void reconnect(ServerPlayer p){
+ var b=FamiliarBattle.bond(p);check(b!=null&&b.storedXp()==FamiliarLevels.xpAt(40)+7&&FamiliarExperience.playerXp(p)==0,"Reconnect preserves stored points and debited player balance");
+ check(FamiliarIdentity.get(p.getInventory().getItem(2)).equals(b.id()),"Copied egg still references the same individual after reconnect");
+ System.out.println("XP054_RECONNECT_PASS: real world reload preserves individual XP, player debit and shared egg identity");
+ }
+ static String transfer(ServerPlayer p,String action){var b=FamiliarBattle.bond(p);return FamiliarBattle.world(p.level().getServer()).experience.transfer(p,b.id(),b.storedXp(),action);}
+ private static void peace(ServerPlayer p){
+ FamiliarBattle.cancel(p);p.setAttached(FamiliarBattle.COMBAT_LOCK,0L);var b=FamiliarBattle.bond(p);
+ if(b!=null){var json=FamiliarCatalog.JSON.toJsonTree(b).getAsJsonObject();json.addProperty("combatUntil",0);FamiliarBattle.update(p,FamiliarBondStore.parse(json));}
+ }
+ private static void trade(ServerPlayer p){
+ var server=p.level().getServer();check(transfer(p,"deposit_all").equals("xp.transferred"),"Charge an egg before trading it");
+ var egg=FamiliarBattle.egg(p).copy();long xp=FamiliarBattle.bond(p).storedXp();
+ FamiliarBattle.release(p);AccessoryAccess.of(p).setItem(AccessoryContainer.FAMILIAR,net.minecraft.world.item.ItemStack.EMPTY);FamiliarService.tick(p);
+ var identity=new com.mojang.authlib.GameProfile(UUID.randomUUID(),"XpTrader054");
+ var q=new ServerPlayer(server,p.level(),identity,net.minecraft.server.level.ClientInformation.createDefault());
+ var connection=new net.minecraft.network.Connection(net.minecraft.network.protocol.PacketFlow.SERVERBOUND);new io.netty.channel.embedded.EmbeddedChannel(connection);
+ q.connection=new net.minecraft.server.network.ServerGamePacketListenerImpl(server,connection,q,net.minecraft.server.network.CommonListenerCookie.createInitial(identity,false));
+ q.setPos(p.position().add(2,0,0));q.setAttached(fr.koka.sanctuary.progression.ProgressionService.RECORD,fr.koka.sanctuary.progression.ProgressRecord.INITIAL.json());
+ fr.koka.sanctuary.progression.ProgressEffects.apply(q,fr.koka.sanctuary.progression.ProgressRecord.INITIAL);
+ q.connection.handleAcceptPlayerLoad(new net.minecraft.network.protocol.game.ServerboundPlayerLoadedPacket());p.level().addNewPlayer(q);
+ AccessoryAccess.of(q).setItem(AccessoryContainer.FAMILIAR,egg.copy());fr.koka.sanctuary.companion.CompanionService.tick(q);
+ check(FamiliarBattle.prepare(q)&&FamiliarBattle.bond(q).storedXp()==xp,"Egg trade preserves its exact stored XP");
+ AccessoryAccess.of(p).setItem(AccessoryContainer.FAMILIAR,egg.copy());
+ check(!FamiliarBattle.prepare(p)&&transfer(p,"withdraw_all").equals("unavailable"),"A retained copy cannot steal the active new owner's balance");
+ check(transfer(q,"withdraw_all").equals("xp.transferred")&&FamiliarExperience.playerXp(q)==xp,"New owner can recover all stored XP");
+ check(FamiliarExperience.playerXp(p)==0&&FamiliarBattle.bond(p).storedXp()==0,"Copies do not hold another XP balance");
+ FamiliarBattle.release(q);AccessoryAccess.of(q).setItem(AccessoryContainer.FAMILIAR,net.minecraft.world.item.ItemStack.EMPTY);FamiliarService.tick(q);q.discard();
+ check(FamiliarBattle.prepare(p),"Empty traded egg can return to its original owner");p.tickCount=((p.tickCount/10)+1)*10;FamiliarService.tick(p);
+ System.out.println("XP054_TRADE_PASS: two players transfer a charged egg, recover its XP and reject a duplicated claim");
+ }
+ private static void recovery(ServerPlayer p){
+ var s=p.level().getServer();var store=FamiliarBattle.world(s).store;
+ try{
+ var original=FamiliarBattle.bond(p);var legacy=FamiliarCatalog.JSON.toJsonTree(original).getAsJsonObject();legacy.addProperty("schema",1);legacy.remove("storedXp");
+ check(FamiliarBondStore.parse(legacy).storedXp()==0,"Schema 1 migration starts with zero XP");
+ var migrationFile=s.getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).resolve("sanctuary/xp-migration-test.json");
+ var unknown=legacy.deepCopy();var unknownId=UUID.randomUUID();unknown.addProperty("id",unknownId.toString());unknown.addProperty("schema",99);
+ var individuals=new com.google.gson.JsonObject();individuals.add(original.id().toString(),legacy);individuals.add(unknownId.toString(),unknown);
+ var oldRoot=new com.google.gson.JsonObject();oldRoot.addProperty("schema",1);oldRoot.add("individuals",individuals);java.nio.file.Files.writeString(migrationFile,FamiliarCatalog.JSON.toJson(oldRoot));
+ var migrating=new FamiliarBondStore(migrationFile);check(migrating.get(unknownId)==null,"Unknown individual schema is disabled");migrating.put(migrating.get(original.id()));migrating.flush();
+ var migrated=com.google.gson.JsonParser.parseString(java.nio.file.Files.readString(migrationFile)).getAsJsonObject().getAsJsonObject("individuals");
+ check(migrated.get(unknownId.toString()).equals(unknown)&&migrated.getAsJsonObject(original.id().toString()).get("schema").getAsInt()==2,"Migration preserves unknown data while upgrading known individuals");
+ for(int phase:new int[]{0,1,2}){boolean paid=phase>0;
+ var before=FamiliarBattle.bond(p);var after=before.charge(before.storedXp()+7,FamiliarBattle.profile(p).health());long balance=FamiliarExperience.playerXp(p);
+ var transfer=new com.google.gson.JsonObject();String receipt=UUID.randomUUID().toString();transfer.addProperty("receipt",receipt);
+ transfer.add("before",FamiliarCatalog.JSON.toJsonTree(before));transfer.add("after",FamiliarCatalog.JSON.toJsonTree(after));transfer.addProperty("playerBefore",balance);transfer.addProperty("playerAfter",balance-7);
+ var entries=new com.google.gson.JsonObject();entries.add(p.getStringUUID(),transfer);var root=new com.google.gson.JsonObject();root.addProperty("schema",1);root.add("pending",entries);
+ store.flush();java.nio.file.Files.writeString(s.getWorldPath(net.minecraft.world.level.storage.LevelResource.ROOT).resolve("sanctuary/familiar-xp-v1.json"),FamiliarCatalog.JSON.toJson(root));
+ if(paid){FamiliarExperience.setPlayerXp(p,balance-7);p.setAttached(FamiliarExperience.RECEIPT,receipt);}
+ if(phase==2){store.put(after);store.flush();}
+ var reloaded=new FamiliarExperience(s,store);check(reloaded.blocked(before.id()),"Pending transfer freezes the individual");reloaded.recover(p);
+ check(store.get(before.id()).storedXp()==before.storedXp()+(paid?7:0),"Recovery commits only a paid native receipt");
+ reloaded.recover(p);check(FamiliarExperience.playerXp(p)==balance-(paid?7:0),"Repeated recovery never credits player XP twice");
+ }
+ // Original ledger resumes once the recovered journal is the same empty canonical document.
+ check(transfer(p,"withdraw_all").equals("xp.transferred"),"Recovered bank can be withdrawn exactly once");
+ }catch(Exception error){throw new AssertionError(error);}
+ }
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Familiar020ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Familiar020ClientChecks.java
new file mode 100644
index 0000000..9923908
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Familiar020ClientChecks.java
@@ -0,0 +1,126 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.companion.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import fr.koka.sanctuary.progression.*;
+import fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.*;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.network.chat.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.Blocks;
+import net.minecraft.world.phys.Vec3;
+
+public final class Familiar020ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ button(screen,"sanctuary.progression.enter").onPress(enter());onboarding.set(false);
+ }});
+ int scale=c.computeOnClient(m->m.options.guiScale().get());
+ var forward=c.computeOnClient(m->m.options.keyUp.getDefaultKey());
+ var posture=c.computeOnClient(m->KeyMapping.get("sanctuary.progression.lying_key").getDefaultKey());
+ var camera=c.computeOnClient(m->m.options.getCameraType());
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary beta020 interactions "+UUID.randomUUID());s.setSeed("42");
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&CompanionClient.record!=null&&m.gui.screen()==null,1200);
+ var server=world.getServer();server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(6);
+ for(int x=-10;x<=10;x++)for(int z=-10;z<=10;z++)for(int y=-1;y<9;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1?Blocks.STONE:Blocks.AIR).defaultBlockState());
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()+.5);p.getInventory().clearContent();p.giveExperienceLevels(10-p.experienceLevel);
+ var egg=new ItemStack(Items.CAT_SPAWN_EGG);egg.set(DataComponents.CUSTOM_NAME,Component.literal("Nuage"));AccessoryAccess.of(p).setItem(0,egg);
+ p.getInventory().setItem(0,new ItemStack(Items.CAT_SPAWN_EGG));CompanionService.send(p,"");
+ });
+ c.waitFor(m->CompanionClient.species==CompanionType.CAT&&clientPet(m)!=null&&FamiliarSize.value(clientPet(m).egg())>0,200);
+ c.runOnClient(m->{
+ require(!ProgressionClient.operator&&!CompanionClient.studied&&CompanionClient.record.acceptAllies(),"Ordinary inhabitant starts with assistance on and study locked");
+ var lines=tooltip(m,new ItemStack(Items.CAT_SPAWN_EGG));
+ require(lines.stream().anyMatch(t->t.getString().contains("Velvet paws"))&&lines.stream().noneMatch(t->t.getString().contains("20%")),"Unlearned tooltip only gives cryptic names");
+ int distinct=(int)List.of(Items.CAT_SPAWN_EGG,Items.BEE_SPAWN_EGG,Items.BLAZE_SPAWN_EGG,Items.GHAST_SPAWN_EGG,Items.WITHER_SPAWN_EGG).stream()
+ .map(i->new ItemStack(i).getStyledHoverName().getStyle().getColor().getValue()).distinct().count();require(distinct==5,"Five rarity colors reach native hover names");
+ var pet=clientPet(m);var state=(FamiliarRenderer.State)m.getEntityRenderDispatcher().extractEntity(pet,.5F);
+ require(((net.minecraft.client.renderer.entity.state.LivingEntityRenderState)state.appearance).isBaby,"Native baby cat model selected");
+ m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new InventoryScreen(m.player));
+ });c.waitTick();hover(c,36);c.takeScreenshot("sanctuary-beta020-egg-cryptic");
+ c.runOnClient(m->m.gui.setScreen(new ProgressionScreen(null)));c.waitTick();
+ c.runOnClient(m->{require(widgets(m.gui.screen()).filter(AbstractWidget.class::isInstance).map(AbstractWidget.class::cast).noneMatch(w->key(w,"sanctuary.companion.open")||key(w,"sanctuary.companion.activate")),"Aptitudes have no familiar menu or use-power button");button(m.gui.screen(),"sanctuary.companion.study_buy").onPress(enter());});
+ c.waitFor(m->CompanionClient.studied,200);require(server.computeOnServer(s->s.getPlayerList().getPlayers().getFirst().experienceLevel)==6,"Actual study purchase costs four levels");
+ c.runOnClient(m->{var lines=tooltip(m,new ItemStack(Items.CAT_SPAWN_EGG));require(lines.stream().anyMatch(t->t.getString().contains("20%")),"Purchased study reveals actual mechanics");m.gui.setScreen(new InventoryScreen(m.player));});
+ c.waitTick();hover(c,36);c.takeScreenshot("sanctuary-beta020-egg-deciphered");
+ c.runOnClient(m->CompanionClient.openInhabitant(null));c.waitTick();
+ for(int gui:new int[]{2,3,4}){
+ c.runOnClient(m->{m.options.guiScale().set(gui);m.resizeGui();});c.waitTick();
+ c.runOnClient(m->{button(m.gui.screen(),"sanctuary.companion.consent_on");for(var widget:widgets(m.gui.screen()).filter(AbstractWidget.class::isInstance).map(AbstractWidget.class::cast).toList())
+ require(widget.getX()>=0&&widget.getRight()<=m.gui.screen().width,"Inhabitant widgets fit at every GUI scale");});
+ c.takeScreenshot("sanctuary-beta020-inhabitant-"+gui);
+ }
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();button(m.gui.screen(),"sanctuary.companion.consent_on").onPress(enter());});c.waitFor(m->!CompanionClient.record.acceptAllies(),200);
+ c.runOnClient(m->m.gui.setScreen(null));c.waitTicks(3);
+ var at=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);pet.setNoAi(true);pet.setDeltaMovement(Vec3.ZERO);pet.setPos(p.position().add(0,0,2));p.setItemInHand(net.minecraft.world.InteractionHand.MAIN_HAND,ItemStack.EMPTY);return pet.position().add(0,.35,0);});
+ c.getInput().holdKey(o->o.keyShift);c.waitTicks(3);face(c,at);c.waitTicks(2);
+ clickWorld(c,InputConstants.MOUSE_BUTTON_RIGHT);
+ c.waitFor(m->clientPet(m)!=null&&CarryService.carried(clientPet(m)),200);c.getInput().releaseKey(o->o.keyShift);c.waitTicks(3);
+ require(server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();return CarryService.carrying(p)&&p.getFirstPassenger()==FamiliarService.active(p);}),"Actual sneak-click carries equipped familiar");
+ c.runOnClient(m->{var state=(FamiliarRenderer.State)m.getEntityRenderDispatcher().extractEntity(clientPet(m),.5F);
+ require(((net.minecraft.client.renderer.entity.state.CatRenderState)state.appearance).isSitting,"Carried cat sits on head");m.options.setCameraType(CameraType.THIRD_PERSON_BACK);m.player.setXRot(10);});
+ c.waitTicks(3);c.takeScreenshot("sanctuary-beta020-carried-baby");
+ c.getInput().holdKey(o->o.keyShift);c.waitTicks(2);clickWorld(c,InputConstants.MOUSE_BUTTON_RIGHT);c.waitFor(m->!CarryService.carrying(m.player),200);c.getInput().releaseKey(o->o.keyShift);
+ c.runOnClient(m->{m.options.setCameraType(CameraType.FIRST_PERSON);m.options.keyUp.setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_UP));KeyMapping.get("sanctuary.progression.lying_key").setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_F9));KeyMapping.resetMapping();m.player.setYRot(90);m.player.setXRot(0);});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var progress=ProgressEffects.get(p).buy("hunger",1);p.setAttached(ProgressionService.RECORD,progress.json());ProgressEffects.apply(p,progress);p.getFoodData().setFoodLevel(8);ProgressionService.send(p,"");
+ // A small ledge leaves enough flight time to inspect the actual rendered dive.
+ var launch=p.blockPosition();for(int x=-2;x<=2;x++)for(int z=-2;z<=2;z++)for(int y=0;y<3;y++)p.level().setBlockAndUpdate(launch.offset(x,y,z),Blocks.STONE.defaultBlockState());
+ p.teleportTo(launch.getX()-1.5,launch.getY()+3,launch.getZ()+.5);
+ });
+ c.waitFor(m->ProgressionClient.progress.hunger()>0&&m.player.onGround(),200);
+ c.runOnClient(m->m.options.setCameraType(CameraType.THIRD_PERSON_BACK));
+ c.getInput().holdKey(o->o.keyUp);c.getInput().holdKey(o->o.keySprint);c.waitTicks(4);
+ c.getInput().holdKey(o->o.keyJump);c.waitTicks(2);c.getInput().releaseKey(o->o.keyJump);
+ c.getInput().pressKey(InputConstants.KEY_F9);
+ c.waitFor(m->LyingService.posture(m.player)==3,100);
+ c.runOnClient(m->{var state=(net.minecraft.client.renderer.entity.state.AvatarRenderState)m.getEntityRenderDispatcher().extractEntity(m.player,.5F);
+ require(state.swimAmount==1&&state.isVisuallySwimming,"Dive immediately renders the body horizontally");});
+ c.takeScreenshot("sanctuary-beta020-dive");c.getInput().releaseKey(o->o.keyUp);c.getInput().releaseKey(o->o.keySprint);
+ c.waitFor(m->m.player.onGround()&&LyingService.posture(m.player)==0,200);
+ c.runOnClient(m->m.options.setCameraType(CameraType.FIRST_PERSON));
+ for(int size:new int[]{120,6000}){
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var egg=AccessoryAccess.of(p).getItem(0).copy();
+ net.minecraft.world.item.component.CustomData.update(DataComponents.CUSTOM_DATA,egg,tag->{var trait=new net.minecraft.nbt.CompoundTag();trait.putInt("schema",1);trait.putInt("per_mille",size);tag.put(FamiliarSize.KEY,trait);});AccessoryAccess.of(p).setItem(0,egg);
+ });c.waitFor(m->clientPet(m)!=null&&FamiliarSize.value(clientPet(m).egg())==size,200);
+ var position=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var pet=FamiliarService.active(p);pet.setNoAi(true);pet.setDeltaMovement(Vec3.ZERO);pet.setPos(p.position().add(0,0,4));return pet.position().add(0,size==120?.1:1.2,0);});
+ face(c,position);c.waitTicks(4);c.takeScreenshot("sanctuary-beta020-size-"+size);
+ }
+ System.out.println("Familiar020 client passed: native cryptic/revealed tooltips, 5 rarity colors, study purchase, inhabitant GUI 2/3/4, baby sitting, real sneak-right-click carry/drop, configured dive key and horizontal render, landing, egg size synchronization and extremes.");
+ }finally{
+ onboarding.set(false);c.getInput().releaseKey(o->o.keyShift);c.getInput().releaseKey(o->o.keyUp);c.getInput().releaseKey(o->o.keyJump);c.getInput().releaseKey(o->o.keySprint);
+ c.runOnClient(m->{m.options.keyUp.setKey(forward);KeyMapping.get("sanctuary.progression.lying_key").setKey(posture);KeyMapping.resetMapping();m.options.setCameraType(camera);m.options.guiScale().set(scale);m.resizeGui();});
+ }
+ }
+ private static List tooltip(Minecraft m,ItemStack stack){return stack.getTooltipLines(Item.TooltipContext.of(m.level),m.player,TooltipFlag.NORMAL);}
+ private static FamiliarEntity clientPet(Minecraft m){for(var e:m.level.entitiesForRendering())if(e instanceof FamiliarEntity pet&&!pet.isRemoved())return pet;return null;}
+ private static void face(ClientGameTestContext c,Vec3 at){c.runOnClient(m->{var d=at.subtract(m.player.getEyePosition());m.player.setYRot((float)Math.toDegrees(Math.atan2(-d.x,d.z)));m.player.setXRot((float)-Math.toDegrees(Math.atan2(d.y,d.horizontalDistance())));});}
+ private static void clickWorld(ClientGameTestContext c,int button){c.runOnClient(m->{var w=m.getWindow();m.mouseHandler.onButton(w.handle(),new MouseButtonInfo(button,0),1);});c.waitTick();c.runOnClient(m->m.mouseHandler.onButton(m.getWindow().handle(),new MouseButtonInfo(button,0),0));c.waitTick();}
+ private static void hover(ClientGameTestContext c,int index){c.runOnClient(m->{var screen=(AbstractContainerScreen>)m.gui.screen();var a=(AbstractContainerScreenAccessor)screen;var slot=screen.getMenu().getSlot(index);var w=m.getWindow();m.mouseHandler.onMove(w.handle(),(a.sanctuary$getLeftPos()+slot.x+8.0)*w.getScreenWidth()/w.getGuiScaledWidth(),(a.sanctuary$getTopPos()+slot.y+8.0)*w.getScreenHeight()/w.getGuiScaledHeight(),0,0);m.mouseHandler.handleAccumulatedMovement();});c.waitTick();}
+ private static Stream widgets(ContainerEventHandler container){return container.children().stream().flatMap(w->w instanceof ContainerEventHandler nested?Stream.concat(Stream.of(w),widgets(nested)):Stream.of(w));}
+ private static boolean key(AbstractWidget w,String key){return w.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key);}
+ private static Button button(Screen screen,String key){return widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(w->key(w,key)).findFirst().orElseThrow(()->new AssertionError("Missing "+key));}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean value,String message){if(!value)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Fog053ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Fog053ClientChecks.java
new file mode 100644
index 0000000..46a91e4
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Fog053ClientChecks.java
@@ -0,0 +1,115 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.client.shader.VanillaLight;
+import fr.koka.sanctuary.client.panorama.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.*;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.renderer.fog.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.effect.*;
+import net.minecraft.world.level.material.FogType;
+
+public final class Fog053ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen s){
+ Starter039TestSupport.selectFirst(s);
+ s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ s.children().stream().filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals("sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Fog053 "+UUID.randomUUID());s.setSeed("53");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setAllowCommands(true);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ world.getServer().runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;
+ s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon");
+ var base=p.blockPosition();int i=0;
+ for(int d:new int[]{8,32,64,96}){int x=(i++%2==0?-1:1)*d/4;for(int dy=0;dy<5;dy++)for(int dx=-2;dx<=2;dx++)p.level().setBlockAndUpdate(base.offset(x+dx,dy,d),net.minecraft.world.level.block.Blocks.CONCRETE.red().defaultBlockState());}
+ });
+ c.runOnClient(m->{m.options.renderDistance().set(8);m.options.bobView().set(false);m.options.fov().set(70);m.player.setYRot(0);m.player.yRotO=0;m.player.setXRot(8);m.player.xRotO=8;VanillaLight.setEnabled(false);});
+ c.waitTicks(35);
+ c.runOnClient(m->{
+ var nativeFog=setup(m,m.gameRenderer.mainCamera());require(nativeFog.renderDistanceStart>0,"Native distant start before enabling shader");
+ VanillaLight.setEnabled(true);var graded=setup(m,m.gameRenderer.mainCamera());require(graded.renderDistanceStart==0&&graded.environmentalStart<=0,"Fog begins immediately at the viewpoint");
+ sameEnds(nativeFog,graded);float prior=-1;
+ for(float d:new float[]{0,1,4,16,32,64,96,128}){float f=PanoramaDepth.fog(new org.joml.Vector3f(0,0,d),PanoramaDepth.Fog.of(graded));require(f>=prior&&f>=0&&f<=1,"Continuous monotonic native GPU fog curve");if(d==0)require(f==0,"No veil at zero distance");if(d==1)require(f>0&&f<.02,"Very light fog starts close to the player");prior=f;}
+ require(prior==1,"Fog fully hides the render boundary");VanillaLight.setEnabled(false);
+ });
+ c.waitTicks(6);c.takeScreenshot("sanctuary-beta053-fog-off");
+ c.runOnClient(m->VanillaLight.setEnabled(true));c.waitFor(m->m.gameRenderer.getAppliedPostEffects().contains(VanillaLight.EFFECT),100);c.waitTicks(6);c.takeScreenshot("sanctuary-beta053-fog-on");
+ var chain=c.computeOnClient(m->m.getShaderManager().getPostChain(VanillaLight.EFFECT,net.minecraft.client.renderer.LevelTargetBundle.MAIN_TARGETS));
+ for(int saturation:new int[]{0,100,200}){
+ c.runOnClient(m->VanillaLight.setSaturation(saturation));c.waitTicks(6);
+ c.runOnClient(m->require(m.getShaderManager().getPostChain(VanillaLight.EFFECT,net.minecraft.client.renderer.LevelTargetBundle.MAIN_TARGETS)==chain,"Live saturation reuses the compiled shader chain"));
+ c.takeScreenshot("sanctuary-beta053-saturation-"+saturation);
+ }
+ c.runOnClient(m->{
+ VanillaLight.setSaturation(170);
+ try{var pref=VanillaLight.class.getDeclaredField("preferences");pref.setAccessible(true);pref.set(null,null);}catch(ReflectiveOperationException e){throw new RuntimeException(e);}
+ require(VanillaLight.saturation()==170,"Saturation preference survives reload from disk");
+ VanillaLight.setSaturation(999);require(VanillaLight.saturation()==200,"Upper saturation limit");VanillaLight.setSaturation(-1);require(VanillaLight.saturation()==0,"Lower saturation limit");VanillaLight.setSaturation(100);
+ });
+ for(String locale:List.of("fr_fr","en_us")){
+ c.runOnClient(m->{
+ m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
+ VanillaLight.setSaturation(100);m.gui.setScreen(new fr.koka.sanctuary.client.shader.ShaderOptionsScreen(null));
+ var slider=widgets(m.gui.screen()).filter(AbstractSliderButton.class::isInstance).map(AbstractSliderButton.class::cast).findFirst().orElseThrow();
+ require(slider.getMessage().getString().contains("100")&&slider.getMessage().getString().contains("Saturation"),"Localized native saturation slider");
+ slider.keyPressed(enter());for(int i=0;i<8;i++)slider.keyPressed(new KeyEvent(InputConstants.KEY_RIGHT,0,0));
+ require(VanillaLight.saturation()>100,"Keyboard interaction immediately applies native slider value");
+ });c.waitTicks(4);c.takeScreenshot("sanctuary-beta053-shader-options-"+locale);
+ }
+ c.runOnClient(m->{m.gui.setScreen(null);VanillaLight.setSaturation(100);});
+ for(CameraType type:List.of(CameraType.FIRST_PERSON,CameraType.THIRD_PERSON_BACK))for(int fov:new int[]{30,110}){
+ c.runOnClient(m->{m.options.setCameraType(type);m.options.fov().set(fov);m.player.setYRot(30);m.player.setXRot(-10);});c.waitTicks(5);
+ c.runOnClient(m->{var f=m.gameRenderer.gameRenderState().levelRenderState.cameraRenderState.fogData;require(f.renderDistanceStart==0&&f.renderDistanceEnd==128,"Camera and FOV preserve the native distance scale");});
+ }
+ c.runOnClient(m->{m.options.setCameraType(CameraType.FIRST_PERSON);m.options.fov().set(70);m.player.setYRot(0);m.player.setXRot(8);
+ var live=m.gameRenderer.mainCamera();
+ for(var medium:List.of(FogType.WATER,FogType.LAVA,FogType.POWDER_SNOW)){
+ Camera camera=new MediumCamera(live,medium);VanillaLight.setEnabled(false);var before=setup(m,camera);VanillaLight.setEnabled(true);sameFog(before,setup(m,camera));
+ }
+ for(var effect:List.of(MobEffects.BLINDNESS,MobEffects.DARKNESS)){
+ m.player.addEffect(new MobEffectInstance(effect,200));VanillaLight.setEnabled(false);var before=setup(m,live);VanillaLight.setEnabled(true);sameFog(before,setup(m,live));m.player.removeEffect(effect);
+ }
+ m.level.setRainLevel(1);m.level.setThunderLevel(1);VanillaLight.setEnabled(false);var before=setup(m,live);VanillaLight.setEnabled(true);sameEnds(before,setup(m,live));m.level.setRainLevel(0);m.level.setThunderLevel(0);
+ });
+ for(String locale:List.of("fr_fr","en_us"))c.runOnClient(m->{m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());require(net.minecraft.network.chat.Component.translatable("sanctuary.shaders.help").getString().contains(locale.equals("fr_fr")?"brume progressive":"progressive fog"),"Localized shader tooltip");});
+ var reload=c.computeOnClient(m->m.reloadResourcePacks());c.waitFor(m->reload.isDone()&&m.gui.overlay()==null,1200);reload.join();c.waitTicks(6);
+ c.runOnClient(m->require(m.gameRenderer.getAppliedPostEffects().contains(VanillaLight.EFFECT)&&setup(m,m.gameRenderer.mainCamera()).renderDistanceStart==0,"Native shader and fog survive resource reload"));
+ c.runOnClient(m->{PanoramaOptions.get().resolution=256;PanoramaCapture.request(m);});
+ c.waitFor(m->PanoramaCapture.lastCapture().isDone(),1200);var panorama=c.computeOnClient(m->PanoramaCapture.lastCapture().join());
+ try{var data=fr.koka.sanctuary.progression.AtomicJson.parse(java.nio.file.Files.readString(panorama.resolve("capture.json"))).getAsJsonObject();
+ for(var face:data.getAsJsonArray("faces"))require(face.getAsJsonObject().getAsJsonObject("fog").get("renderStart").getAsFloat()>0,"Technical panorama retains raw native fog matching its depth mask");
+ }catch(java.io.IOException e){throw new RuntimeException(e);}
+ c.waitTicks(5);c.runOnClient(m->require(setup(m,m.gameRenderer.mainCamera()).renderDistanceStart==0,"Normal progressive fog resumes after six panorama faces"));
+ c.takeScreenshot("sanctuary-beta053-fog-restored");
+ }
+ System.out.println("FOG053_CLIENT_PASS: native progressive fog, toggle, near/far, camera/FOV, fluids/effects/weather, FR/EN, resource reload and raw panorama restoration");
+ System.out.println("SATURATION053_CLIENT_PASS: 0/100/200 live GPU screenshots, native FR/EN keyboard slider, bounded saved preference, no shader reload per change");
+ }
+ private static FogData setup(Minecraft m,Camera camera){try{var field=net.minecraft.client.renderer.GameRenderer.class.getDeclaredField("fogRenderer");field.setAccessible(true);return ((FogRenderer)field.get(m.gameRenderer)).setupFog(camera,8,DeltaTracker.ZERO,0,m.level);}catch(ReflectiveOperationException e){throw new RuntimeException(e);}}
+ private static void sameEnds(FogData a,FogData b){require(a.environmentalEnd==b.environmentalEnd&&a.renderDistanceEnd==b.renderDistanceEnd&&a.skyEnd==b.skyEnd&&a.cloudEnd==b.cloudEnd&&a.color.equals(b.color),"Native ends, sky/cloud limits and colour preserved");}
+ private static void sameFog(FogData a,FogData b){sameEnds(a,b);require(a.environmentalStart==b.environmentalStart&&a.renderDistanceStart==b.renderDistanceStart,"Special visibility restriction remains entirely native");}
+ private static final class MediumCamera extends Camera {
+ private final Camera live;private final FogType medium;
+ MediumCamera(Camera live,FogType medium){this.live=live;this.medium=medium;}
+ @Override public FogType getFluidInCamera(){return medium;}
+ @Override public net.minecraft.world.entity.Entity entity(){return live.entity();}
+ @Override public net.minecraft.world.phys.Vec3 position(){return live.position();}
+ @Override public net.minecraft.core.BlockPos blockPosition(){return live.blockPosition();}
+ @Override public net.minecraft.world.attribute.EnvironmentAttributeProbe attributeProbe(){return live.attributeProbe();}
+ @Override public org.joml.Vector3fc forwardVector(){return live.forwardVector();}
+ }
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static java.util.stream.Stream widgets(net.minecraft.client.gui.components.events.ContainerEventHandler root){return root.children().stream().flatMap(w->w instanceof net.minecraft.client.gui.components.events.ContainerEventHandler child?java.util.stream.Stream.concat(java.util.stream.Stream.of(w),widgets(child)):java.util.stream.Stream.of(w));}
+ private static void require(boolean v,String text){if(!v)throw new AssertionError(text);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/FoodPanorama039ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/FoodPanorama039ClientChecks.java
new file mode 100644
index 0000000..973313c
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/FoodPanorama039ClientChecks.java
@@ -0,0 +1,134 @@
+package fr.koka.sanctuary.gametest;
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.grave.*;
+import fr.koka.sanctuary.food.*;
+import fr.koka.sanctuary.client.panorama.*;
+import net.minecraft.core.component.DataComponents;
+import java.nio.file.*;
+import java.nio.*;
+import javax.imageio.ImageIO;
+import org.joml.Matrix4f;
+import org.joml.Vector3f;
+import fr.koka.sanctuary.progression.*;
+import fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.entity.SkullBlockEntity;
+import net.minecraft.world.level.gamerules.GameRules;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+public final class FoodPanorama039ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ Starter039TestSupport.manual=true;
+ var starterFrames=new java.util.concurrent.atomic.AtomicInteger();
+ var starterOptions=new java.util.concurrent.atomic.AtomicReference>();
+ var starterChosen=new java.util.concurrent.atomic.AtomicReference();
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(m.gui.screen() instanceof HelloWorldScreen screen&&!screen.starterChoices().isEmpty()){
+ require(m.player==null&&m.level==null,"Starter chosen before any world or player appears");
+ int frame=starterFrames.incrementAndGet();
+ if(frame==1){starterOptions.set(screen.starterChoices());require(screen.starterChoices().size()==3,"Three starters displayed");require(!button(screen,"sanctuary.progression.enter").active,"Entering requires colour and starter");m.getLanguageManager().setSelected("fr_fr");m.getLanguageManager().onResourceManagerReload(m.getResourceManager());}
+ if(frame==3){var slot=widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("StarterSlot")).findFirst().orElseThrow();var w=m.getWindow();m.mouseHandler.onMove(w.handle(),(slot.getX()+11.0)*w.getScreenWidth()/w.getGuiScaledWidth(),(slot.getY()+11.0)*w.getScreenHeight()/w.getGuiScaledHeight(),0,0);m.mouseHandler.handleAccumulatedMovement();}
+ if(frame==8){net.minecraft.client.Screenshot.grab(m,false);net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking.send(new StarterPayloads.Create("",0,"ender_dragon"));}
+ if(frame==18){require(screen.starterChoices().equals(starterOptions.get()),"Forged choice cannot reroll the offer");starterChosen.set(screen.starterChoices().getFirst().mob());Starter039TestSupport.selectFirst(screen);widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());button(screen,"sanctuary.progression.enter").onPress(enter());}
+ }});
+ net.fabricmc.fabric.api.client.gametest.v1.world.TestWorldSave savedWorld;
+ var armed=new AtomicBoolean(true);
+
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary food panorama039 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);var server=world.getServer();
+
+ savedWorld=world.getWorldSave();
+ c.runOnClient(m->{m.getLanguageManager().setSelected("en_us");m.getLanguageManager().onResourceManagerReload(m.getResourceManager());});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var store=ProgressionService.session(s).starters;require(store.get(p.getUUID()).selected().equals(starterChosen.get()),"Server persists chosen starter");require(starterChosen.get().equals(p.getAttached(StarterCompanions.RECEIPT)),"Starter delivery receipt on first arrival");require(fr.koka.sanctuary.inventory.AccessoryAccess.of(p).getItem(fr.koka.sanctuary.inventory.AccessoryContainer.FAMILIAR).getItem()==StarterCompanions.egg(starterChosen.get()).getItem(),"Selected egg is really equipped");});
+ server.runOnServer(s->{s.getGlobalGameRules().set(GameRules.NATURAL_HEALTH_REGENERATION,false,s);var p=s.getPlayerList().getPlayers().getFirst();p.getFoodData().setFoodLevel(1);p.getFoodData().setSaturation(0);p.getInventory().setItem(0,new ItemStack(Items.APPLE,12));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->FoodKnowledgeClient.ready()&&FoodDetailsClient.current()!=null,150);
+ c.runOnClient(m->{require(!FoodKnowledgeClient.knows(new ItemStack(Items.APPLE)),"Untasted food hidden");require(new ItemStack(Items.APPLE).getTooltipImage().isEmpty(),"Unknown tooltip has no food metrics");});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var stack=new ItemStack(Items.APPLE);stack.get(DataComponents.CONSUMABLE).onConsume(p.level(),p,stack);});
+ c.waitFor(m->FoodKnowledgeClient.knows(new ItemStack(Items.APPLE)),100);
+ c.runOnClient(m->{require(new ItemStack(Items.APPLE).getTooltipImage().orElseThrow() instanceof FoodDetailsTooltip.Data,"Tasting reveals native icon tooltip");require(new ItemStack(Items.COOKED_BEEF).getTooltipImage().isEmpty(),"Untasted steak remains hidden");});
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setExperienceLevels(8);ProgressionService.send(p,"");});
+ c.waitFor(m->m.player.experienceLevel==8,100);
+ c.runOnClient(m->m.gui.setScreen(new ProgressionScreen(null)));c.waitTicks(5);
+ c.runOnClient(m->{var b=button(m.gui.screen(),"sanctuary.food_knowledge.buy");require(b.active,"Food study available with XP");b.onPress(enter());});
+ c.waitFor(m->m.player.experienceLevel==4&&FoodKnowledgeClient.knows(new ItemStack(Items.COOKED_BEEF)),100);
+ c.takeScreenshot("sanctuary-beta039-food-study");c.runOnClient(m->m.gui.setScreen(null));
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var stack=GraveService.head(p,List.of(new ItemStack(Items.DIAMOND)));fr.koka.sanctuary.inventory.AccessoryAccess.of(p).setItem(fr.koka.sanctuary.inventory.AccessoryContainer.HEAD,stack);});
+ c.waitFor(m->fr.koka.sanctuary.cosmetics.HeadCosmeticService.appearance(m.player).is(Items.PLAYER_HEAD),150);
+ c.runOnClient(m->{var stack=fr.koka.sanctuary.cosmetics.HeadCosmeticService.appearance(m.player);var h=((HeadCosmeticState.Access)m.getEntityRenderDispatcher().extractEntity(m.player,.5f)).sanctuary$headCosmetic();require(h.blockEntity instanceof net.minecraft.client.renderer.blockentity.state.SkullBlockRenderState,"Skull uses native component-aware renderer");var skull=(net.minecraft.client.renderer.blockentity.state.SkullBlockRenderState)h.blockEntity;require(skull.renderType==m.playerSkinRenderCache().getOrDefault(stack.get(DataComponents.PROFILE)).renderType(),"Skull uses owner's skin cache, not a generic Steve texture");require(h.block.isEmpty(),"No generic block model over the skull");m.options.setCameraType(net.minecraft.client.CameraType.THIRD_PERSON_FRONT);});
+ c.waitTicks(5);c.takeScreenshot("sanctuary-beta039-head-skin");
+ c.runOnClient(m->{m.gui.setScreen(new PanoramaOptionsScreen(null));});c.waitTicks(5);c.takeScreenshot("sanctuary-beta039-panorama-options");c.runOnClient(m->m.gui.setScreen(null));
+ c.runOnClient(m->{m.player.setYRot(33);m.player.setXRot(17);m.player.yRotO=31;m.player.xRotO=15;});
+ var before=c.computeOnClient(m->List.of(m.getWindow().getWidth(),m.getWindow().getHeight(),m.options.fov().get(),m.options.getCameraType()));
+ c.runOnClient(m->{PanoramaOptions.get().enabled=true;PanoramaOptions.get().resolution=256;net.minecraft.client.Screenshot.grab(m,false);require(m.player.getYRot()==33&&m.player.getXRot()==17&&m.player.yRotO==31&&m.player.xRotO==15,"Camera angles restored synchronously");});
+ c.waitFor(m->PanoramaCapture.lastCapture().isDone(),1200);
+ Path first=c.computeOnClient(m->PanoramaCapture.lastCapture().join());
+ require(c.computeOnClient(m->List.of(m.getWindow().getWidth(),m.getWindow().getHeight(),m.options.fov().get(),m.options.getCameraType())).equals(before),"Window, FOV and third-person choice restored");
+ verifyCapture(first);
+ c.waitFor(m->!PanoramaCapture.busy(),100);
+ c.runOnClient(m->net.minecraft.client.Screenshot.grab(m,false));c.waitFor(m->PanoramaCapture.lastCapture().isDone(),1200);
+ Path second=c.computeOnClient(m->PanoramaCapture.lastCapture().join());require(!first.equals(second),"Second panorama cannot overwrite first");verifyCapture(second);
+ c.waitFor(m->!PanoramaCapture.busy(),100);
+ c.runOnClient(m->{PanoramaOptions.get().enabled=false;PanoramaOptions.get().resolution=1024;net.minecraft.client.Screenshot.grab(m,false);require(PanoramaCapture.lastCapture().join().equals(second),"Normal screenshot does not invoke panorama");});
+ c.waitTicks(5);c.takeScreenshot("sanctuary-beta039-after-panorama");
+ server.runOnServer(sv->{
+ var p=sv.getPlayerList().getPlayers().getFirst();var progress=ProgressRecord.INITIAL;for(int i=1;i<6;i++)progress=progress.buy("inventory",i);p.setAttached(ProgressionService.RECORD,progress.json());ProgressEffects.apply(p,progress);
+ p.getInventory().clearContent();fr.koka.sanctuary.inventory.AccessoryAccess.of(p).clearContent();fr.koka.sanctuary.inventory.HotbarRows.select(p,2);
+ p.getInventory().setItem(0,new ItemStack(Items.DIAMOND,12));p.getInventory().setItem(10,new ItemStack(Items.EMERALD,3));fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p).setItem(17,new ItemStack(Items.APPLE,5));
+ p.setItemSlot(net.minecraft.world.entity.EquipmentSlot.HEAD,new ItemStack(Items.IRON_HELMET));fr.koka.sanctuary.inventory.AccessoryAccess.of(p).setItem(fr.koka.sanctuary.inventory.AccessoryContainer.CAPE,new ItemStack(fr.koka.sanctuary.cosmetics.AccessoryService.ZERO_CAPE));fr.koka.sanctuary.inventory.AccessoryAccess.of(p).setItem(fr.koka.sanctuary.inventory.AccessoryContainer.FAMILIAR,new ItemStack(Items.CHICKEN_SPAWN_EGG));
+ var layout=GraveLayout.capture(p);for(int i=0;i((GraveInventoryMenu)m.player.containerMenu).visibleRows()==6&&m.player.containerMenu.getSlot(18).hasItem(),150);c.waitTicks(5);
+ c.runOnClient(m->{var screen=(GraveInventoryScreen)m.gui.screen();var menu=screen.getMenu();require(menu.getSlot(18).xm.gameMode.handleContainerInput(m.player.containerMenu.containerId,cell,0,net.minecraft.world.inventory.ContainerInput.QUICK_MOVE,m.player));c.waitFor(m->m.player.containerMenu.getSlot(cell).getItem().isEmpty(),100);}
+ c.waitTicks(6);
+ require(server.computeOnServer(sv->{var p=sv.getPlayerList().getPlayers().getFirst();System.out.println("GRAVE039 recovery state: "+java.util.stream.IntStream.range(0,9).mapToObj(i->p.getInventory().getItem(i).toString()).toList()+" helmet="+p.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD)+" accessories="+java.util.stream.IntStream.range(0,3).mapToObj(i->fr.koka.sanctuary.inventory.AccessoryAccess.of(p).getItem(i).toString()).toList());return p.getInventory().contains(new ItemStack(Items.DIAMOND))&&p.getItemBySlot(net.minecraft.world.entity.EquipmentSlot.HEAD).is(Items.IRON_HELMET)&&fr.koka.sanctuary.inventory.AccessoryAccess.of(p).getItem(fr.koka.sanctuary.inventory.AccessoryContainer.CAPE).is(fr.koka.sanctuary.cosmetics.AccessoryService.ZERO_CAPE)&&fr.koka.sanctuary.inventory.AccessoryAccess.of(p).getItem(fr.koka.sanctuary.inventory.AccessoryContainer.FAMILIAR).is(Items.CHICKEN_SPAWN_EGG);}),"Real recovery packets route storage, helmet, cape and familiar correctly");
+ c.waitTicks(5);c.takeScreenshot("sanctuary-beta039-grave-recovery");c.runOnClient(m->{m.options.guiScale().set(1);m.resizeGui();});c.waitTicks(5);c.takeScreenshot("sanctuary-beta039-grave-scale1");
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.getLanguageManager().setSelected("fr_fr");m.getLanguageManager().onResourceManagerReload(m.getResourceManager());});c.waitTicks(5);c.takeScreenshot("sanctuary-beta039-grave-fr");
+ c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);c.runOnClient(m->m.options.setCameraType(net.minecraft.client.CameraType.FIRST_PERSON));
+ server.runOnServer(sv->{var p=sv.getPlayerList().getPlayers().getFirst();p.getFoodData().setFoodLevel(3);p.getFoodData().setSaturation(0);p.getFoodData().addExhaustion(3.5f);});c.waitTicks(12);c.takeScreenshot("sanctuary-beta039-food-underlay");
+ server.runOnServer(sv->{var p=sv.getPlayerList().getPlayers().getFirst();fr.koka.sanctuary.inventory.AccessoryAccess.of(p).clearContent();sv.saveEverything(true,true,true);});
+ System.out.println("FOOD PANORAMA039 client passed: hidden/tasted/studied tooltips, real purchase, owner skull renderer, six colour/depth/fog faces, repeated capture, restored camera. Output: "+first);
+ }
+ int beforeReconnect=starterFrames.get();
+ try(var world=savedWorld.open()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ require(starterFrames.get()==beforeReconnect,"Returning resident skips starter selection");
+ world.getServer().runOnServer(sv->{var p=sv.getPlayerList().getPlayers().getFirst();require(starterChosen.get().equals(p.getAttached(StarterCompanions.RECEIPT))&&fr.koka.sanctuary.inventory.AccessoryAccess.of(p).getItem(fr.koka.sanctuary.inventory.AccessoryContainer.FAMILIAR).isEmpty(),"Reconnect never replaces a removed starter");});
+ System.out.println("STARTER039_CLIENT_PASS: configuration choice, forged choice rejected, equipped once, reconnect without a second gift");
+ }finally{armed.set(false);Starter039TestSupport.manual=false;PanoramaOptions.get().enabled=false;}
+ }
+ private static void verifyCapture(Path folder){try{
+ var data=com.google.gson.JsonParser.parseString(Files.readString(folder.resolve("capture.json"))).getAsJsonObject();require(data.getAsJsonArray("faces").size()==6,"Metadata has six faces");
+ boolean zeroToOne=data.get("clip_z_zero_to_one").getAsBoolean();
+ for(int i=0;i<6;i++){
+ for(String prefix:List.of("panorama_","depth_","fog_")){var image=ImageIO.read(folder.resolve(prefix+i+".png").toFile());require(image.getWidth()==256&&image.getHeight()==256,"Square export "+prefix+i);}
+ byte[] raw=Files.readAllBytes(folder.resolve("depth_"+i+".f32"));require(raw.length==256*256*4,"Depth float32 size");
+ var bytes=ByteBuffer.wrap(raw).order(ByteOrder.LITTLE_ENDIAN);int surfaces=0;for(int n=0;n<256*256;n++){float z=bytes.getFloat();require(Float.isFinite(z)&&z>=0&&z<=1,"Valid reversed GPU depth");if(z>0)surfaces++;}
+ if(i==5)require(surfaces>256*256/2,"Bottom face contains terrain depth, not a buffer cleared by HUD");
+ var metadata=data.getAsJsonArray("faces").get(i).getAsJsonObject();var numbers=metadata.getAsJsonArray("inverse_view_projection_column_major");float[] inv=new float[16];for(int n=0;n<16;n++)inv[n]=numbers.get(n).getAsFloat();
+ var numbers2=metadata.getAsJsonArray("view_projection_column_major");float[] vp=new float[16];for(int n=0;n<16;n++)vp[n]=numbers2.get(n).getAsFloat();
+ var world=PanoramaDepth.position(new Matrix4f().set(inv),.2f,-.4f,.5f,zeroToOne);var clip=new Matrix4f().set(vp).transformProject(new Vector3f(world));require(Math.abs(clip.x-.2f)<.001&&Math.abs(clip.y+.4f)<.001&&Math.abs(clip.z-(zeroToOne?.5f:0))<.001,"Inverse projection matches backend depth convention");
+ }
+ }catch(Exception e){throw new RuntimeException(e);}}
+ private static Stream widgets(ContainerEventHandler c){return c.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static Button button(Screen s,String key){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow();}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean ok,String why){if(!ok)throw new AssertionError(why);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/GravesFood038ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/GravesFood038ClientChecks.java
new file mode 100644
index 0000000..ec3bcec
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/GravesFood038ClientChecks.java
@@ -0,0 +1,77 @@
+package fr.koka.sanctuary.gametest;
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.grave.*;
+import fr.koka.sanctuary.progression.*;
+import fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.core.BlockPos;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.entity.SkullBlockEntity;
+import net.minecraft.world.level.gamerules.GameRules;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+
+public final class GravesFood038ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(armed.get()&&m.gui.screen() instanceof HelloWorldScreen screen){widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());button(screen,"sanctuary.progression.enter").onPress(enter());armed.set(false);}});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary graves food038 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);var server=world.getServer();
+ server.runOnServer(s->{s.getGlobalGameRules().set(GameRules.NATURAL_HEALTH_REGENERATION,false,s);s.setDifficulty(net.minecraft.world.Difficulty.NORMAL,true);var p=s.getPlayerList().getPlayers().getFirst();fr.koka.sanctuary.food.FoodKnowledgeService.tasted(p,new ItemStack(Items.COOKED_BEEF));p.getInventory().setItem(0,new ItemStack(Items.COOKED_BEEF,12));p.getFoodData().setFoodLevel(3);p.getFoodData().setSaturation(1.5f);p.getFoodData().addExhaustion(2);p.inventoryMenu.broadcastChanges();});
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.player.setXRot(10);});
+ c.waitFor(m->FoodDetailsClient.current()!=null&&FoodDetailsClient.current().food()==3&&FoodDetailsClient.current().capacity()==6,150);
+ c.waitTicks(5);c.runOnClient(m->require(FoodDetailsClient.renderedIcons()==3,"Native hunger hook renders exactly three icons"));c.takeScreenshot("sanctuary-beta038-food-three");
+ c.runOnClient(m->{var data=(FoodDetailsTooltip.Data)new ItemStack(Items.COOKED_BEEF).getTooltipImage().orElseThrow();require(data.food()==8&&Math.abs(data.saturation()-12.8f)<.001&&data.foodGain()==3&&data.overflow(),"Native tooltip supplies food/saturation icons with capacity-aware gain");require(net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent.create(data) instanceof FoodDetailsTooltip,"Fabric maps the native item image to the food icon renderer");m.gui.setScreen(new InventoryScreen(m.player));});c.waitTicks(3);hover(c,36);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-food-tooltip");c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var r=ProgressEffects.get(p);for(int i=0;i<7;i++)r=r.buy("hunger",1);for(int i=0;i<5;i++)r=r.buy("inventory",1);p.setAttached(ProgressionService.RECORD,r.json());ProgressEffects.apply(p,r);ProgressionService.send(p,"");p.getFoodData().setFoodLevel(13);p.getFoodData().setSaturation(7.5f);});
+ c.waitFor(m->ProgressionClient.progress.inventoryRows()==6&&FoodDetailsClient.current()!=null&&FoodDetailsClient.current().capacity()==20&&FoodDetailsClient.renderedIcons()==10,150);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-food-ten");
+ server.runOnServer(s->Companion019GameTests.equip(s.getPlayerList().getPlayers().getFirst(),fr.koka.sanctuary.companion.CompanionType.PIG));
+ c.waitFor(m->FoodDetailsClient.current()!=null&&FoodDetailsClient.current().foodMultiplier()>1,200);
+ c.runOnClient(m->require(Math.abs(FoodDetailsClient.current().preview(new ItemStack(Items.APPLE)).saturationGain()-2.64f)<.01,"Server pig bonus reaches the actual client food preview"));
+ server.runOnServer(s->fr.koka.sanctuary.inventory.AccessoryAccess.of(s.getPlayerList().getPlayers().getFirst()).setItem(0,ItemStack.EMPTY));c.waitFor(m->FoodDetailsClient.current().foodMultiplier()==1,100);
+ c.runOnClient(m->m.gui.setScreen(new FoodDetailsOptionsScreen(null)));c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-food-options");c.runOnClient(m->m.gui.setScreen(null));
+ BlockPos pos=server.computeOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().east(2);p.level().setBlockAndUpdate(at,Blocks.PLAYER_HEAD.defaultBlockState());var skull=(SkullBlockEntity)p.level().getBlockEntity(at);var items=new ArrayList();for(int i=0;i<64;i++)items.add(new ItemStack(i==54?Items.EMERALD:Items.DIAMOND,i+1));skull.applyComponentsFromItemStack(GraveService.head(p,items));skull.setChanged();p.level().sendBlockUpdated(at,skull.getBlockState(),skull.getBlockState(),3);return at;});
+ c.waitFor(m->GraveService.isGrave(m.level.getBlockEntity(pos)),100);
+ c.runOnClient(m->{var result=m.gameMode.useItemOn(m.player,net.minecraft.world.InteractionHand.MAIN_HAND,new net.minecraft.world.phys.BlockHitResult(net.minecraft.world.phys.Vec3.atCenterOf(pos),net.minecraft.core.Direction.UP,pos,false));require(result.consumesAction(),"Real client block interaction consumes the right-click");});
+ c.waitForScreen(GraveScreen.class);c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().is(Items.DIAMOND),100);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-grave-page1");
+ c.runOnClient(m->button(m.gui.screen(),"sanctuary.grave.page").onPress(enter()));c.waitFor(m->((GraveMenu)m.player.containerMenu).page()==1&&m.player.containerMenu.getSlot(0).getItem().is(Items.EMERALD),100);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-grave-page2");
+ click(c,0,true);c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().isEmpty(),100);
+ require(server.computeOnServer(s->GraveService.container((SkullBlockEntity)s.overworld().getBlockEntity(pos)).getItem(54).isEmpty()),"Real packet extraction removes second-page contents server-side");
+ c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var skull=(SkullBlockEntity)p.level().getBlockEntity(pos);var head=Block.getDrops(skull.getBlockState(),p.level(),pos,skull).getFirst();p.level().removeBlock(pos,false);p.getInventory().setItem(2,head);GraveService.open(p,head);});
+ c.waitForScreen(GraveScreen.class);c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().is(Items.DIAMOND),100);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-grave-portable");
+ click(c,0,true);c.waitFor(m->m.player.containerMenu.getSlot(0).getItem().isEmpty(),100);c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ c.runOnClient(m->m.options.guiScale().set(1));c.runOnClient(m->m.resizeGui());
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();GraveService.open(p,p.getInventory().getItem(2));});c.waitForScreen(GraveScreen.class);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-grave-scale1");
+ c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ c.runOnClient(m->m.gui.setScreen(new InventoryScreen(m.player)));c.waitTicks(3);hover(c,38);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-grave-memory");c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.getLanguageManager().setSelected("fr_fr");m.getLanguageManager().onResourceManagerReload(m.getResourceManager());m.gui.setScreen(new FoodDetailsOptionsScreen(null));});c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-food-options-fr");
+ c.runOnClient(m->{require(FoodDetailsClient.label("food_icons").getString().equals("Nourriture"),"French icon label is translated");FoodDetailsClient.options().tooltips=false;require(new ItemStack(Items.COOKED_BEEF).getTooltipImage().isEmpty(),"Tooltip display switch removes food icons");FoodDetailsClient.options().tooltips=true;m.gui.setScreen(new InventoryScreen(m.player));});c.waitTicks(3);hover(c,36);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-food-tooltip-fr");c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setExperienceLevels(10);ProgressionService.send(p,"");});
+ c.waitFor(m->m.player.experienceLevel==10,100);c.runOnClient(m->CycleClient.open(null,"faction"));
+ c.waitFor(m->CycleClient.view!=null&&CycleClient.view.prestige()==0&&m.gui.screen() instanceof CycleFactionScreen,100);c.waitTicks(5);c.takeScreenshot("sanctuary-beta038-faction-founding");
+ c.runOnClient(m->{widgets(m.gui.screen()).filter(EditBox.class::isInstance).map(EditBox.class::cast).findFirst().orElseThrow().setValue("Les Veilleurs");button(m.gui.screen(),"sanctuary.cycle.create").onPress(enter());});c.waitTicks(2);
+ c.runOnClient(m->button(m.gui.screen(),"sanctuary.cycle.confirm").onPress(enter()));
+ c.waitFor(m->CycleClient.view.faction()!=null&&m.player.experienceLevel==0,100);c.waitTicks(5);
+ c.runOnClient(m->require(CycleClient.view.faction().capacity()==1&&CycleClient.view.faction().owner().equals(m.player.getUUID())&&CycleClient.view.prestige()==0,"Real founding packet spends ten levels, no prestige, one founder place"));c.takeScreenshot("sanctuary-beta038-faction-founded");
+ System.out.println("GRAVES FOOD038 client passed: real server food sync, three/ten icons, saturation tooltip, options, two-page chest, packet shift-click and portable grave.");
+ }finally{armed.set(false);}
+ }
+ private static void hover(ClientGameTestContext c,int index){c.runOnClient(m->{var screen=(AbstractContainerScreen>)m.gui.screen();var a=(AbstractContainerScreenAccessor)screen;var slot=screen.getMenu().getSlot(index);var w=m.getWindow();m.mouseHandler.onMove(w.handle(),(a.sanctuary$getLeftPos()+slot.x+8.0)*w.getScreenWidth()/w.getGuiScaledWidth(),(a.sanctuary$getTopPos()+slot.y+8.0)*w.getScreenHeight()/w.getGuiScaledHeight(),0,0);m.mouseHandler.handleAccumulatedMovement();});}
+ private static void click(ClientGameTestContext c,int index,boolean shift){hover(c,index);c.waitTick();if(shift)c.getInput().holdKey(o->o.keyShift);c.runOnClient(m->{var w=m.getWindow();m.mouseHandler.onButton(w.handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,shift?InputConstants.MOD_SHIFT:0),1);m.mouseHandler.onButton(w.handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_LEFT,shift?InputConstants.MOD_SHIFT:0),0);});if(shift)c.getInput().releaseKey(o->o.keyShift);c.waitTicks(2);}
+ private static Stream widgets(ContainerEventHandler c){return c.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static Button button(Screen s,String key){return widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow();}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean ok,String why){if(!ok)throw new AssertionError(why);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/GroupWork018Checks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/GroupWork018Checks.java
new file mode 100644
index 0000000..9fad5d6
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/GroupWork018Checks.java
@@ -0,0 +1,73 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.mining.*;
+import fr.koka.sanctuary.building.*;
+import fr.koka.sanctuary.progression.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.*;
+import net.minecraft.client.KeyMapping;
+import net.minecraft.core.*;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.*;
+
+/** Real client gestures: a changed crosshair must never replace ongoing server work. */
+final class GroupWork018Checks {
+ static void run(ClientGameTestContext c,TestServerContext server) {
+ var previous=c.computeOnClient(m -> InputConstants.getKey(key().saveString()));
+ try {
+ c.runOnClient(m -> {key().setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_R));KeyMapping.resetMapping();});
+ BlockPos base=server.computeOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(8);
+ for(int x=-6;x<=6;x++)for(int z=-6;z<=8;z++)for(int y=-1;y<=6;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1 ? Blocks.BEDROCK : Blocks.AIR).defaultBlockState());
+ var record=ProgressEffects.get(p);while(record.mining()<2)record=record.buy("mining",1);while(record.building()<2)record=record.buy("building",1);
+ p.setAttached(ProgressionService.RECORD,record.json());ProgressEffects.apply(p,record);ProgressionService.send(p,"");
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()+.5);p.getInventory().clearContent();p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.DIAMOND_PICKAXE));
+ for(int i=0;i<8;i++)p.level().setBlockAndUpdate(at.offset(i%4,1,3+i/4),Blocks.STONE.defaultBlockState());
+ p.level().setBlockAndUpdate(at.offset(-3,1,0),Blocks.STONE.defaultBlockState());p.inventoryMenu.broadcastFullState();return at;
+ });
+ var origin=base.offset(0,1,3);var other=base.offset(-3,1,0);
+ c.waitFor(m -> m.player.onGround() && Math.abs(m.player.getY()-base.getY())<.1 && m.player.getMainHandItem().is(Items.DIAMOND_PICKAXE) && ProgressionClient.progress.mining()==2 && m.level.getBlockState(origin).is(Blocks.STONE),600);
+ c.getInput().lookAt(origin);c.waitTicks(3);c.getInput().holdKey(InputConstants.KEY_R);c.waitFor(m -> MiningClient.previewSize()==8,200);
+ c.getInput().holdKey(o -> o.keyAttack);server.waitFor(s -> MiningManager.active(s.getPlayerList().getPlayers().getFirst()),200);
+ c.getInput().lookAt(other);c.runOnClient(m -> m.mouseHandler.onScroll(m.getWindow().handle(),0,-1));
+ c.waitTick();c.runOnClient(m -> require(MiningClient.previewSize()==8 && MiningClient.selectedRank()==2,"Looking at another one-block vein and scrolling keep the original eight-block selection"));
+ c.getInput().holdKey(o -> o.keyRight);c.waitTicks(2);c.getInput().releaseKey(o -> o.keyRight);
+ c.waitFor(m -> m.level.getBlockState(origin).isAir(),500);c.waitTicks(15);
+ server.runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();
+ for(int i=0;i<8;i++)require(p.level().getBlockState(origin.offset(i%4,0,i/4)).isAir(),"Every initially selected block is mined while looking away");
+ require(p.level().getBlockState(other).is(Blocks.STONE) && p.getMainHandItem().getDamageValue()==8,"Holding after completion cannot start the newly aimed vein");
+ });c.takeScreenshot("sanctuary-beta018-mining-locked-camera");
+ c.getInput().releaseKey(o -> o.keyAttack);c.waitTicks(3);c.getInput().lookAt(other);c.waitTicks(3);c.getInput().holdKey(o -> o.keyAttack);
+ c.waitFor(m -> m.level.getBlockState(other).isAir(),300);c.getInput().releaseKey(o -> o.keyAttack);c.getInput().releaseKey(InputConstants.KEY_R);c.waitTicks(3);
+
+ var wall=base.offset(0,2,3);
+ server.runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(base.getX()+.5,base.getY(),base.getZ()+.5);
+ for(int x=-2;x<=2;x++)for(int y=-2;y<=2;y++) {p.level().setBlockAndUpdate(wall.offset(x,y,-1),Blocks.AIR.defaultBlockState());p.level().setBlockAndUpdate(wall.offset(x,y,0),Blocks.STONE.defaultBlockState());}
+ p.level().setBlockAndUpdate(other,Blocks.STONE.defaultBlockState());p.setItemInHand(InteractionHand.MAIN_HAND,new ItemStack(Items.OAK_STAIRS,32));p.inventoryMenu.broadcastFullState();
+ });
+ c.waitFor(m -> m.player.getMainHandItem().is(Items.OAK_STAIRS) && m.level.getBlockState(wall).is(Blocks.STONE) && Math.abs(m.player.getX()-(base.getX()+.5))<.1,400);
+ c.getInput().lookAt(wall);c.waitTicks(3);c.getInput().holdKey(InputConstants.KEY_R);c.waitFor(m -> BuildingClient.previewSize()==25,200);
+ Direction initial=c.computeOnClient(m -> m.player.getDirection());c.getInput().holdKey(o -> o.keyUse);
+ c.waitFor(m -> m.level.getBlockState(wall.north()).is(Blocks.OAK_STAIRS),200);
+ c.getInput().lookAt(other);c.runOnClient(m -> m.mouseHandler.onScroll(m.getWindow().handle(),0,-1));
+ c.getInput().holdKey(o -> o.keyRight);c.waitTicks(2);c.getInput().releaseKey(o -> o.keyRight);
+ c.runOnClient(m -> require(BuildingClient.selectedRank()==2,"Wheel cannot resize the locked surface"));
+ c.waitFor(m -> m.player.getMainHandItem().getCount()==7,400);c.waitTicks(15);
+ server.runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();require(p.getMainHandItem().getCount()==7 && !BuildingManager.active(p),"A held group places one surface only, with exact native material consumption");
+ for(int x=-2;x<=2;x++)for(int y=-2;y<=2;y++) {var state=p.level().getBlockState(wall.offset(x,y,-1));require(state.is(Blocks.OAK_STAIRS) && state.getValue(StairBlock.FACING)==initial,"Grid and stair orientation remain frozen while camera and player move");}
+ });
+ c.getInput().releaseKey(o -> o.keyUse);c.getInput().releaseKey(InputConstants.KEY_R);c.getInput().lookAt(wall);c.waitTicks(3);c.takeScreenshot("sanctuary-beta018-building-locked-camera");
+ System.out.println("GroupWork018 native client passed: camera retarget, movement within reach, frozen ranks, exact initial vein and oriented surface, no automatic restart, release and new gesture");
+ } finally {
+ c.getInput().releaseKey(o -> o.keyAttack);c.getInput().releaseKey(o -> o.keyUse);c.getInput().releaseKey(o -> o.keyRight);c.getInput().releaseKey(InputConstants.KEY_R);
+ c.runOnClient(m -> {key().setKey(previous);KeyMapping.resetMapping();});
+ }
+ }
+ private static KeyMapping key() {return KeyMapping.get("sanctuary.mining.key");}
+ private static void require(boolean ok,String text) {if(!ok)throw new AssertionError(text);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Head050ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Head050ClientChecks.java
new file mode 100644
index 0000000..3f887b2
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Head050ClientChecks.java
@@ -0,0 +1,114 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.boats.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import net.minecraft.world.item.Items;
+import net.minecraft.world.level.block.entity.CampfireBlockEntity;
+import net.minecraft.client.renderer.blockentity.state.*;
+import net.minecraft.client.renderer.entity.state.AvatarRenderState;
+import fr.koka.sanctuary.client.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.CameraType;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.entity.*;
+import net.minecraft.world.item.ItemStack;
+import net.minecraft.world.phys.Vec3;
+
+public final class Head050ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);var pose=new AtomicBoolean(false);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(pose.get()&&m.player!=null){m.player.setYHeadRot(0);m.player.yHeadRotO=0;m.player.setYBodyRot(0);m.player.yBodyRotO=0;}});
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen s){
+ Starter039TestSupport.selectFirst(s);
+ widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->key(b,"sanctuary.progression.enter")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ try(var world=c.worldBuilder().adjustSettings(s->{
+ s.setName("Head050 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setAllowCommands(true);
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());
+ }).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ for(String locale:new String[]{"en_us","fr_fr"})c.runOnClient(m->{
+ m.getLanguageManager().setSelected(locale);m.getLanguageManager().onResourceManagerReload(m.getResourceManager());
+ for(var v:CrewBoats.variants()){
+ var boat=v.type().create(m.level,EntitySpawnReason.COMMAND);
+ require(m.getEntityRenderDispatcher().getRenderer(boat)!=null,"Native renderer for "+v.wood());
+ var mesh=TiledBoatModel.hull(v.columns(),v.rows(),v.raft()).bakeRoot();
+ require(mesh.getAllParts().size()==v.tiles()+3+(v.raft()?0:2*(v.columns()+v.rows())+4),"One floor per cell, only exterior rails and two paddles");
+ require(mesh.getAllParts().stream().allMatch(part->part.xScale==1&&part.yScale==1&&part.zScale==1),"No stretched model part");
+ require(mesh.hasChild("left_paddle")&&mesh.hasChild("right_paddle"),"Exactly one native pair of paddles");
+ require(TiledBoatModel.waterMask(v.columns(),v.rows()).bakeRoot().getAllParts().size()==v.tiles()+1,"Water mask covers every floor tile");
+ require(!new ItemStack(v.item()).getHoverName().getString().startsWith("item.sanctuary"),"Localized item name "+locale);
+ }
+ });
+ var server=world.getServer();
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();p.getAbilities().invulnerable=true;p.getInventory().clearContent();
+ s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"sanctuary time vanilla");
+ s.getCommands().performPrefixedCommand(s.createCommandSourceStack(),"time set noon");
+ p.setPos(p.position().add(0,2,0));
+ });
+ c.runOnClient(m->m.options.setCameraType(CameraType.THIRD_PERSON_FRONT));c.getInput().pressKey(InputConstants.KEY_F1);c.getInput().lookAt(45,-25);pose.set(true);
+ for(var item:List.of(Items.OAK_SHELF,Items.LECTERN,Items.BED.red(),Items.STONE_BUTTON,Items.LEVER,Items.FLOWER_POT,Items.CHISELED_BOOKSHELF)){
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(item));
+ if(item==Items.OAK_SHELF)for(var contents:List.of(Items.DIAMOND,Items.POPPY,Items.GOLD_INGOT)){
+ p.inventoryMenu.setCarried(new ItemStack(contents,3));require(HeadBlockService.use(p,p,true),"Insert real shelf item");
+ }
+ else if(item==Items.LECTERN){p.inventoryMenu.setCarried(new ItemStack(Items.WRITABLE_BOOK));require(HeadBlockService.use(p,p,true),"Insert real lectern book");}
+ else if(item==Items.FLOWER_POT){p.inventoryMenu.setCarried(new ItemStack(Items.POPPY));require(HeadBlockService.use(p,p,true),"Plant real flower");}
+ else if(item==Items.CHISELED_BOOKSHELF){p.inventoryMenu.setCarried(new ItemStack(Items.ENCHANTED_BOOK));require(HeadBlockService.use(p,p,true),"Insert enchanted book");}
+ else if(item==Items.STONE_BUTTON||item==Items.LEVER)HeadBlockService.use(p,p,false);
+ p.level().getEntitiesOfClass(net.minecraft.world.entity.item.ItemEntity.class,p.getBoundingBox().inflate(8)).forEach(Entity::discard);
+ });
+ c.waitTicks(12);
+ c.runOnClient(m->{
+ var h=head(m);var state=HeadCosmeticData.state(HeadCosmeticService.appearance(m.player));
+ if(item==Items.OAK_SHELF)require(h.blockEntity instanceof ShelfRenderState shelf&&Arrays.stream(shelf.items).filter(Objects::nonNull).count()==3,"All three shelf items reach actual native renderer");
+ if(item==Items.LECTERN)require(h.blockEntity instanceof LecternRenderState book&&book.hasBook,"Lectern renderer shows its book");
+ if(item==Items.BED.red())require(!h.block.isEmpty()&&!h.second.isEmpty()&&Math.abs(h.secondZ)+Math.abs(h.secondX)==1,"Complete bed has two adjoining native models");
+ if(item==Items.STONE_BUTTON||item==Items.LEVER)require(h.baseY>=1/16F&&state.getValue(net.minecraft.world.level.block.state.properties.BlockStateProperties.ATTACH_FACE)==net.minecraft.world.level.block.state.properties.AttachFace.FLOOR,"Switch lies on top");
+ if(item==Items.FLOWER_POT)require(state.is(net.minecraft.world.level.block.Blocks.POTTED_POPPY),"Potted flower reaches display");
+ });
+ c.takeScreenshot("sanctuary-beta050-head-"+net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(item).getPath());
+ }
+ pose.set(false);
+ server.runOnServer(s->AccessoryAccess.of(s.getPlayerList().getPlayers().getFirst()).clearContent());
+ c.runOnClient(m->m.options.setCameraType(CameraType.THIRD_PERSON_BACK));
+ for(var shape:List.of(new CrewBoats.Shape(1,2),new CrewBoats.Shape(2,1))){
+ server.runOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var old=p.getVehicle();p.stopRiding();if(old!=null)old.getSelfAndPassengers().toList().reversed().forEach(Entity::discard);
+ var boat=CrewBoats.variant("oak",shape.columns(),shape.rows()).type().create(s.overworld(),EntitySpawnReason.COMMAND);boat.setPos(p.position().add(0,8,0));s.overworld().addFreshEntity(boat);
+ var motor=EntityTypes.ALLAY.create(s.overworld(),EntitySpawnReason.COMMAND);motor.setPos(boat.position());motor.setNoAi(true);s.overworld().addFreshEntity(motor);motor.startRiding(boat);p.startRiding(boat,true,true);
+ });
+ c.waitFor(m->m.player.getVehicle() instanceof CrewBoat b&&b.columns()==shape.columns()&&b.rows()==shape.rows(),200);
+ c.getInput().holdKey(o->o.keyJump);c.getInput().holdKey(o->o.keyUp);c.waitTicks(20);
+ Vec3 from=c.computeOnClient(m->m.player.getVehicle().position());c.waitTicks(40);Vec3 to=c.computeOnClient(m->m.player.getVehicle().position());
+ require(to.y>from.y+2&&to.subtract(from).horizontalDistance()>5,"Each rectangular boat flies from native pilot inputs");
+ c.getInput().lookAt(25,20);c.waitTicks(5);c.takeScreenshot("sanctuary-beta050-boat-"+shape.columns()+"x"+shape.rows());
+ c.getInput().releaseKey(o->o.keyUp);c.getInput().releaseKey(o->o.keyJump);
+ }
+ System.out.println("HEAD050_CLIENT_PASS: 88 tiled models FR/EN, shelf contents, lectern book, complete bed, switches on crown, potted flower, enchanted books and two new rectangular flights");
+ }
+ }
+ private static HeadCosmeticState head(net.minecraft.client.Minecraft m){
+ var renderer=m.getEntityRenderDispatcher().getRenderer(m.player);
+ var state=renderer.createRenderState(m.player,1);
+ return ((HeadCosmeticState.Access)state).sanctuary$headCosmetic();
+ }
+ private static Stream widgets(ContainerEventHandler root){return root.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static boolean key(Button button,String key){return button.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key);}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean yes,String message){if(!yes)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/HeadBlocks043ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/HeadBlocks043ClientChecks.java
new file mode 100644
index 0000000..ba0a7da
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/HeadBlocks043ClientChecks.java
@@ -0,0 +1,51 @@
+package fr.koka.sanctuary.gametest;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.*;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.*;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.world.item.*;
+import net.minecraft.world.inventory.ContainerInput;
+import net.minecraft.world.Container;
+import com.mojang.blaze3d.platform.InputConstants;
+public class HeadBlocks043ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c){
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen s){
+ Starter039TestSupport.selectFirst(s);widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ widgets(s).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getMessage().getString().startsWith("Enter Sanctuary")).findFirst().orElseThrow().onPress(enter());onboarding.set(false);
+ }});
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Head blocks043 "+UUID.randomUUID());s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);s.setAllowCommands(true);s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary_test:flat")).findFirst().orElseThrow());}).create()){
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);var server=world.getServer();
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var r=ProgressRecord.INITIAL;for(int i=1;i<6;i++)r=r.buy("inventory",i);p.setAttached(ProgressionService.RECORD,r.json());ProgressEffects.apply(p,r);ProgressionService.send(p,"");AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.CHEST));var head=HeadBlockService.session(p);HeadBlockService.scoped(head,()->{((Container)head.entity).setItem(0,new ItemStack(Items.DIAMOND,11));return null;});});
+ c.waitTicks(5);c.runOnClient(m->{m.gui.setScreen(new InventoryScreen(m.player));ClientPlayNetworking.send(new HeadCosmeticPayloads.Interact(-1));});
+ c.waitFor(m->m.gui.screen() instanceof ContainerScreen,150);c.waitTicks(5);c.takeScreenshot("sanctuary-beta043-head-chest-six-rows");
+ c.runOnClient(m->m.gameMode.handleContainerInput(m.player.containerMenu.containerId,0,0,ContainerInput.QUICK_MOVE,m.player));
+ c.waitFor(m->m.player.getInventory().countItem(Items.DIAMOND)==11,150);
+ c.waitTicks(10);
+ require(server.computeOnServer(s->s.getPlayerList().getPlayers().getFirst().getInventory().countItem(Items.DIAMOND)==11),"Native client shift-click reaches server exactly once");
+ server.runOnServer(s->AccessoryAccess.of(s.getPlayerList().getPlayers().getFirst()).removeItemNoUpdate(AccessoryContainer.HEAD));
+ c.waitFor(m->m.player.containerMenu==m.player.inventoryMenu,150);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.FURNACE));HeadBlockService.use(p,p,false);});
+ c.waitFor(m->m.gui.screen() instanceof FurnaceScreen,150);c.waitTicks(5);c.takeScreenshot("sanctuary-beta043-head-furnace");c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(3);
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.setGameMode(net.minecraft.world.level.GameType.CREATIVE);AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,new ItemStack(Items.COMMAND_BLOCK));require(HeadBlockService.use(p,p,false),"Native operator editor requested");});
+ c.waitFor(m->m.gui.screen() instanceof CommandBlockEditScreen,150);c.waitTicks(4);c.takeScreenshot("sanctuary-beta043-head-operator-editor");c.getInput().pressKey(InputConstants.KEY_ESCAPE);
+ System.out.println("HEAD043_CLIENT_PASS: inventory gesture, six rows, real shift-click packet, removal closes menu, furnace and native operator editor");
+ }
+ }
+ private static Stream widgets(ContainerEventHandler root){return root.children().stream().flatMap(w->w instanceof ContainerEventHandler child?Stream.concat(Stream.of(w),widgets(child)):Stream.of(w));}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean yes,String message){if(!yes)throw new AssertionError(message);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/HeadCosmetic021ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/HeadCosmetic021ClientChecks.java
new file mode 100644
index 0000000..7f8160e
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/HeadCosmetic021ClientChecks.java
@@ -0,0 +1,142 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.authlib.GameProfile;
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.cosmetics.*;
+import fr.koka.sanctuary.inventory.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.*;
+import net.minecraft.client.gui.components.*;
+import net.minecraft.client.gui.components.events.*;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.*;
+import net.minecraft.client.renderer.blockentity.state.*;
+import net.minecraft.core.component.DataComponents;
+import net.minecraft.core.registries.Registries;
+import net.minecraft.network.*;
+import net.minecraft.network.chat.*;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.network.protocol.PacketFlow;
+import net.minecraft.network.protocol.game.*;
+import net.minecraft.server.level.*;
+import net.minecraft.server.network.*;
+import net.minecraft.world.InteractionHand;
+import net.minecraft.world.entity.player.Player;
+import net.minecraft.world.item.*;
+import net.minecraft.world.level.block.*;
+import net.minecraft.world.level.block.entity.*;
+import net.minecraft.world.phys.Vec3;
+
+public final class HeadCosmetic021ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var onboarding=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m->{if(onboarding.get()&&m.gui.screen() instanceof HelloWorldScreen screen){
+ widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(enter());
+ button(screen,"sanctuary.progression.enter").onPress(enter());onboarding.set(false);
+ }});
+ int scale=c.computeOnClient(m->m.options.guiScale().get());
+ try(var world=c.worldBuilder().adjustSettings(s->{s.setName("Sanctuary beta021 cosmetics "+UUID.randomUUID());s.setSeed("42");
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null&&e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);}).create()) {
+ world.getConnection().waitForChunksDownload(2400);c.waitFor(m->m.player!=null&&ProgressionClient.active()&&m.gui.screen()==null,1200);
+ var server=world.getServer();
+ UUID remote=server.computeOnServer(s->{
+ var p=s.getPlayerList().getPlayers().getFirst();var at=p.blockPosition().above(6);
+ for(int x=-8;x<=8;x++)for(int z=-8;z<=8;z++)for(int y=-1;y<9;y++)p.level().setBlockAndUpdate(at.offset(x,y,z),(y==-1?Blocks.STONE:Blocks.AIR).defaultBlockState());
+ p.teleportTo(at.getX()+.5,at.getY(),at.getZ()+.5);p.getInventory().clearContent();
+ var profile=new GameProfile(UUID.randomUUID(),"Porteur");
+ var other=new ServerPlayer(s,p.level(),profile,ClientInformation.createDefault());
+ var connection=new Connection(PacketFlow.SERVERBOUND);new io.netty.channel.embedded.EmbeddedChannel(connection);
+ other.connection=new ServerGamePacketListenerImpl(s,connection,other,CommonListenerCookie.createInitial(profile,false));
+ other.setPos(p.position().add(0,0,2.5));other.setYRot(180);other.setYHeadRot(180);other.setNoGravity(true);
+ other.setAttached(ProgressionService.RECORD,ProgressRecord.INITIAL.json());ProgressEffects.apply(other,ProgressRecord.INITIAL);
+ other.connection.handleAcceptPlayerLoad(new ServerboundPlayerLoadedPacket());
+ p.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(other)));p.level().addNewPlayer(other);
+ equip(other,new ItemStack(Items.OAK_SIGN));return other.getUUID();
+ });
+ c.waitFor(m->m.level.getPlayerByUUID(remote)!=null&&HeadCosmeticService.appearance(m.level.getPlayerByUUID(remote)).is(Items.OAK_SIGN),240);
+ var aim=c.computeOnClient(m->m.level.getPlayerByUUID(remote).getEyePosition().add(0,.45,0));face(c,aim);c.waitTicks(4);
+ click(c);c.waitFor(m->m.gui.screen() instanceof AbstractSignEditScreen,100);
+ c.runOnClient(m->{for(char ch:"Bonjour!".toCharArray())m.gui.screen().charTyped(new CharacterEvent(ch));});
+ c.takeScreenshot("sanctuary-beta021-sign-editor");c.getInput().pressKey(InputConstants.KEY_ESCAPE);
+ c.waitFor(m->{var stack=HeadCosmeticService.appearance(m.level.getPlayerByUUID(remote));return List.of(DataComponents.SIGN_TEXT_FRONT,DataComponents.SIGN_TEXT_BACK).stream().anyMatch(k->stack.getOrDefault(k,SignText.EMPTY).getMessages(false).getFirst().getString().equals("Bonjour!"));},200);
+ c.runOnClient(m->{var h=render(m,m.level.getPlayerByUUID(remote));require(h.blockEntity instanceof SignRenderState&&!h.block.isEmpty(),"Sign has native board and native text render state");
+ var sign=(SignRenderState)h.blockEntity;require(Stream.concat(sign.frontText.getMessages(false).stream(),sign.backText.getMessages(false).stream()).anyMatch(t->t.getString().equals("Bonjour!")),"Written text reaches native renderer");});
+ c.waitTicks(3);c.takeScreenshot("sanctuary-beta021-sign-written");
+ server.runOnServer(s->{var p=(ServerPlayer)s.getPlayerList().getPlayers().getFirst().level().getPlayerByUUID(remote);equip(p,new ItemStack(Items.OAK_HANGING_SIGN));});
+ c.waitFor(m->HeadCosmeticService.appearance(m.level.getPlayerByUUID(remote)).is(Items.OAK_HANGING_SIGN),100);c.waitTicks(5);click(c);
+ c.waitFor(m->m.gui.screen() instanceof HangingSignEditScreen,100);c.runOnClient(m->{for(char ch:"Suspendu".toCharArray())m.gui.screen().charTyped(new CharacterEvent(ch));});c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitTicks(6);
+ // Native item models and components are tested through actual server synchronization and render extraction.
+ List models=new ArrayList<>(List.of(Items.OAK_DOOR,Items.IRON_DOOR,Items.BED.red(),Items.STRAW_BED,
+ Items.MINECART,Items.CHEST_MINECART,Items.HOPPER_MINECART,Items.FURNACE_MINECART,Items.TNT_MINECART,Items.COMMAND_BLOCK_MINECART,
+ Items.COBWEB,Items.OAK_LEAVES,Items.GLASS,Items.STAINED_GLASS.purple(),Items.TINTED_GLASS,Items.COPPER_GOLEM_STATUE.waxed().oxidized(),Items.CAKE,Items.PISTON,Items.COW_SPAWN_EGG));
+ models.addAll(Items.BANNER.asList());
+ for(Item item:models) {
+ server.runOnServer(s->equip((ServerPlayer)s.getPlayerList().getPlayers().getFirst().level().getPlayerByUUID(remote),new ItemStack(item)));
+ c.waitFor(m->HeadCosmeticService.appearance(m.level.getPlayerByUUID(remote)).is(item),100);
+ c.runOnClient(m->{var h=render(m,m.level.getPlayerByUUID(remote));var state=HeadCosmeticData.state(new ItemStack(item));
+ if(state!=null&&(state.getBlock() instanceof DoorBlock||state.getBlock() instanceof BedBlock))require(!h.block.isEmpty()&&!h.second.isEmpty(),"Both halves render: "+item);
+ if(item instanceof MinecartItem)require(h.entity!=null&&h.item.isEmpty(),"Minecart uses entity model: "+item);
+ if(state!=null&&state.getBlock() instanceof AbstractBannerBlock)require(h.blockEntity instanceof BannerRenderState b&&b.baseColor==((BannerItem)item).getColor(),"Banner keeps native base color: "+item);
+ if(HeadCosmeticData.wrapsHead(state))require(h.wraps,"Block encloses head: "+item);
+ if(item instanceof SpawnEggItem)require(h.entity==null&&!h.item.isEmpty(),"Egg retains existing item rendering");
+ });c.waitTicks(2);
+ if(List.of(Items.OAK_DOOR,Items.BED.red(),Items.MINECART,Items.COBWEB,Items.OAK_LEAVES,Items.GLASS,Items.CAKE).contains(item))c.takeScreenshot("sanctuary-beta021-"+net.minecraft.core.registries.BuiltInRegistries.ITEM.getKey(item).getPath());
+ }
+ server.runOnServer(s->{var p=(ServerPlayer)s.getPlayerList().getPlayers().getFirst().level().getPlayerByUUID(remote);equip(p,net.minecraft.world.entity.raid.Raid.getOminousBannerInstance(p.registryAccess().lookupOrThrow(Registries.BANNER_PATTERN)));});
+ c.waitFor(m->HeadCosmeticService.appearance(m.level.getPlayerByUUID(remote)).getOrDefault(DataComponents.BANNER_PATTERNS,BannerPatternLayers.EMPTY).layers().size()>0,100);
+ c.runOnClient(m->{var p=m.level.getPlayerByUUID(remote);var b=(BannerRenderState)render(m,p).blockEntity;
+ require(b.patterns.equals(HeadCosmeticService.appearance(p).get(DataComponents.BANNER_PATTERNS))&&!b.patterns.layers().isEmpty(),"Ominous banner keeps every native pattern");});
+ c.waitTicks(3);c.takeScreenshot("sanctuary-beta021-ominous-banner");
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();equip(p,new ItemStack(Items.CAKE));
+ p.getInventory().setItem(0,new ItemStack(Items.DYED_CANDLE.blue(),2));p.getInventory().setItem(1,new ItemStack(Items.FLINT_AND_STEEL));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->HeadCosmeticService.appearance(m.player).is(Items.CAKE)&&m.player.getInventory().getItem(0).is(Items.DYED_CANDLE.blue()),100);
+ c.runOnClient(m->{m.options.guiScale().set(2);m.resizeGui();m.gui.setScreen(new InventoryScreen(m.player));
+ require(widgets(m.gui.screen()).filter(Button.class::isInstance).map(Button.class::cast).noneMatch(b->b.getMessage().getString().equalsIgnoreCase("Cosmetic")),"No large cosmetic button");});
+ clickSlot(c,36,0);c.takeScreenshot("sanctuary-beta021-cursor-candle");c.waitFor(m->m.player.inventoryMenu.getCarried().is(Items.DYED_CANDLE.blue()),100);
+ clickSlot(c,66,1);c.waitFor(m->HeadCosmeticData.candle(HeadCosmeticService.appearance(m.player))==Items.DYED_CANDLE.blue()&&m.player.inventoryMenu.getCarried().getCount()==1,100);
+ clickSlot(c,36,0);clickSlot(c,37,0);c.waitFor(m->m.player.inventoryMenu.getCarried().is(Items.FLINT_AND_STEEL),100);
+ clickSlot(c,66,1);c.waitFor(m->HeadCosmeticData.lit(HeadCosmeticService.appearance(m.player))&&m.player.inventoryMenu.getCarried().getDamageValue()==1,100);
+ clickSlot(c,37,0);c.waitFor(m->m.player.inventoryMenu.getCarried().isEmpty(),100);
+ c.takeScreenshot("sanctuary-beta021-inventory-candle");
+ for(int slot:new int[]{5,6,7,8,45,64,65,66}){hover(c,slot);c.takeScreenshot("sanctuary-beta021-slot-"+slot);}
+ clickSlot(c,66,1);c.waitFor(m->!HeadCosmeticData.lit(HeadCosmeticService.appearance(m.player)),100);
+ c.waitTicks(5);clickSlot(c,66,1);c.waitFor(m->HeadCosmeticData.candle(HeadCosmeticService.appearance(m.player))==Items.AIR&&m.player.inventoryMenu.getCarried().is(Items.DYED_CANDLE.blue()),100);
+ clickSlot(c,36,0);clickSlot(c,66,0);c.waitFor(m->m.player.inventoryMenu.getCarried().is(Items.CAKE)&&HeadCosmeticService.appearance(m.player).isEmpty(),100);
+ clickSlot(c,38,0);c.waitFor(m->m.player.inventoryMenu.getCarried().isEmpty(),100);
+ server.runOnServer(s->equip(s.getPlayerList().getPlayers().getFirst(),new ItemStack(Items.OAK_SIGN)));c.waitFor(m->HeadCosmeticService.appearance(m.player).is(Items.OAK_SIGN),100);c.waitTicks(3);
+ clickSlot(c,66,1);c.waitFor(m->m.player.inventoryMenu.getCarried().is(Items.OAK_SIGN)&&m.gui.screen() instanceof InventoryScreen,100);
+ clickSlot(c,39,0);c.waitFor(m->m.player.inventoryMenu.getCarried().isEmpty(),100);
+ c.runOnClient(m->m.gui.setScreen(null));
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();var other=(ServerPlayer)p.level().getPlayerByUUID(remote);
+ equip(other,new ItemStack(Items.TNT));other.setHealth(6);other.hurtServer(other.level(),other.damageSources().generic(),1);
+ });
+ c.waitFor(m->{for(var entity:m.level.entitiesForRendering())if(entity instanceof net.minecraft.world.entity.item.PrimedTnt)return true;return false;},80);
+ c.waitTicks(3);c.takeScreenshot("sanctuary-beta021-tnt-primed");
+ // Move the observer out of the explosion; the armed avatar and native terrain remain in the laboratory.
+ server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.teleportTo(p.getX()+12,p.getY(),p.getZ());});
+ c.waitTicks(90);
+ c.runOnClient(m->{for(var entity:m.level.entitiesForRendering())require(!(entity instanceof net.minecraft.world.entity.item.PrimedTnt),"Native TNT completed its fuse and disappeared after explosion");});
+ System.out.println("HeadCosmetic021 client passed: actual remote sign/hanging-sign clicks and text packets, 16 banner colors and ominous patterns, complete beds/doors, six minecarts, wrapping blocks, unchanged egg, inventory cake/candle actions and own-sign exclusion.");
+ } finally {onboarding.set(false);c.runOnClient(m->{m.options.guiScale().set(scale);m.resizeGui();});}
+ }
+ private static HeadCosmeticState render(Minecraft m,Player p){return ((HeadCosmeticState.Access)m.getEntityRenderDispatcher().extractEntity(p,.5F)).sanctuary$headCosmetic();}
+ private static void equip(ServerPlayer p,ItemStack item){AccessoryAccess.of(p).setItem(AccessoryContainer.HEAD,item);}
+ private static void face(ClientGameTestContext c,Vec3 at){c.runOnClient(m->{var d=at.subtract(m.player.getEyePosition());m.player.setYRot((float)Math.toDegrees(Math.atan2(-d.x,d.z)));m.player.setXRot((float)-Math.toDegrees(Math.atan2(d.y,d.horizontalDistance())));});}
+ private static void hover(ClientGameTestContext c,int index){c.runOnClient(m->{var screen=(AbstractContainerScreen>)m.gui.screen();var a=(fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor)screen;var slot=screen.getMenu().getSlot(index);var w=m.getWindow();double x=a.sanctuary$getLeftPos()+slot.x+8.0,y=a.sanctuary$getTopPos()+slot.y+8.0;double sx=(double)w.getScreenWidth()/w.getGuiScaledWidth(),sy=(double)w.getScreenHeight()/w.getGuiScaledHeight();m.mouseHandler.onMove(w.handle(),x*sx,y*sy,(x-m.mouseHandler.getScaledXPos(w))*sx,(y-m.mouseHandler.getScaledYPos(w))*sy);m.mouseHandler.handleAccumulatedMovement();});c.waitTicks(2);}
+ private static void clickSlot(ClientGameTestContext c,int slot,int button){hover(c,slot);c.runOnClient(m->m.mouseHandler.onButton(m.getWindow().handle(),new MouseButtonInfo(button==0?InputConstants.MOUSE_BUTTON_LEFT:InputConstants.MOUSE_BUTTON_RIGHT,0),1));c.waitTick();c.runOnClient(m->m.mouseHandler.onButton(m.getWindow().handle(),new MouseButtonInfo(button==0?InputConstants.MOUSE_BUTTON_LEFT:InputConstants.MOUSE_BUTTON_RIGHT,0),0));c.waitTicks(3);}
+ private static void click(ClientGameTestContext c){c.runOnClient(m->m.mouseHandler.onButton(m.getWindow().handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_RIGHT,0),1));c.waitTick();c.runOnClient(m->m.mouseHandler.onButton(m.getWindow().handle(),new MouseButtonInfo(InputConstants.MOUSE_BUTTON_RIGHT,0),0));c.waitTick();}
+ private static Stream widgets(ContainerEventHandler c){return c.children().stream().flatMap(w->w instanceof ContainerEventHandler nested?Stream.concat(Stream.of(w),widgets(nested)):Stream.of(w));}
+ private static Button button(Screen screen,String key){return widgets(screen).filter(Button.class::isInstance).map(Button.class::cast).filter(w->w.getMessage().getContents() instanceof TranslatableContents t&&t.getKey().equals(key)).findFirst().orElseThrow(()->new AssertionError("Missing "+key));}
+ private static KeyEvent enter(){return new KeyEvent(InputConstants.KEY_RETURN,0,0);}
+ private static void require(boolean value,String text){if(!value)throw new AssertionError(text);}
+}
diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Inventory010ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Inventory010ClientChecks.java
new file mode 100644
index 0000000..f9078cc
--- /dev/null
+++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/gametest/Inventory010ClientChecks.java
@@ -0,0 +1,151 @@
+package fr.koka.sanctuary.gametest;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import fr.koka.sanctuary.client.*;
+import fr.koka.sanctuary.inventory.*;
+import fr.koka.sanctuary.progression.*;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
+import net.fabricmc.fabric.api.client.gametest.v1.FabricClientGameTest;
+import net.fabricmc.fabric.api.client.gametest.v1.context.ClientGameTestContext;
+import net.minecraft.client.gui.components.Button;
+import net.minecraft.client.gui.screens.inventory.*;
+import net.minecraft.client.gui.screens.worldselection.WorldCreationUiState;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.core.registries.BuiltInRegistries;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.contents.TranslatableContents;
+import net.minecraft.world.SimpleMenuProvider;
+import net.minecraft.world.inventory.*;
+import net.minecraft.world.item.*;
+
+/** Native screen/packet checks; exclusively creates an ignored development world. */
+public final class Inventory010ClientChecks implements FabricClientGameTest {
+ public void runTest(ClientGameTestContext c) {
+ var armed=new AtomicBoolean(true);
+ ClientTickEvents.END_CLIENT_TICK.register(m -> {
+ if(armed.get() && m.gui.screen() instanceof HelloWorldScreen screen) {
+ var press=new KeyEvent(InputConstants.KEY_RETURN,0,0);
+ screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b->b.getClass().getSimpleName().equals("ColorButton")).findFirst().orElseThrow().onPress(press);
+ screen.children().stream().filter(Button.class::isInstance).map(Button.class::cast)
+ .filter(b->b.getMessage().getContents() instanceof TranslatableContents t && t.getKey().equals("sanctuary.progression.enter"))
+ .findFirst().orElseThrow().onPress(press);armed.set(false);
+ }
+ });
+ int scale=c.computeOnClient(m -> m.options.guiScale().get());
+ try(var world=c.worldBuilder().adjustSettings(s -> {
+ s.setName("Sanctuary beta012 inventory "+UUID.randomUUID());
+ s.setWorldType(s.getNormalPresetList().stream().filter(e->e.preset()!=null && e.preset().getRegisteredName().equals("sanctuary:sanctuary")).findFirst().orElseThrow());
+ s.setSeed("42");s.setGameMode(WorldCreationUiState.SelectedGameMode.SURVIVAL);
+ }).create()) {
+ world.getConnection().waitForChunksDownload(2400);
+ c.waitFor(m->m.player!=null && ProgressionClient.active() && m.gui.screen()==null,1200);
+ for(int rank=0;rank<=5;rank++) {
+ if(rank>0) world.getServer().runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();p.giveExperienceLevels(16);
+ require(ProgressionService.purchase(p,"inventory",ProgressEffects.get(p).revision()),"Server purchase succeeds");ProgressionService.send(p,"");
+ });
+ int expected=rank;c.waitFor(m->ProgressionClient.progress.inventory()==expected,200);
+ for(int gui:new int[]{1,0}) {
+ c.runOnClient(m -> {m.options.guiScale().set(gui);m.resizeGui();m.gui.setScreen(new InventoryScreen(m.player));bounds((AbstractContainerScreen>)m.gui.screen());});
+ c.waitTick();c.takeScreenshot("sanctuary-beta012-inventory-"+(rank+1)+"-gui-"+gui);
+ c.runOnClient(m->m.player.closeContainer());
+ }
+ }
+ for(var type:BuiltInRegistries.MENU) {
+ boolean supported=world.getServer().computeOnServer(server -> {
+ var p=server.getPlayerList().getPlayers().getFirst();
+ var ext=(SanctuaryExtendedInventoryMenu)type.create(99,p.getInventory());ext.sanctuary$ensureExtendedInventory();
+ return ext.sanctuary$hasExtendedInventory();
+ });
+ if(!supported)continue;
+ c.runOnClient(m -> Inventory012RenderProbe.reset());
+ String name=BuiltInRegistries.MENU.getKey(type).getPath();
+ world.getServer().runOnServer(s -> {
+ var p=s.getPlayerList().getPlayers().getFirst();p.openMenu(new SimpleMenuProvider((id,inv,owner)->type.create(id,inv),Component.literal(name)));
+ if(p.containerMenu instanceof ChestMenu chest) {
+ for(int i=0;im.gui.screen() instanceof AbstractContainerScreen> screen && screen.getMenu().getType()==type,200);
+ c.runOnClient(m -> {
+ var screen=(AbstractContainerScreen>)m.gui.screen();bounds(screen);
+ var ext=(SanctuaryExtendedInventoryMenu)screen.getMenu();
+ if(ext.sanctuary$hasExtendedInventory()) {
+ var canvas=(fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor)screen;
+ screen.mouseScrolled(canvas.sanctuary$getLeftPos()+88,screen.height-30,0,10);
+ bounds(screen);
+ }
+ });
+ c.waitTick();c.takeScreenshot("sanctuary-beta012-"+name+"-minimum");
+ c.runOnClient(m -> {
+ var canvas=(fr.koka.sanctuary.mixin.client.AbstractContainerScreenAccessor)m.gui.screen();
+ require(Inventory012RenderProbe.captured && Inventory012RenderProbe.x==canvas.sanctuary$getLeftPos()
+ && Inventory012RenderProbe.y==canvas.sanctuary$getTopPos(),"Native background origin matches actual slot origin: "+name);
+ });
+ c.runOnClient(m->m.player.closeContainer());c.waitForScreen(null);
+ }
+ world.getServer().runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();p.getInventory().setItem(60,new ItemStack(Items.DIAMOND,23));p.inventoryMenu.broadcastChanges();});
+ c.waitFor(m->m.player.getInventory().getItem(60).getCount()==23,200);
+ c.runOnClient(m->{m.gui.setScreen(new InventoryScreen(m.player));bounds((AbstractContainerScreen>)m.gui.screen());});
+ c.takeScreenshot("sanctuary-beta012-last-row-synced");
+ c.getInput().pressKey(InputConstants.KEY_TAB);
+ c.waitFor(m->m.player.getInventory().getItem(60).isEmpty() && m.player.getInventory().getItem(51).getCount()==23,200);
+ c.runOnClient(m -> {
+ var screen=m.gui.screen();
+ var backwards=new KeyEvent(InputConstants.KEY_TAB,0,1);
+ net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents.allowKeyPress(screen).invoker().allowKeyPress(screen,backwards);
+ net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents.afterKeyRelease(screen).invoker().afterKeyRelease(screen,backwards);
+ });
+ c.waitFor(m->m.player.getInventory().getItem(60).getCount()==23,200);
+ c.runOnClient(m -> {
+ var key=net.minecraft.client.KeyMapping.get("screen.sanctuary.inventory.rotate_key");
+ key.setKey(InputConstants.Type.KEYBOARD.getOrCreate(InputConstants.KEY_E));net.minecraft.client.KeyMapping.resetMapping();
+ });
+ c.getInput().pressKey(InputConstants.KEY_E);
+ c.waitFor(m->m.gui.screen() instanceof InventoryScreen && m.player.getInventory().getItem(51).getCount()==23,200);
+ c.getInput().pressKey(InputConstants.KEY_ESCAPE);c.waitForScreen(null);
+ c.runOnClient(m -> {
+ var key=net.minecraft.client.KeyMapping.get("screen.sanctuary.inventory.rotate_key");key.setKey(key.getDefaultKey());net.minecraft.client.KeyMapping.resetMapping();
+ });
+ world.getServer().runOnServer(server -> {var p=server.getPlayerList().getPlayers().getFirst();p.setExperienceLevels(0);ProgressionService.send(p,"");});
+ c.runOnClient(m -> ProgressionClient.open(null));c.waitTick();
+ c.runOnClient(m -> require(buttons(m.gui.screen())
+ .noneMatch(b -> b.visible && b.getMessage().getString().equals("+")),"No unaffordable plus button"));
+ c.takeScreenshot("sanctuary-beta012-progression-no-xp");
+ world.getServer().runOnServer(server -> {
+ var p=server.getPlayerList().getPlayers().getFirst();server.getPlayerList().op(p.nameAndId());ProgressionService.send(p,"");
+ });
+ c.waitFor(m -> ProgressionClient.operator,200);
+ world.getServer().runOnServer(server -> {var p=server.getPlayerList().getPlayers().getFirst();p.giveExperienceLevels(30);ProgressionService.send(p,"");});
+ c.waitFor(m -> m.player.experienceLevel==30,200);c.waitTick();c.takeScreenshot("sanctuary-beta012-progression-ready");
+ c.runOnClient(m -> buttons(m.gui.screen())
+ .filter(b -> b.visible && b.active && b.getMessage().getString().equals("+")).findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0)));
+ c.waitFor(m -> ProgressionClient.progress.vitality()==1 && m.player.experienceLevel==29,200);
+ c.takeScreenshot("sanctuary-beta012-progression-operator");
+ c.runOnClient(m -> buttons(m.gui.screen())
+ .filter(b -> b.visible && b.active && b.getMessage().getString().equals("−")).findFirst().orElseThrow().onPress(new KeyEvent(InputConstants.KEY_RETURN,0,0)));
+ c.waitFor(m -> ProgressionClient.progress.vitality()==0 && m.player.experienceLevel==30,200);
+ c.runOnClient(m -> require(ProgressionClient.progress.history().getLast().target().equals("remove_vitality"),"Native UI refund is recorded by the server"));
+
+ c.runOnClient(m->m.player.closeContainer());
+ } finally {armed.set(false);c.runOnClient(m->{m.options.guiScale().set(scale);m.resizeGui();});}
+ }
+ private static java.util.stream.Stream