Compare commits

...
5 Commits
Author SHA1 Message Date
koka b97ec6c9e8 Fix dimension inventory synchronization and opaque PBR sheen in beta.151
Build Sanctuary / build (push) Waiting to run
2026-09-18 22:11:32 +02:00
koka 448e2febff Keep soil matte and align solar wave glints with native sunset in beta.150
Build Sanctuary / build (push) Waiting to run
2026-09-18 21:47:33 +02:00
koka 1c23d0c17e Separate PBR and SSR ranges and remove foliage sheen in beta.149
Build Sanctuary / build (push) Waiting to run
2026-09-18 20:40:47 +02:00
koka 6f871446bb Balance PBR at 50 percent and soften solar water highlights in beta.148
Build Sanctuary / build (push) Waiting to run
2026-09-18 20:12:08 +02:00
koka 96a36d99be Fix close PBR flicker, soften moonlight and extend range in beta.147
Build Sanctuary / build (push) Waiting to run
2026-09-18 19:56:03 +02:00
27 changed files with 798 additions and 49 deletions
+1
View File
@@ -11,6 +11,7 @@ La vision est dans `docs/vision.md` ; elle décrit aussi des fonctionnalités fu
- Ne pas modifier un monde existant, régénérer des chunks, changer un format de sauvegarde ou activer une expansion sans contrat de migration explicite.
- Garder les identifiants `sanctuary:*` stables et documenter toute évolution de la génération avec graine et version.
- Ajouter les libellés FR/EN des nouvelles interfaces. Vérifier les dépendances pour la version Minecraft exacte ; aucune compatibilité supposée à partir du nom d'un mod.
- Les tests graphiques Sanctuary ciblent uniquement Vulkan. OpenGL est abandonné comme cible de validation depuis le 18 septembre 2026 : ne plus lancer de suite OpenGL ni revendiquer sa prise en charge à partir des essais historiques.
- 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 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.
+34 -2
View File
@@ -1,5 +1,37 @@
# Sanctuary
## beta.151 — Nether, reflets des blocs et SSR (pack local)
Synchronisation de l’inventaire au changement de dimension, retour de l’intensité
spéculaire des blocs solides à son niveau antérieur et bois mat avec relief.
SSR activé par défaut à 20 %, choix sauvegardés conservés.
[Contrat et vérifications](docs/nether-pbr-beta151.md).
## beta.150 — Terre mate et reflets du coucher de soleil (pack local)
Terre mate avec relief conservé. Le soleil et son reflet se colorent ensemble
en suivant la couleur et la transition du coucher de soleil natif de Minecraft.
Les accents lumineux suivent les normales animées des vaguelettes et leur
orientation vers le soleil et la caméra. [Contrat et vérifications](docs/matte-dirt-beta150.md).
## beta.149 — Distances PBR / SSR et feuillage (pack local)
Deux distances séparées, 64 blocs par défaut chacune, jusqu’à 256 blocs.
Option renommée « SSR » et suppression du voile spéculaire sur le feuillage.
[Contrat et vérifications](docs/independent-distances-beta149.md).
## beta.148 — PBR 50 % et soleil diffus (pack local)
PBR à 50 %, reflet solaire plus doux avec fusion par texel préservant
la texture de l’eau. SSR à 20 %, portée commune jusqu’à 256 blocs.
[Contrat et vérifications](docs/pbr-soft-sun-beta148.md).
## beta.147 — PBR proche, lune et portée 256 (pack local)
Correction du scintillement à très courte distance, reflet lunaire adouci
et distance commune PBR/SSR jusqu’à 256 blocs.
[Contrat et vérifications](docs/pbr-close-range-beta147.md).
## beta.146 — Reflets et transitions de l’eau (pack local)
Correction du masquage des personnages en F5 et sélection stricte des surfaces PBR.
@@ -2336,7 +2368,7 @@ Depuis beta.090, la cible est **Minecraft 26.3 finale**, publiée le 15 septembr
| Fabric API | 0.160.5+26.3 |
| Fabric Loom | 1.17.20 |
| Gradle Wrapper | 9.5.1, distribution vérifiée par SHA-256 |
| Sanctuary / pack | beta.146 |
| Sanctuary / pack | beta.151 |
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
@@ -2365,7 +2397,7 @@ décrits dans [Validation](docs/testing.md).
Résultats :
- `mods/sanctuary/build/libs/sanctuary-beta.146.jar` : mod à installer avec
- `mods/sanctuary/build/libs/sanctuary-beta.151.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).
+68
View File
@@ -0,0 +1,68 @@
# RENDER-149 — Distances PBR et SSR indépendantes
Branche `codex/separate-reflection-distances-beta149`, socle beta.148 `6f87144`.
- Deux réglages : Distance PBR et Distance SSR, chacun à 64 blocs par défaut.
Choix 16, 32, 64, 128, 256 blocs, enregistrés séparément.
- Le PBR conserve son propre fondu et sa propre limite de portée. Le SSR
utilise sa distance pour les surfaces réfléchissantes et sa recherche de
paysage. Le maillage et la sélection des surfaces couvrent le maximum des
deux portées actives, afin que réduire l’une ne coupe pas l’autre.
- Ancien défaut commun 32 migré une fois vers PBR 64 ; nouveau réglage SSR 64.
Les anciennes valeurs PBR différentes de 32 restent conservées. Après la
migration, un choix explicite 32 est conservé comme les autres valeurs.
- Option nommée exactement « SSR » en français et en anglais, sans la mention
« expérimental ». Les autres options expérimentales du jeu sont inchangées.
- Intensités PBR 50 % et SSR 20 %, rendu solaire diffus de beta.148 conservés.
SSR conserve sa dépendance au PBR et les limites du paysage visible/chargé
et du budget de maillage.
- Les feuilles (et fibres de laine, même profil entièrement mat) ne reçoivent
plus de large éclat spéculaire blanc. Leur relief normal reste présent.
La pierre et le bois retrouvent exactement leur poids spéculaire précédent,
comme les surfaces polies, les métaux, l’eau et le verre. La suppression
concerne les profils non métalliques de rugosité 0,98, avec transition 0,90–0,98.
## Vérifications
Contrôles de persistance indépendante, valeurs par défaut et libellés FR/EN.
Scènes graphiques avec PBR variable/SSR 256 puis PBR 256/SSR variable.
- Suite PBR/préférences Vulkan réussie : deux défauts 64 et persistance
indépendante sur 16/32/64/128/256 ; libellés FR/EN et nom SSR vérifiés.
Journal : `build/beta149-pbr-vulkan.log`.
- Suites SSR Vulkan et OpenGL réussies avec les portées dissociées, F5,
intersections, eau/verre/métal, soleil/lune, OFF/zéro et rechargement.
Journaux : `build/beta149-ssr-vulkan.log`, `build/beta149-ssr-opengl.log`.
Ces passes précèdent l’ajustement final du profil entièrement mat.
- Suite PBR Vulkan finale avec feuillage réussie : 170 172 pixels de feuilles
contrôlés, saturation relative 1,00007, relief visible sur 63 236 pixels.
Journal : `build/beta149-foliage-vulkan.log` ; captures correspondantes
dans `build/evidence/beta149-foliage-vulkan/`.
- Bois et briques après recentrage sur le feuillage : aucun pixel modifié
par rapport à la capture précédant l’ajustement, sur les zones de contrôle
de 26 304 et 40 896 pixels respectivement.
- `./gradlew check build` exécuté dans l’instantané isolé : 252 tests serveur,
229 réussis et 23 échecs, mêmes identifiants qu’en beta.148. Aucun nouvel échec.
Journal : `build/beta149-check-build-isolated.log` ; comparaison :
`build/beta149-server-failures.json`. L’ajustement final du feuillage concerne
seulement le shader et son test client. Le contrôle global reste en échec.
- Suite PBR OpenGL finale réussie également : 170 199 pixels de feuilles,
saturation relative 1,00001, relief visible sur 63 263 pixels.
Journal : `build/beta149-foliage-opengl.log` ; captures correspondantes
dans `build/evidence/beta149-foliage-opengl/`.
- Assemblage final réussi : `./gradlew build assemblePack -x :sanctuary:check`,
sans répéter les contrôles purs/serveur exécutés dans l’instantané.
Journal : `build/beta149-build-pack.log`. Cette exclusion ne transforme
pas le contrôle complet en réussite.
## Artefact
[Sanctuary-beta.149.mrpack](../build/Sanctuary-beta.149.mrpack), copié aussi dans
`sanctuary-beta/build/` avec les autres versions. Archive vérifiée, un seul JAR
Sanctuary beta.149 identique au JAR construit, shaders identiques aux sources,
libellés SSR et deux portées FR/EN vérifiés. Minecraft 26.3 / Loader 0.19.5.
Taille : 10 520 465 octets. SHA-256 :
`ef5519d9a0e2006b865b62371a6f8712db02ab138aa9698339d6fdb02fb4befa`.
Reçu : `build/beta149-artifacts.json`. Aucune installation Prism ni publication.
+96
View File
@@ -0,0 +1,96 @@
# RENDER-150 — Terre mate et reflets solaires colorés
Branche `codex/matte-dirt-beta150`, socle beta.149 `1c23d0c`.
La terre sèche utilise un profil entièrement mat (rugosité 0,98, métal 0),
comme le feuillage corrigé en beta.149. Cela enlève l’éclat blanc/gris diffus
sans supprimer les normales ni le contraste de relief. Le SSR de ces matières
est également atténué par leur rugosité, sans changer son réglage global.
Blocs concernés : dirt, coarse_dirt, rooted_dirt, grass_block, dirt_path,
farmland, podzol et mycelium. Les faces terreuses suivent ainsi le même profil.
Les autres profils de matière sont inchangés.
Le disque du soleil et son reflet utilisent la même couleur native
`SkyRenderState.sunriseAndSunsetColor`, issue de l’attribut environnemental
Minecraft `SUNRISE_SUNSET_COLOR`. Son alpha pilote la transition depuis le
blanc : aucune palette horaire ou altitude artificielle. Le soleil reste
neutre tant que le dégradé natif est absent et se colore avec le ciel.
La teinte solaire est convertie en lumière linéaire pour le PBR. Le mixin
modifie seulement la couleur du disque solaire, préserve l’alpha de pluie,
la texture native et la lune, et respecte la désactivation globale de Vanilla Light.
La fusion diffuse de l’eau et sa normale animée d’origine sont conservées.
Les accents suivent les pentes des vaguelettes qui réfléchissent le soleil
vers la caméra. Leur pulsation vient des normales animées de la texture,
sans oscillateur indépendant ni inversion artificielle du relief.
Une faible variation stable par texel évite des crêtes toutes identiques ;
elle module seulement l’intensité, sans produire des points isolés aléatoires.
Les détails s’atténuent lorsqu’ils deviennent plus petits qu’un pixel écran.
Les accents restent limités au reflet solaire et conservent sa couleur.
Les lampes et la lune gardent leurs
couleurs propres.
PBR 50 %, SSR 20 % et deux portées indépendantes de 64 blocs par défaut conservés.
Minecraft 26.3, Java 25 et dépendances inchangés ; aucune sauvegarde modifiée.
## Vérifications
Tests des profils de terre et scènes GPU comparant PBR OFF/ON sur terre et
feuillage : conservation de la couleur native et présence du relief.
- Suites PBR finales Vulkan/OpenGL réussies, dont profils des huit types de
sol, proximité, intensités, préférences et feuillage.
Journaux : `build/beta150-pbr-vulkan-final.log` et
`build/beta150-pbr-opengl-final.log`.
- Terre : 180 469 pixels contrôlés, saturation relative 0,99877 sur les deux
moteurs ; relief visible sur 145 415 pixels. Captures conservées dans
`build/evidence/beta150-pbr-vulkan-final/` et
`build/evidence/beta150-pbr-opengl-final/`.
Les tests comparent la contribution PBR au rendu natif et capturent aussi
le disque du soleil, avant puis pendant la transition native du ciel.
Le contrôle serveur `./gradlew check build`, exécuté dans une copie isolée
`build/server-validation` avant les derniers ajouts exclusivement client,
compte 229 réussites et 23 échecs sur 252 tests. Les 23 identifiants d’échec
sont identiques à beta.149 ; aucun nouvel identifiant. Le contrôle global
n’est donc pas vert. Journaux : `build/beta150-check-build-isolated.log` et
`build/beta150-server-failures.json`.
Les scènes solaires comparent le rendu PBR au natif, avant et pendant le
coucher de soleil. À 2 000 ticks, la teinte native est blanche ; à 11 500 ticks
sa transition commence légèrement (alpha 0,051) ; à 12 400 ticks l’alpha
natif atteint 0,831 et le disque devient chaud (centre RGB 255/255/135).
Les deux moteurs ont validé cette synchronisation native.
Un lancement sans sélection PBR a également rencontré l’assertion client
Blocodex « The full snapshot must become visible together », avant les scènes
GPU (`build/beta150-water-motion-vulkan.log`). Il ne constitue pas une
validation de la suite client générale.
La suite finale Vulkan des éclats liés aux vaguelettes passe
(`build/beta150-wave-glints-vulkan.log`) : animation native, masquage F5,
distances indépendantes, reflets solaires et lunaires, matériaux transparents,
rechargement des ressources et redimensionnement. Dans la scène solaire,
150 563 pixels sont éclaircis, sans pixel blanc saturé.
La suite OpenGL finale passe également
(`build/beta150-wave-glints-opengl.log`) : 148 180 pixels éclaircis, aucun
pixel blanc saturé. Les captures finales sont conservées dans
`build/evidence/beta150-wave-glints-vulkan/` et
`build/evidence/beta150-wave-glints-opengl/`.
Le ressenti des vaguelettes en mouvement reste à confirmer par le créateur
sur sa configuration ; les tests ne constituent pas une validation esthétique.
## Pack local
Construction et assemblage réussis avec
`./gradlew build assemblePack -x :sanctuary:check`, après le contrôle complet
ci-dessus. L’exclusion ne transforme pas ses échecs connus en réussite.
Export packwiz puis vérification ZIP, version Fabric, Minecraft 26.3,
Loader 0.19.5, JAR unique identique au construit, shader final et mixin solaire.
- Pack : `/Users/koka/Documents/sanctuary/sanctuary-beta/build/Sanctuary-beta.150.mrpack`
- Taille : 10522942 octets.
- SHA-256 pack : `65a4a44948dcdcf0325b5e24c076a03f8832184f25f5c1ff16f69bd3ee641b93`.
- SHA-256 JAR : `4962e6f8cafd6ef3e81e088bf9b0092a264441632cfa095d4fe7394b855f093d`.
- Reçu : `build/beta150-artifacts.json`, également copié à côté du pack.
Aucun canal publié, aucune instance Prism ni sauvegarde personnelle modifiée.
+93
View File
@@ -0,0 +1,93 @@
# FIX-151 — Nether, PBR opaque et SSR
Branche `codex/nether-wood-beta151`, socle beta.150 `448e2fe`.
## Déconnexion Nether
Le rapport Windows beta.150 transmis le 18 septembre 2026 indique
`IndexOutOfBoundsException: Index 46 out of bounds for length 46` lors de
`ClientPacketListener.handleContainerContent`, paquet `container_set_content`.
Le passage au Nether dans le test client local reproduit exactement cette trace
(journal `build/beta151-client-first.log`). Ce défaut n’est pas un crash GPU.
Minecraft recrée le joueur client et son menu lors de `handleRespawn`, utilisé
aussi pour les changements de dimension. La progression n’était réattachée
qu’en fin de tick ; un paquet d’inventaire dans le même lot arrivait avant la
création des 18 cases supplémentaires et des 3 accessoires. Le correctif lie la
progression déjà reçue au nouveau joueur dès la fin du traitement du paquet de
respawn, avant le paquet suivant. Aucun paquet ni objet n’est ignoré et aucun
format de sauvegarde ne change. Sans progression Sanctuary active, le chemin
vanilla reste inchangé.
## Rendu
La beta.146 avait porté le gain solaire des surfaces opaques de 3 à 20
(4 × 5), soit environ 6,7 fois plus. Le renforcement prévu pour les reflets de
l’eau avait ainsi créé un voile sur les matières sombres, dont l’obsidienne.
Les surfaces opaques retrouvent le gain 3 et le budget spéculaire total 0,5,
partagé entre soleil et lampes. La teinte native du coucher de soleil, les
normales et le relief sont conservés. Eau et verre conservent leur traitement.
Le bois utilise le profil mat 0,98 : planches, bûches et variantes de construction,
bambou, tiges et hyphes du Nether. Ce classement ne transforme pas le nylium en bois.
SSR est activé par défaut à 20 %, avec portée 64 blocs. Un choix OFF déjà
sauvegardé reste OFF. PBR reste à 50 % par défaut.
## Cible graphique
Décision du créateur pendant ce ticket : **Vulkan uniquement** pour les nouvelles
validations. Le passage OpenGL de ce ticket s’était terminé avant cette demande ;
il reste une trace historique, pas une cible à conserver ni à relancer.
Cette décision est reportée dans `AGENTS.md` et `docs/testing.md`.
## Vérification
Les assertions GPU Vulkan et les quatre transferts successifs
Overworld → Nether → Overworld → Nether → Overworld passent sur un nouveau monde
plat de développement, graine 122. Après chaque transfert : menu de 67 cases,
pile de 7 diamants en inventaire natif, 3 émeraudes dans la première case
supplémentaire et 5 lingots d’or dans la dernière, toujours présents côté client.
Les scènes PBR conservent 99,878 % de la saturation relative du chêne et 99,968 %
de celle de l’épicéa écorcé sur 185 776 pixels chacune. Le relief affecte
respectivement 101 382 et 51 671 pixels. De face, l’obsidienne ne gagne en moyenne
que 5,114 niveaux RGB sur 255 (102 480 pixels), avec relief visible sur 95 225
pixels. Captures inspectées : bois et obsidienne PBR OFF/ON.
Les contrôles couvrent aussi les réglages SSR absents (ON/20 %), la persistance
d’un choix OFF, les profils bois, les petits déplacements de caméra, les
intensités, le rechargement des ressources et le redimensionnement.
Le premier passage Vulkan corrigé valide toutes les assertions, mais se bloque
ensuite à la fermeture dans les barrières de synchronisation du programme de
test Fabric ; son processus est arrêté après capture des piles de threads.
Premier journal : `build/beta151-client-vulkan-final.log` ; diagnostic :
`build/beta151-client-shutdown-threads.txt`. Le second passage Vulkan réussit entièrement, fermeture comprise, en **3 min 9 s**
(`build/beta151-client-vulkan-clean.log`). La suite OpenGL terminée avant l’abandon de
cette cible a réussi en 2 min 42 s ; aucun nouveau test OpenGL n’est prévu.
Le contrôle complet `./gradlew check build` dans `build/server-validation`
(3 068 fichiers de production identiques aux sources courantes) termine en
11 min 13 s : **229 réussites, 23 échecs sur 252**. Les 23 identifiants sont
strictement identiques à beta.150 : aucun nouveau, aucun résolu.
Le contrôle global reste rouge. Journaux et liste :
`build/beta151-check-build.log`, `build/beta151-server-failures.json`.
Ces échecs restent ouverts et ne constituent pas la validation du correctif
réseau ; celui-ci repose sur la reproduction puis le test client des transferts.
## Pack local
`./gradlew build assemblePack -x :sanctuary:check` réussit en 11 s,
indépendamment du contrôle complet en échec ci-dessus. Export packwiz réussi ;
ZIP, version Fabric, Minecraft 26.3, Loader 0.19.5, JAR Sanctuary unique,
mixin de respawn et shader exacts, classes conformes aux sorties compilées et
absence des tests dans le JAR vérifiés.
- Pack : `/Users/koka/Documents/sanctuary/sanctuary-beta/build/Sanctuary-beta.151.mrpack`.
- SHA-256 pack : `1d87804d75edda0a95c318ea3feaf47ecadeeb29d86ca55483a42741d7a5fcab`.
- SHA-256 JAR : `6676c67f8be4e650a9b6a8213c8b0fdf3cf7e62b030c3d1a6d4805d438a8e543`.
- Reçu : `build/beta151-artifacts.json`.
Les essais sont effectués sur Apple M1 ; la validation sur le PC Windows du
créateur reste à faire.
Aucune sauvegarde personnelle ouverte, aucun canal publié ni instance installée.
+82
View File
@@ -0,0 +1,82 @@
# RENDER-147 — PBR proche, lune et portée 256 blocs
Branche `codex/pbr-close-range-beta147`, socle beta.146 `cf9a670`.
Minecraft 26.3, Java 25 ; dépendances et formats de sauvegarde inchangés.
## Résultat
- Le prépassage des récepteurs PBR comparait encore les profondeurs natives
avec un epsilon fixe de 0,0000001. En profondeur inversée, cette marge
devenait trop stricte au voisinage de la caméra : de nombreux fragments
alternaient entre le rendu natif et le PBR lors de mouvements minuscules.
- La comparaison utilise maintenant une distance reconstruite avec la marge
bornée de 5 mm déjà employée par les passes couleur. La sélection de la
surface PBR la plus proche et son test de profondeur EQUAL restent stricts.
Aucun décalage du maillage ni fondu supprimant le PBR près de la caméra.
- La lune ne reçoit plus le renforcement spéculaire du soleil (×20 pour
l’eau, ×4 pour les autres matières). Son coefficient lunaire et ses phases
restent appliqués ; le grand reflet solaire est conservé.
- Distance commune PBR/SSR : 16, 32 (défaut), 64, 128 ou 256 blocs.
À 256, le rayon SSR peut aussi rechercher jusqu’à 256 blocs. Nombre de pas
et budgets de maillage inchangés ; les limites du terrain visible/chargé,
des reflets hors écran et du budget de maillage subsistent.
- PBR 80 %, SSR 20 %, mélange des eaux et préférences de beta.146 conservés.
Libellés FR/EN mis à jour.
## Vérifications
Mondes de test jetables, macOS Apple M1. Aucune sauvegarde personnelle utilisée.
- Reproduction avant correction sous Vulkan : à 12 cm du sol de briques,
59 442 / 262 272 pixels changent de plus de 12 niveaux RVB cumulés sous une
rotation de 0,00002 degré. Le test échoue. Journal :
`build/beta147-close-before-vulkan.log`.
- Même scène après correction : 1 / 262 272 pixels à 12 cm ; également testé
à 25 cm, 50 cm et 1 m. Suite PBR Vulkan réussie :
`build/beta147-close-after-vulkan.log`.
- Preuves avant/après dans `build/evidence/close-before-vulkan/` et
`build/evidence/close-after-vulkan/`.
- Suite PBR et préférences OpenGL finale réussie : mêmes scènes proches,
persistance des distances dont 256 blocs, FR/EN, OFF/zéro, intensités,
rechargement, redimensionnement et effets combinés.
Journal : `build/beta147-close-final-opengl.log` ; preuves :
`build/evidence/beta147-close-final-opengl/`.
- Suite SSR Vulkan finale réussie : F5, intersections, portées dont 256,
eau/verre/métal, OFF/zéro, rechargement et redimensionnement.
Journal : `build/beta147-ssr-final-vulkan.log` ; preuves :
`build/evidence/beta147-ssr-final-vulkan/`.
- À PBR 80 % / SSR 20 %, le soleil conserve 22 697 pixels quasi blancs sur
la scène aquatique 854 × 480. La lune seule en compte zéro, tout en modifiant
visiblement 40 808 pixels par rapport au PBR OFF. Capture du bloom lunaire
inspectée : reflet doux, sans plateau blanc.
- Suite SSR OpenGL finale réussie également : zéro pixel quasi blanc pour
la lune, 22 111 pour le soleil et 40 749 pixels de reflet lunaire visibles.
Intersections : 91 / 518 400 pixels changés sous rotation minuscule.
Journal : `build/beta147-ssr-final-opengl.log` ; captures correspondantes
dans `build/evidence/beta147-ssr-final-opengl/`.
- `./gradlew check build` dans un instantané isolé : 252 tests serveur,
229 réussis, 23 échecs, avec exactement les mêmes identifiants que beta.146.
Journal : `build/beta147-check-build-isolated.log` ; comparaison :
`build/beta147-server-failures.json`. La vérification globale reste en échec.
Les derniers ajouts lune/256 concernent exclusivement le rendu client,
ses réglages et ses tests ; ils sont validés par les suites finales ci-dessus.
- Assemblage final réussi : `./gradlew build assemblePack -x :sanctuary:check`,
après les contrôles purs et serveur exécutés dans l’instantané, sans les
répéter. Journal : `build/beta147-build-pack.log`. Cette exclusion ne
transforme pas le contrôle complet en réussite.
## Artefact
[Sanctuary-beta.147.mrpack](../build/Sanctuary-beta.147.mrpack), également copié
à côté de beta.146 dans `sanctuary-beta/build/`. Aucun ancien pack remplacé,
aucune installation Prism ou publication effectuée.
Archive ZIP vérifiée, un seul JAR Sanctuary beta.147, identique au JAR construit,
shaders identiques aux sources finales, options FR/EN 256 présentes,
Minecraft 26.3 / Fabric Loader 0.19.5 conservés. Taille : 10 519 851 octets.
SHA-256 : `b8044243dbe160d5171ea584052cf5732393e4d91352c29c33b727af80c0a5bc`.
Reçu : `build/beta147-artifacts.json`.
+68
View File
@@ -0,0 +1,68 @@
# RENDER-148 — PBR 50 % et reflet solaire diffus
Branche `codex/pbr-soft-sun-beta148`, socle beta.147 `96a36d9`.
Minecraft 26.3, Java 25, dépendances et sauvegardes inchangés.
## Résultat
- PBR à 50 % par défaut, SSR toujours à 20 % et portée commune jusqu’à 256.
- Les configurations anciennes contenant exactement PBR 80 % passent à 50 %.
Un marqueur de migration conserve ensuite tout choix explicite, y compris
un nouveau choix 80 %. Les autres anciennes intensités restent conservées.
- Le reflet solaire de l’eau avait un renforcement ×20 et une compression
exponentielle qui effaçait les détails dans une large zone blanche.
Le renforcement passe à ×6 et le lobe est plus diffus (rugosité minimale
de 0,42 au lieu de 0,34 pour le lobe céleste de l’eau).
- Pour le soleil sur l’eau, fusion par texel entre éclaircissement vers le
blanc et lumière douce fondée sur la couleur native. La pondération emploie
une compression rationnelle, sans atteindre brutalement un plateau blanc.
La texture et ses animations restent natives ; aucun bruit ajouté.
- La lune conserve son intensité atténuée, sans renforcement solaire.
Les lampes et matières solides conservent leur fusion spéculaire précédente.
Le bloom reste réglable et suit la lumière réellement ajoutée.
## Vérifications
Scènes plates jetables, Apple M1, Vulkan et OpenGL. Le test solaire contrôle
une lumière largement visible et limite les pixels quasi blancs à moins de
0,5 % de l’image, au lieu d’imposer une grande tache blanche.
Premier contrôle Vulkan à PBR 50 % / SSR 20 % : 149 908 pixels éclaircis,
zéro pixel quasi blanc sur 854 × 480. Captures vues du dessus et à soleil bas
inspectées : la texture reste visible dans la traînée lumineuse.
Preuves : `build/evidence/beta148-preview/`.
Rendu de la traînée solaire approuvé par le créateur après inspection de la capture.
- Suites SSR complètes Vulkan et OpenGL réussies : F5, intersections, portées,
eau/verre/métal, OFF/zéro, lune, bloom, rechargement et redimensionnement.
Journaux : `build/beta148-ssr-vulkan.log`, `build/beta148-ssr-opengl.log`.
Sous OpenGL, 146 680 pixels éclaircis et zéro pixel quasi blanc dans la scène
solaire. La lune reste visible sans saturation (35 865 pixels modifiés).
Preuves : `build/evidence/beta148-ssr-vulkan/` et
`build/evidence/beta148-ssr-opengl/`.
- Suites PBR/préférences Vulkan et OpenGL réussies : défaut 50 %, migration
de l’ancien 80 %, conservation d’un nouveau choix explicite 80 %, valeurs
personnalisées, FR/EN, proximité, OFF/zéro, intensités et rechargement.
Journaux : `build/beta148-pbr-vulkan.log`, `build/beta148-pbr-opengl.log`.
- `./gradlew check build` dans l’instantané isolé : 252 tests serveur,
229 réussis, 23 échecs. Exactement les mêmes identifiants que beta.147,
aucun nouvel échec. Journal : `build/beta148-check-build-isolated.log` ;
comparaison : `build/beta148-server-failures.json`. Le contrôle global
demeure donc en échec.
- Assemblage final réussi : `./gradlew build assemblePack -x :sanctuary:check`,
sans répéter les contrôles purs et serveur déjà exécutés dans l’instantané.
Journal : `build/beta148-build-pack.log`. L’exclusion ne constitue pas
une réussite du contrôle complet.
## Artefact
[Sanctuary-beta.148.mrpack](../build/Sanctuary-beta.148.mrpack), également copié
à côté des autres versions dans `sanctuary-beta/build/`. Archive ZIP vérifiée,
un seul JAR Sanctuary beta.148 identique au JAR construit, shaders finaux
identiques aux sources, textes FR/EN 50 % et dépendances exactes vérifiés.
Taille : 10 520 050 octets. SHA-256 :
`597cc2a96eabc3cf0ebf8ac59b4f94cf7cabf0c773e1dce13c10736ca6fcba38`.
Reçu : `build/beta148-artifacts.json`.
Aucune publication ni installation Prism effectuée.
+8
View File
@@ -1,5 +1,13 @@
# Vérifier Sanctuary
## Cible graphique — décision du 18 septembre 2026
Les nouvelles validations graphiques Sanctuary utilisent **Vulkan uniquement**.
OpenGL est abandonné comme cible de validation à la demande du créateur.
Les anciens résultats OpenGL ci-dessous sont historiques et ne constituent
pas un engagement de compatibilité. Ne plus programmer de suite OpenGL.
Utiliser Java 25 et les dépendances épinglées dans `gradle.properties`.
```sh
+2 -2
View File
@@ -9,8 +9,8 @@ loom_version=1.17.20
fabric_api_version=0.160.5+26.3
# Release counter: beta.001, .002, .003, ... (see docs/versioning.md).
mod_version=beta.146
pack_version=beta.146
mod_version=beta.151
pack_version=beta.151
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
@@ -27,6 +27,8 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
c.runOnClient(m->check(analyses==ProceduralPbr.analyses()&&ProceduralPbr.materialCount()>0,"Source textures are cached, not regenerated every frame"));
unchanged(on,capture(c,null),"Static PBR has no animated noise");
look(c,server,.00002f,40.00002f);frames(c);unchanged(on,capture(c,null),"PBR tiny camera motion");look(c,server,0,40);frames(c);
closeRange(c,server);
camera(c,server,10.5,69,5.5,65);look(c,server,0,40);settle(c);
c.runOnClient(m->VanillaLight.setPbrIntensity(20));frames(c);var low=difference(off,capture(c,"pbr-20"));
check(delta.total>low.total*1.3,"PBR strength affects actual GPU output");
c.runOnClient(m->VanillaLight.setPbrIntensity(100));frames(c);var high=difference(off,capture(c,"pbr-100"));check(high.total>delta.total*1.2,"Strong PBR visibly increases relief");
@@ -48,10 +50,97 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
c.runOnClient(m->m.getWindow().setWindowed(oldWidth,oldHeight));c.waitFor(m->m.gameRenderer.mainRenderTarget().width==on.width(),200);frames(c);
c.runOnClient(m->{VanillaLight.setPixelShadows(true);VanillaLight.setSsgi(true);VanillaLight.setBloom(true);VanillaLight.setOreEmission(true);VanillaLight.setEdgeHighlights(true);});
c.waitFor(m->PixelShadows.applied()&&PixelShadows.ready()&&ScreenSpaceGI.applied()&&Bloom.applied()&&EdgeHighlights.applied()&&ProceduralPbr.applied(),1200);frames(c);capture(c,"pbr-combined");
matteGround(c,server,Blocks.OAK_LEAVES,"leaves",true);
matteGround(c,server,Blocks.DIRT,"dirt",false);
matteGround(c,server,Blocks.OAK_PLANKS,"oak-planks",false);
matteGround(c,server,Blocks.STRIPPED_SPRUCE_LOG,"stripped-spruce",false);
darkStone(c,server);
netherRoundTrip(c,server);
c.runOnClient(m->VanillaLight.setEnabled(false));c.waitTicks(8);c.runOnClient(m->check(!ProceduralPbr.allocated()&&ProceduralPbr.materialCount()==0,"Global OFF releases PBR"));
c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setPbr(false);VanillaLight.setSsgi(false);});
System.out.println("PBR134_PASS normals; profiles; texture cache; directional lighting; intensity; OFF/zero; tiny camera; reload; resize; combined; default="+delta+" high="+high+" directional="+directions);
}
private static void darkStone(ClientGameTestContext c,TestServerContext server){
server.runOnServer(s->{for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++)
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),Blocks.OBSIDIAN.defaultBlockState());});
camera(c,server,10.5,69,5.5,65);look(c,server,0,90);settle(c);
enable(c,false);var off=capture(c,"pbr151-obsidian-face-off");enable(c,true);var on=capture(c,"pbr151-obsidian-face-on");
double gain=0;int samples=0,relief=0;
for(int y=off.height()/4;y<off.height()*3/4;y++)for(int x=off.width()/4;x<off.width()*3/4;x++){
int i=y*off.width()+x,a=off.pixels()[i],b=on.pixels()[i];
double before=((a>>16&255)+(a>>8&255)+(a&255))/3.0,after=((b>>16&255)+(b>>8&255)+(b&255))/3.0;
gain+=Math.max(0,after-before);samples++;if(Math.abs(after-before)>1)relief++;
}
System.out.println("PBR151_OBSIDIAN samples="+samples+" meanBrightening="+gain/samples+" relief="+relief);
check(samples>1000&&gain/samples<18,"Dark stone keeps its dark face without amplified white sheen");
check(relief>100,"Obsidian PBR remains visible");
look(c,server,0,20);settle(c);capture(c,"pbr151-obsidian-grazing");
}
private static void netherRoundTrip(ClientGameTestContext c,TestServerContext server){
c.runOnClient(m->{VanillaLight.setEnabled(true);VanillaLight.setPbr(true);VanillaLight.setSsr(true);VanillaLight.setColoredLights(true);VanillaLight.setPixelShadows(true);});
server.runOnServer(s->{var p=s.getPlayerList().getPlayers().getFirst();
p.getInventory().setItem(0,new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.DIAMOND,7));
var overflow=fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(p);
overflow.setItem(0,new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.EMERALD,3));
overflow.setItem(17,new net.minecraft.world.item.ItemStack(net.minecraft.world.item.Items.GOLD_INGOT,5));
p.inventoryMenu.broadcastFullState();});
for(String dimension:new String[]{"the_nether","overworld","the_nether","overworld"}){
server.runOnServer(s->command(s,"execute in minecraft:"+dimension+" run tp @a 10.5 100 5.5"));
c.waitFor(m->m.level!=null&&m.player!=null&&m.level.dimension().identifier().getPath().equals(dimension)&&m.gui.screen()==null,2400);
c.waitTicks(40);
c.runOnClient(m->{
check(m.player.inventoryMenu.slots.size()==67,"Inventory has exactly 46 native, 18 extra and 3 accessory slots");
check(m.player.getInventory().getItem(0).is(net.minecraft.world.item.Items.DIAMOND)&&m.player.getInventory().getItem(0).getCount()==7,"Native stack survives transfer");
var overflow=fr.koka.sanctuary.inventory.SanctuaryOverflowAccess.of(m.player);
check(overflow.getItem(0).is(net.minecraft.world.item.Items.EMERALD)&&overflow.getItem(0).getCount()==3,"First overflow stack survives transfer");
check(overflow.getItem(17).is(net.minecraft.world.item.Items.GOLD_INGOT)&&overflow.getItem(17).getCount()==5,"Last overflow stack survives transfer");
});
capture(c,"nether151-"+dimension);
server.runOnServer(s->check(s.getPlayerList().getPlayers().size()==1,"Player stays connected after dimension transfer"));
System.out.println("NETHER151_TRANSFER_PASS "+dimension);
}
}
private static void matteGround(ClientGameTestContext c,TestServerContext server,net.minecraft.world.level.block.Block block,String name,boolean leaves){
c.runOnClient(m->{VanillaLight.setSsr(false);VanillaLight.setPbrIntensity(50);VanillaLight.setPixelShadows(false);VanillaLight.setSsgi(false);VanillaLight.setBloom(false);VanillaLight.setOreEmission(false);VanillaLight.setEdgeHighlights(false);});
server.runOnServer(s->{if(leaves)command(s,"time set 1000");for(int x=-5;x<=24;x++)for(int z=-5;z<=24;z++)
s.overworld().setBlockAndUpdate(new BlockPos(x,64,z),leaves?block.defaultBlockState().setValue(net.minecraft.world.level.block.LeavesBlock.PERSISTENT,true):block.defaultBlockState());});
camera(c,server,10.5,69,5.5,65);look(c,server,0,40);settle(c);
enable(c,false);var off=capture(c,"pbr150-"+name+"-off");enable(c,true);var on=capture(c,"pbr150-"+name+"-on");
double nativeChroma=0,pbrChroma=0;int samples=0,relief=0;
for(int y=off.height()/3;y<off.height()*9/10;y++)for(int x=off.width()/10;x<off.width()*9/10;x++){
int i=y*off.width()+x,a=off.pixels()[i],b=on.pixels()[i],r=a>>16&255,g=a>>8&255,blue=a&255;
if(leaves?(g<30||g<r*1.1||g<blue*1.1):(r<30||r<g*1.05||g<blue*1.1))continue;
nativeChroma+=chroma(a);pbrChroma+=chroma(b);samples++;
if(Math.abs((b>>16&255)-r)+Math.abs((b>>8&255)-g)+Math.abs((b&255)-blue)>6)relief++;
}
double retained=pbrChroma/nativeChroma;
System.out.println("PBR150_MATTE material="+name+" samples="+samples+" chromaRetained="+retained+" relief="+relief);
check(samples>1000&&retained>.985,name+" preserves native colour without specular veil: "+retained);
check(relief>100,name+" normal relief remains visible");
}
private static double chroma(int pixel){
int r=pixel>>16&255,g=pixel>>8&255,b=pixel&255,max=Math.max(r,Math.max(g,b)),min=Math.min(r,Math.min(g,b));
return (max-min)/(double)Math.max(1,max);
}
private static void closeRange(ClientGameTestContext c,TestServerContext server){
int failures=0;
c.runOnClient(m->VanillaLight.setPbrIntensity(80));
for(double distance:new double[]{.12,.25,.5,1.0}){
double eye=c.computeOnClient(m->(double)m.player.getEyeHeight());
camera(c,server,4.5,65-eye+distance,5.5,65);look(c,server,17,55);settle(c);frames(c);
var before=capture(c,"pbr147-close-"+distance);
look(c,server,17.00002f,55.00002f);frames(c);var after=capture(c,"pbr147-close-moved-"+distance);
int changed=0,count=0;
for(int y=before.height()/10;y<before.height()*9/10;y++)for(int x=before.width()/10;x<before.width()*9/10;x++){
int i=y*before.width()+x,a=before.pixels()[i],b=after.pixels()[i];count++;
if(Math.abs((a>>16&255)-(b>>16&255))+Math.abs((a>>8&255)-(b>>8&255))+Math.abs((a&255)-(b&255))>12)changed++;
}
System.out.println("PBR147_CLOSE distance="+distance+" changed="+changed+" samples="+count);
if(changed>count*.001)failures++;
}
c.runOnClient(m->VanillaLight.setPbrIntensity(50));
check(failures==0,"Close PBR stays stable under subpixel motion: failing distances="+failures);
}
private static void materials(){
int[] flat=new int[16];Arrays.fill(flat,0xFF808080);var profile=PbrMaterials.profile("bricks");
for(int p:PbrMaterials.generate(flat,4,4,profile))check((p>>8&0xFFFF)==0x8080,"Flat source has neutral normal");
@@ -61,27 +150,38 @@ public final class Pbr134ClientChecks implements FabricClientGameTest {
check(PbrMaterials.profile("water").metal()==0&&PbrMaterials.profile("glass").metal()==0,"Water and glass stay dielectric");
int[] glass={0x00FFFFFF,0x00000000,0x00000000,0x00FFFFFF};
for(int p:PbrMaterials.generate(glass,2,2,PbrMaterials.profile("glass")))check((p>>8&0xFFFF)==0x8080,"Transparent glass texels fall back to geometric normal");
for(String soil:new String[]{"dirt","coarse_dirt","rooted_dirt","grass_block","dirt_path","farmland","podzol","mycelium"})
check(PbrMaterials.profile(soil).roughness()>=.98f&&PbrMaterials.profile(soil).metal()==0,"Dry soil has a fully matte profile: "+soil);
for(String wood:new String[]{"oak_planks","spruce_stairs","dark_oak_slab","stripped_spruce_log","stripped_crimson_stem","warped_hyphae","bamboo_mosaic","pale_oak_trapdoor"})
check(PbrMaterials.profile(wood).roughness()>=.98f&&PbrMaterials.profile(wood).metal()==0,"Wood remains fully matte: "+wood);
check(PbrMaterials.profile("crimson_nylium").roughness()<.9f,"Nether ground is not classified as wood");
check(PbrMaterials.profile("oak_planks").roughness()>PbrMaterials.profile("polished_andesite").roughness(),"Wood remains rougher than polished stone");
}
private static void preferences(){try{
var p=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");Files.writeString(p,"{\"ssgi\":true,\"ssgiIntensity\":35,\"bloom\":false}");forgetPreferences();
check(VanillaLight.pbr()&&VanillaLight.pbrIntensity()==80&&VanillaLight.ssgi()&&!VanillaLight.bloom(),"Missing PBR setting defaults ON while preserving explicit old choices");
check(VanillaLight.pbr()&&VanillaLight.pbrIntensity()==50&&VanillaLight.ssgi()&&!VanillaLight.bloom(),"Missing PBR setting defaults ON while preserving explicit old choices");
Files.writeString(p,"{\"pbrIntensity\":80}");forgetPreferences();
check(VanillaLight.pbrIntensity()==50,"Previous 80 percent baseline migrates to 50 once");
VanillaLight.setPbrIntensity(80);forgetPreferences();check(VanillaLight.pbrIntensity()==80,"New explicit 80 percent choice persists after migration");
for(int n:new int[]{-1,0,50,100,101}){VanillaLight.setPbr(true);VanillaLight.setPbrIntensity(n);forgetPreferences();check(VanillaLight.pbr()&&VanillaLight.pbrIntensity()==Math.clamp(n,0,100),"PBR preference persistence/clamp");}
check(VanillaLight.pbrDistance()==32,"Shared distance default");
check(VanillaLight.pbrDistance()==64&&VanillaLight.ssrDistance()==64,"Independent distances default to 64");
check(!VanillaLight.naturalWater(false)&&VanillaLight.naturalWater(true),"World-specific water defaults");
for(int n:new int[]{16,32,64,128}){VanillaLight.setPbrDistance(n);forgetPreferences();check(VanillaLight.pbrDistance()==n,"Shared distance persistence");}
for(int n:new int[]{16,32,64,128,256}){VanillaLight.setPbrDistance(n);forgetPreferences();check(VanillaLight.pbrDistance()==n&&VanillaLight.ssrDistance()==64,"PBR distance changes independently");}
for(int n:new int[]{16,32,64,128,256}){VanillaLight.setSsrDistance(n);forgetPreferences();check(VanillaLight.ssrDistance()==n&&VanillaLight.pbrDistance()==256,"SSR distance changes independently");}
VanillaLight.setSsrDistance(64);
for(int n:new int[]{0,1,2,4,8}){VanillaLight.setWaterBlendRadius(n);forgetPreferences();check(VanillaLight.waterBlendRadius()==n,"Water blend persistence");}
VanillaLight.setPbrDistance(32);VanillaLight.setWaterBlendRadius(4);
check(!VanillaLight.ssr()&&VanillaLight.ssrIntensity()==20,"Missing SSR defaults OFF at 20 percent");
check(VanillaLight.ssr()&&VanillaLight.ssrIntensity()==20,"Missing SSR defaults ON at 20 percent");
for(int n:new int[]{-1,0,50,100,101}){VanillaLight.setSsr(true);VanillaLight.setSsrIntensity(n);forgetPreferences();check(VanillaLight.ssr()&&VanillaLight.ssrIntensity()==Math.clamp(n,0,100),"SSR preference persistence/clamp");}
VanillaLight.setSsr(false);VanillaLight.setSsrIntensity(20);forgetPreferences();check(!VanillaLight.ssr(),"SSR OFF persists");
var manager=net.minecraft.client.Minecraft.getInstance().getLanguageManager();var oldLanguage=manager.getSelected();
for(String locale:new String[]{"fr_fr","en_us"}){
manager.setSelected(locale);manager.onResourceManagerReload(net.minecraft.client.Minecraft.getInstance().getResourceManager());
for(String key:new String[]{"pbr_distance","pbr_distance_help","pbr_distance_value","water_blend","water_blend_help","water_blend_value","ssr","ssr_help","ssr_intensity","ssr_intensity_help","ssr_intensity_value"}){
for(String key:new String[]{"ssr_distance","ssr_distance_help","ssr_distance_value","pbr_distance","pbr_distance_help","pbr_distance_value","water_blend","water_blend_help","water_blend_value","ssr","ssr_help","ssr_intensity","ssr_intensity_help","ssr_intensity_value"}){
String full="sanctuary.shaders."+key;check(!net.minecraft.network.chat.Component.translatable(full,50).getString().equals(full),"SSR FR/EN "+full);
}
}
check(net.minecraft.network.chat.Component.translatable("sanctuary.shaders.ssr").getString().equals("SSR"),"SSR label without experimental qualifier");
manager.setSelected(oldLanguage);manager.onResourceManagerReload(net.minecraft.client.Minecraft.getInstance().getResourceManager());
Files.writeString(p,"{\"naturalWater\":false,\"pbrIntensity\":37}");forgetPreferences();
check(!VanillaLight.naturalWater(false)&&!VanillaLight.naturalWater(true)&&VanillaLight.pbrIntensity()==37,"Legacy water OFF and personal PBR intensity are preserved");
@@ -45,25 +45,81 @@ final class PixelReflectionsClientChecks {
check(torsoChange<torsoPixels*.01,"SSR does not draw through the visible F5 torso: "+torsoChange+" / "+torsoPixels);
c.runOnClient(m->m.options.setCameraType(net.minecraft.client.CameraType.FIRST_PERSON));c.waitTicks(20);
server.runOnServer(s->command(s,"gamemode spectator @a"));c.waitTicks(10);
for(int distance:new int[]{16,64,128,32}){
c.runOnClient(m->VanillaLight.setPbrDistance(distance));settle(c);frames(c);capture(c,"ssr146-distance-"+distance);
c.runOnClient(m->check(ProceduralPbr.applied()&&PixelReflections.applied(),"Both material passes share distance "+distance));
for(int distance:new int[]{16,64,128,256,32}){
c.runOnClient(m->{VanillaLight.setPbrDistance(distance);VanillaLight.setSsrDistance(256);});settle(c);frames(c);capture(c,"ssr146-distance-"+distance);
c.runOnClient(m->check(ProceduralPbr.applied()&&PixelReflections.applied(),"Independent material distances render at PBR "+distance+" / SSR 256"));
}
c.runOnClient(m->{VanillaLight.setPbrIntensity(80);VanillaLight.setSsrIntensity(20);});
for(int distance:new int[]{16,64,256}){
c.runOnClient(m->{VanillaLight.setPbrDistance(256);VanillaLight.setSsrDistance(distance);});settle(c);frames(c);
capture(c,"ssr149-independent-"+distance);
c.runOnClient(m->check(ProceduralPbr.applied()&&PixelReflections.applied(),"PBR 256 remains active with independent SSR "+distance));
}
c.runOnClient(m->VanillaLight.setPbrDistance(32));
c.runOnClient(m->VanillaLight.setSsrDistance(32));
c.runOnClient(m->{VanillaLight.setPbrIntensity(50);VanillaLight.setSsrIntensity(20);});
camera(c,server,.5,68,10,70);settle(c);frames(c);
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(8);var solarOff=capture(c,"ssr148-solar-water-off");
c.runOnClient(m->VanillaLight.setPbr(true));frames(c);
var solar=capture(c,"ssr146-solar-water");
int white=0;for(int pixel:solar.pixels())if((pixel>>16&255)>235&&(pixel>>8&255)>235&&(pixel&255)>235)white++;
check(white>300,"Solar reflection has a broad near-white footprint at PBR 80 / SSR 20 percent: "+white);
int brightened=0;for(int i=0;i<solar.pixels().length;i++){
int a=solarOff.pixels()[i],b=solar.pixels()[i];
if((b>>16&255)+(b>>8&255)+(b&255)>(a>>16&255)+(a>>8&255)+(a&255)+24)brightened++;
}
check(brightened>5000,"Soft solar reflection remains broadly visible: "+brightened);
check(white<solar.pixels().length*.005,"Soft solar reflection preserves texture instead of a white plateau: "+white);
System.out.println("PBR148_SOLAR brightened="+brightened+" white="+white);
System.out.println("SSR146_SOLAR_WHITE pixels="+white+" width="+solar.width()+" height="+solar.height());
c.runOnClient(m->VanillaLight.setBloom(true));frames(c);capture(c,"ssr146-solar-water-bloom");
c.runOnClient(m->check(MaterialBloom.contribution()!=null,"Material light reaches bloom"));
c.runOnClient(m->VanillaLight.setBloom(false));frames(c);
c.runOnClient(m->check(MaterialBloom.contribution()==null,"Bloom OFF releases material halo"));
server.runOnServer(s->command(s,"time set 18000"));c.waitTicks(15);
c.runOnClient(m->VanillaLight.setSsr(false));c.waitTicks(8);
var moon=capture(c,"ssr147-moon-water");
int moonWhite=0;for(int pixel:moon.pixels())if((pixel>>16&255)>235&&(pixel>>8&255)>235&&(pixel&255)>235)moonWhite++;
check(moonWhite<Math.max(10,white*.05),"Moon reflection stays much softer than the sun: "+moonWhite+" / "+white);
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(8);var moonOff=capture(c,"ssr147-moon-water-off");
check(changed(moonOff,moon)>100,"Soft moon reflection remains visible");
System.out.println("PBR147_MOON white="+moonWhite+" sunWhite="+white+" changed="+changed(moonOff,moon));
c.runOnClient(m->{VanillaLight.setPbr(true);VanillaLight.setSsr(true);VanillaLight.setBloom(true);});frames(c);capture(c,"ssr147-moon-water-bloom");
c.runOnClient(m->VanillaLight.setBloom(false));
server.runOnServer(s->command(s,"time set 1000"));c.waitTicks(10);
var sunLook=c.computeOnClient(m->{var sun=PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);return new float[]{(float)Math.toDegrees(Math.atan2(-sun.x,sun.z)),(float)Math.toDegrees(Math.asin(sun.y))};});
look(c,server,sunLook[0],sunLook[1]);frames(c);capture(c,"ssr146-solar-path-low-sun");
c.runOnClient(m->VanillaLight.setBloom(true));frames(c);capture(c,"ssr146-solar-path-low-sun-bloom");
for(int time:new int[]{2000,11500,12400}){
c.runOnClient(m->{VanillaLight.setBloom(false);VanillaLight.setSsr(false);});
server.runOnServer(s->command(s,"time set "+time));c.waitTicks(15);
var solarLook=c.computeOnClient(m->{var sun=PixelShadows.sunDirection(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunAngle);return new float[]{(float)Math.toDegrees(Math.atan2(-sun.x,sun.z)),(float)Math.toDegrees(Math.asin(sun.y))};});
look(c,server,solarLook[0],solarLook[1]);pbrFrames(c);
var warm=capture(c,"ssr150-solar-"+time);
c.runOnClient(m->VanillaLight.setPbr(false));c.waitTicks(8);var nativeWater=capture(c,"ssr150-solar-native-"+time);
long red=0,green=0,blue=0;int samples=0;
for(int y=warm.height()/3;y<warm.height()*9/10;y++)for(int x=warm.width()/3;x<warm.width()*2/3;x++){
int i=y*warm.width()+x,a=nativeWater.pixels()[i],b=warm.pixels()[i];
int dr=(b>>16&255)-(a>>16&255),dg=(b>>8&255)-(a>>8&255),db=(b&255)-(a&255);
if(dr>3){red+=dr;green+=dg;blue+=db;samples++;}
}
System.out.println("PBR150_SOLAR_COLOUR time="+time+" pitch="+solarLook[1]+" samples="+samples+" red="+red+" green="+green+" blue="+blue);
var tint=c.computeOnClient(m->SolarColour.tint(m.gameRenderer.gameRenderState().levelRenderState.skyRenderState));
float nativeFade=c.computeOnClient(m->m.gameRenderer.gameRenderState().levelRenderState.skyRenderState.sunriseAndSunsetColor.w());
System.out.println("PBR150_NATIVE_SUNSET time="+time+" fade="+nativeFade+" tint="+tint);
check(samples>500,"Solar reflection remains visible: "+time);
if(nativeFade>.1)check(red>green*1.1&&red>blue*1.1,"Reflection follows warm native horizon colour: "+time);
if(nativeFade==0)check(tint.equals(new org.joml.Vector3f(1)),"No early tint before native sunset");
c.runOnClient(m->{VanillaLight.setPbr(true);VanillaLight.setSsr(true);VanillaLight.setBloom(true);});frames(c);capture(c,"ssr150-solar-bloom-"+time);
c.runOnClient(m->VanillaLight.setBloom(false));
look(c,server,solarLook[0],-solarLook[1]);frames(c);var sunDisc=capture(c,"ssr150-sun-disc-"+time);
long sr=0,sg=0,sb=0;int sunSamples=0;
for(int y=sunDisc.height()/2-5;y<sunDisc.height()/2+5;y++)for(int x=sunDisc.width()/2-5;x<sunDisc.width()/2+5;x++){
int pixel=sunDisc.pixels()[y*sunDisc.width()+x];sr+=pixel>>16&255;sg+=pixel>>8&255;sb+=pixel&255;sunSamples++;
}
System.out.println("PBR150_SUN_DISC time="+time+" red="+sr/sunSamples+" green="+sg/sunSamples+" blue="+sb/sunSamples);
if(nativeFade>.1)check(sr>sb*1.05,"Sun disc follows native warm tint: "+time);
look(c,server,solarLook[0],solarLook[1]);
}
c.runOnClient(m->{VanillaLight.setBloom(false);VanillaLight.setSsrIntensity(100);});
server.runOnServer(s->command(s,"time set 6000"));c.waitTicks(10);
camera(c,server,.5,68,0,70);look(c,server,0,12);settle(c);
@@ -203,6 +259,7 @@ final class PixelReflectionsClientChecks {
int i=y*a.width()+x,aa=a.pixels()[i],bb=b.pixels()[i];
if(Math.abs((aa>>16&255)-(bb>>16&255))+Math.abs((aa>>8&255)-(bb>>8&255))+Math.abs((aa&255)-(bb&255))>8)n++;
}return n;}
private static void pbrFrames(ClientGameTestContext c){int start=c.computeOnClient(m->ProceduralPbr.renderedFrames());c.waitFor(m->ProceduralPbr.applied()&&ProceduralPbr.renderedFrames()>start+8,1200);}
private static void frames(ClientGameTestContext c){int start=c.computeOnClient(m->PixelReflections.frames());c.waitFor(m->PixelReflections.applied()&&ProceduralPbr.ready()&&PixelReflections.frames()>start+8,1200);}
private static void settle(ClientGameTestContext c){c.waitTicks(45);c.waitFor(m->m.levelRenderer.hasRenderedAllSections()&&ProceduralPbr.ready(),1200);c.waitTicks(10);}
}
@@ -56,7 +56,6 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
var info = com.mojang.blaze3d.systems.RenderSystem.getDevice().getDeviceInfo();
String expected = System.getProperty("sanctuary.test.graphicsBackend", "vulkan");
check(net.minecraft.client.PreferredGraphicsApi.DEFAULT.getBackendsToTry()[0] instanceof com.mojang.renderpearl.backend.vulkan.VulkanBackend, "Default tries native Vulkan first");
check(net.minecraft.client.PreferredGraphicsApi.OPENGL.getBackendsToTry()[0] instanceof com.mojang.renderpearl.backend.opengl.GlBackend, "Explicit OpenGL remains available");
check(expected.isEmpty() || info.backendName().equalsIgnoreCase(expected),
"Requested graphics backend must not silently fall back: " + expected + " / " + info.backendName());
System.out.println("SHADER139_BACKEND " + info.backendName() + " / " + info.name()
@@ -454,7 +453,7 @@ public final class PixelShadows122ClientChecks implements FabricClientGameTest {
var path=FabricLoader.getInstance().getConfigDir().resolve("sanctuary-shaders.json");
Files.deleteIfExists(path);forgetPreferences();
check(VanillaLight.bloom()&&VanillaLight.bloomIntensity()==20&&VanillaLight.pbr()&&VanillaLight.ssgi(),"Fresh defaults: bloom20, PBR and SSGI ON");
check(VanillaLight.pbrIntensity()==80&&VanillaLight.ssrIntensity()==20&&VanillaLight.ssgiIntensity()==35,"PBR 80 and SSR 20 defaults, SSGI strength retained");
check(VanillaLight.pbrIntensity()==50&&VanillaLight.ssrIntensity()==20&&VanillaLight.ssgiIntensity()==35,"PBR 50 and SSR 20 defaults, SSGI strength retained");
Files.writeString(path,"{\"pbr\":false,\"ssgi\":false,\"bloomIntensity\":73}");forgetPreferences();
check(!VanillaLight.pbr()&&!VanillaLight.ssgi()&&VanillaLight.bloomIntensity()==73,"Explicit personal toggles and intensity survive changed defaults");
Files.writeString(path,"{\"bloom\":false}");forgetPreferences();
@@ -32,8 +32,14 @@ final class PbrMaterials implements AutoCloseable {
if(name.equals("iron_block")||name.equals("gold_block")||name.equals("netherite_block")||name.contains("copper")&&!name.contains("ore")&&!name.contains("raw"))
return new Profile(name.contains("oxidized")?.65f:.32f,name.contains("oxidized")?.35f:.85f);
if(name.contains("polished")||name.contains("glazed")||name.equals("diamond_block")||name.equals("emerald_block")||name.equals("ruby_block")||name.equals("sapphire_block"))return new Profile(.42f,0);
// Dry soil keeps its normal relief without a broad white specular coating.
if(java.util.Set.of("dirt","coarse_dirt","rooted_dirt","grass_block","dirt_path","farmland","podzol","mycelium").contains(name))return new Profile(.98f,0);
if(name.contains("wool")||name.contains("leaves"))return new Profile(.98f,0);
if(name.contains("planks")||name.contains("log")||name.contains("wood"))return new Profile(.88f,0);
// All wooden shapes share a matte response, including Nether stems/hyphae.
String woodName=name.startsWith("stripped_")?name.substring(9):name;
for(String wood:new String[]{"oak","spruce","birch","jungle","acacia","dark_oak","mangrove","cherry","pale_oak","bamboo","crimson","warped"})
if(woodName.startsWith(wood+"_")&&Set.of("planks","log","wood","stem","hyphae","slab","stairs","fence","fence_gate","door","trapdoor","button","pressure_plate","sign","wall_sign","hanging_sign","wall_hanging_sign","block","mosaic","mosaic_slab","mosaic_stairs").contains(woodName.substring(wood.length()+1)))return new Profile(.98f,0);
if(name.contains("planks")||name.contains("log")||name.contains("wood"))return new Profile(.98f,0);
return new Profile(.82f,0);
}
Slot slot(TextureAtlasSprite sprite,BlockState state){
@@ -48,7 +48,7 @@ final class PbrTerrain implements AutoCloseable {
long frame=mc.level.getGameTime();var active=new ArrayList<Section>();
for(var render:mc.levelRenderer.visibleSections()){
long key=render.getSectionNode();var pos=render.getRenderOrigin();
if(pos.offset(8,8,8).distToCenterSqr(camera)>Math.pow(VanillaLight.pbrDistance()+16,2))continue;
if(pos.offset(8,8,8).distToCenterSqr(camera)>Math.pow(VanillaLight.materialDistance()+16,2))continue;
var chunk=mc.level.getChunkSource().getChunk(pos.getX()>>4,pos.getZ()>>4,ChunkStatus.FULL,false);
if(chunk==null)continue;
var storage=chunk.getSection(mc.level.getSectionIndex(pos.getY()));
@@ -90,8 +90,8 @@ public final class PixelReflections {
if(uniform==null)uniform=new MappableRingBuffer(()->"Sanctuary reflection parameters",GpuBuffer.USAGE_UNIFORM|GpuBuffer.USAGE_MAP_WRITE,160);
uniform.rotate();try(var map=uniform.currentBuffer().map(false,true)){
Std140Builder.intoBuffer(map.data()).putMat4f(vp).putMat4f(new Matrix4f(vp).invert())
.putVec4(RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,VanillaLight.ssrIntensity()/100f,VanillaLight.pbrIntensity()/100f,VanillaLight.pbrDistance())
.putVec4(128,32,0,0);
.putVec4(RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,VanillaLight.ssrIntensity()/100f,VanillaLight.pbrIntensity()/100f,VanillaLight.ssrDistance())
.putVec4(Math.max(128,VanillaLight.ssrDistance()),32,VanillaLight.materialDistance(),0);
}
int max=java.util.stream.Stream.concat(solid.stream(),transparent.stream()).mapToInt(PbrTerrain.Entry::indices).max().orElse(0);
RenderSystem.getSequentialBuffer(PrimitiveTopology.QUADS).requestIndexCount(max);
@@ -66,13 +66,16 @@ public final class ProceduralPbr {
if(lunar)sun=PixelShadows.sunDirection(sky.moonAngle);
float moon=sky.moonPhase==null?1:(float)(.5+.5*Math.cos(sky.moonPhase.index()*Math.PI/4));
float strength=(lunar?.12f*moon:1)*sky.rainBrightness*Math.clamp((sun.y-.04f)/.16f,0,1);
var sunColour=lunar?new org.joml.Vector3f(1):SolarColour.tint(sky);
boolean shadow=PixelShadows.applied();
scene.rotate();try(var mapped=scene.currentBuffer().map(false,true)){
Std140Builder.intoBuffer(mapped.data()).putMat4f(new Matrix4f(vp).invert())
.putVec4(sun.x,sun.y,sun.z,strength)
.putVec4(VanillaLight.pbrIntensity()/100f,RenderSystem.getDevice().getDeviceInfo().isZZeroToOne()?1:0,shadow?1:0,VanillaLight.pbrDistance())
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,ColoredLights.directionalReady()?1:0,0,0)
.putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0).putVec4(0,0,0,0);
.putVec4(fog.renderDistanceStart,fog.renderDistanceEnd,fog.environmentalStart,fog.environmentalEnd).putVec4(fog.color.w,ColoredLights.directionalReady()?1:0,lunar?0:1,0)
.putVec4(sunColour.x*sunColour.x,sunColour.y*sunColour.y,sunColour.z*sunColour.z,1)
.putVec4((float)(camera.pos.x%256),(float)(camera.pos.y%256),(float)(camera.pos.z%256),0)
.putVec4(0,0,0,0).putVec4(0,0,0,0);
}
var transforms=new ArrayList<GpuBufferSlice>(entries.size());int max=0;
for(var entry:entries){var p=entry.origin();var offset=new Vector3f((float)(p.getX()-camera.pos.x),(float)(p.getY()-camera.pos.y),(float)(p.getZ()-camera.pos.z));
@@ -29,12 +29,15 @@ public final class ShaderOptionsScreen extends OptionsSubScreen {
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.pbr",OptionInstance.cachedConstantTooltip(
Component.translatable("sanctuary.shaders.pbr_help")),VanillaLight.pbr(),VanillaLight::setPbr));
slider("pbr_intensity",VanillaLight.pbrIntensity(),VanillaLight::setPbrIntensity);
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.pbr_distance",OptionInstance.cachedConstantTooltip(
Component.translatable("sanctuary.shaders.pbr_distance_help")),(caption,value)->Component.translatable("sanctuary.shaders.pbr_distance_value",value),
new OptionInstance.Enum<>(List.of(16,32,64,128,256),Codec.INT),VanillaLight.pbrDistance(),VanillaLight::setPbrDistance));
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.ssr",OptionInstance.cachedConstantTooltip(
Component.translatable("sanctuary.shaders.ssr_help")),VanillaLight.ssr(),VanillaLight::setSsr));
slider("ssr_intensity",VanillaLight.ssrIntensity(),VanillaLight::setSsrIntensity);
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.pbr_distance",OptionInstance.cachedConstantTooltip(
Component.translatable("sanctuary.shaders.pbr_distance_help")),(caption,value)->Component.translatable("sanctuary.shaders.pbr_distance_value",value),
new OptionInstance.Enum<>(List.of(16,32,64,128),Codec.INT),VanillaLight.pbrDistance(),VanillaLight::setPbrDistance));
list.addBig(new OptionInstance<Integer>("sanctuary.shaders.ssr_distance",OptionInstance.cachedConstantTooltip(
Component.translatable("sanctuary.shaders.ssr_distance_help")),(caption,value)->Component.translatable("sanctuary.shaders.ssr_distance_value",value),
new OptionInstance.Enum<>(List.of(16,32,64,128,256),Codec.INT),VanillaLight.ssrDistance(),VanillaLight::setSsrDistance));
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.colored_lights",OptionInstance.cachedConstantTooltip(
Component.translatable("sanctuary.shaders.colored_lights_help")),VanillaLight.coloredLights(),VanillaLight::setColoredLights));
list.addBig(OptionInstance.createBoolean("sanctuary.shaders.warm_torches",OptionInstance.cachedConstantTooltip(
@@ -0,0 +1,15 @@
package fr.koka.sanctuary.client.shader;
import net.minecraft.client.renderer.state.level.SkyRenderState;
import org.joml.Vector3f;
/** Uses the exact colour and fade already extracted for Minecraft's horizon. */
public final class SolarColour {
private SolarColour(){}
public static Vector3f tint(SkyRenderState sky){
var colour=sky.sunriseAndSunsetColor;
if(colour==null)return new Vector3f(1);
float weight=Math.clamp(colour.w(),0,1);
return new Vector3f(1).lerp(new Vector3f(colour.x(),colour.y(),colour.z()),weight);
}
}
@@ -29,9 +29,12 @@ public final class VanillaLight {
boolean lensFlare=true;
int lensFlareIntensity=30;
boolean pbr=true;
int pbrIntensity=80;
int pbrDistance=32;
boolean ssr=false;
int pbrIntensity=50;
int pbrBalanceVersion;
int pbrDistance=64;
int ssrDistance=64;
int reflectionDistanceVersion;
boolean ssr=true;
int ssrIntensity=20;
boolean ssgi=true;
boolean coloredLights=true;
@@ -63,7 +66,13 @@ public final class VanillaLight {
preferences.sunshaftIntensity=Math.clamp(preferences.sunshaftIntensity,0,100);
preferences.lensFlareIntensity=Math.clamp(preferences.lensFlareIntensity,0,100);
preferences.ssrIntensity=Math.clamp(preferences.ssrIntensity,0,100);
if(preferences.reflectionDistanceVersion<1&&preferences.pbrDistance==32)preferences.pbrDistance=64;
preferences.reflectionDistanceVersion=1;
preferences.pbrDistance=normalizePbrDistance(preferences.pbrDistance);
preferences.ssrDistance=normalizePbrDistance(preferences.ssrDistance);
// Migrate the previous 80% baseline once; later explicit 80% choices persist.
if(preferences.pbrBalanceVersion<1&&preferences.pbrIntensity==80)preferences.pbrIntensity=50;
preferences.pbrBalanceVersion=1;
preferences.pbrIntensity=Math.clamp(preferences.pbrIntensity,0,100);
preferences.coloredLightIntensity=Math.clamp(preferences.coloredLightIntensity,0,100);
preferences.coloredLightDistance=normalizeColoredLightDistance(preferences.coloredLightDistance);
@@ -110,9 +119,12 @@ public final class VanillaLight {
public static int ssrIntensity(){enabled();return preferences.ssrIntensity;}
public static void setSsr(boolean value){enabled();preferences.ssr=value;save();}
public static void setSsrIntensity(int value){enabled();preferences.ssrIntensity=Math.clamp(value,0,100);save();}
public static int ssrDistance(){enabled();return preferences.ssrDistance;}
public static void setSsrDistance(int value){enabled();preferences.ssrDistance=normalizePbrDistance(value);save();}
static int materialDistance(){return Math.max(pbrDistance(),ssr()&&ssrIntensity()>0?ssrDistance():0);}
public static int pbrDistance(){enabled();return preferences.pbrDistance;}
public static void setPbrDistance(int value){enabled();preferences.pbrDistance=normalizePbrDistance(value);save();}
static int normalizePbrDistance(int value){return value<24?16:value<48?32:value<96?64:128;}
static int normalizePbrDistance(int value){return value<24?16:value<48?32:value<96?64:value<192?128:256;}
public static boolean pbr(){enabled();return preferences.pbr;}
public static int pbrIntensity(){enabled();return preferences.pbrIntensity;}
public static void setPbr(boolean value){enabled();preferences.pbr=value;save();}
@@ -0,0 +1,22 @@
package fr.koka.sanctuary.mixin.client;
import fr.koka.sanctuary.client.ProgressionClient;
import fr.koka.sanctuary.progression.ProgressEffects;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ClientPacketListener;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
/** Respawn creates a new player/menu; extend it before the next inventory packet, not next tick. */
@Mixin(ClientPacketListener.class)
abstract class InventoryRespawnMixin {
@Inject(method = "handleRespawn", at = @At("RETURN"))
private void sanctuary$restoreInventoryLayout(CallbackInfo ci) {
var player = Minecraft.getInstance().player;
if (player != null && ProgressionClient.active()) {
ProgressEffects.bind(player, ProgressionClient.progress);
}
}
}
@@ -0,0 +1,28 @@
package fr.koka.sanctuary.mixin.client;
import com.mojang.renderpearl.api.buffers.GpuBufferSlice;
import fr.koka.sanctuary.client.shader.SolarColour;
import fr.koka.sanctuary.client.shader.VanillaLight;
import net.minecraft.client.renderer.SkyRenderer;
import net.minecraft.client.renderer.state.level.SkyRenderState;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
@Mixin(SkyRenderer.class)
abstract class SolarColourMixin {
@Unique private Vector3f sanctuary$sunTint=new Vector3f(1);
@Inject(method="render",at=@At("HEAD"))
private void sanctuary$nativeSunset(GpuBufferSlice fog,SkyRenderState sky,CallbackInfo ci){
sanctuary$sunTint=VanillaLight.active()?SolarColour.tint(sky):new Vector3f(1);
}
@ModifyArg(method="renderSun",at=@At(value="INVOKE",target="Lnet/minecraft/client/renderer/DynamicGpuData;writeTransform(Lorg/joml/Matrix4f;Lorg/joml/Vector4f;)Lcom/mojang/renderpearl/api/buffers/GpuBufferSlice;"),index=1)
private Vector4f sanctuary$tintSun(Vector4f nativeColour){
return new Vector4f(nativeColour.x*sanctuary$sunTint.x,nativeColour.y*sanctuary$sunTint.y,nativeColour.z*sanctuary$sunTint.z,nativeColour.w);
}
}
@@ -1,7 +1,7 @@
{
"sanctuary.shaders.ssr_intensity_value": "Reflection strength: %s%%",
"sanctuary.shaders.ssr_intensity_help": "Strength of visible landscape reflections on solids and water. Default: 20%.",
"sanctuary.shaders.ssr": "Landscape reflections — experimental",
"sanctuary.shaders.ssr": "SSR",
"sanctuary.shaders.ssr_help": "Colours material texels with the visible landscape. Requires PBR; off-screen objects cannot be reflected.",
"sanctuary.shaders.ssr_intensity": "Reflection strength",
"sanctuary.graphics.default_vulkan": "Vulkan (default)",
@@ -2303,7 +2303,7 @@
"sanctuary.shaders.pbr": "PBR · material relief",
"sanctuary.shaders.pbr_help": "Experimental: procedural block normals and roughness, sun, moon and local light highlights. Enable Colored lights for local direction. Range 32 blocks. ON at 80% by default.",
"sanctuary.shaders.pbr_intensity": "PBR intensity",
"sanctuary.shaders.pbr_intensity_help": "Material relief and highlights, including water. Default: 80%.",
"sanctuary.shaders.pbr_intensity_help": "Material relief and highlights, including water. Default: 50%.",
"sanctuary.shaders.pbr_intensity_value": "PBR intensity: %s %%",
"sanctuary.shaders.lens_flare": "Lens flare · soft rectangles",
"sanctuary.shaders.lens_flare_help": "Soft rectangular sun and moon reflections. Obstacles hide the sources. Independent of the bloom halo.",
@@ -2330,10 +2330,13 @@
"sanctuary.shaders.water_intensity": "Water intensity",
"sanctuary.shaders.water_intensity_help": "At 0%, keeps native rendering. At 100%, applies all three profiles.",
"sanctuary.shaders.water_intensity_value": "Water intensity: %s%%",
"sanctuary.shaders.pbr_distance": "PBR and SSR distance",
"sanctuary.shaders.pbr_distance_value": "PBR and SSR distance: %s blocks",
"sanctuary.shaders.pbr_distance_help": "Shared range for relief and reflecting surfaces. 16, 32, 64 or 128 blocks. Longer distances cost more; mesh memory is bounded.",
"sanctuary.shaders.pbr_distance": "PBR distance",
"sanctuary.shaders.pbr_distance_value": "PBR distance: %s blocks",
"sanctuary.shaders.pbr_distance_help": "Independent PBR range: 16, 32, 64, 128 or 256 blocks. Default: 64 blocks. Limited to visible terrain and mesh budget.",
"sanctuary.shaders.water_blend": "Water blending",
"sanctuary.shaders.water_blend_value": "Water blending: %s × %s blocks",
"sanctuary.shaders.water_blend_help": "Transition width between bed-dependent water colours: 1, 3, 5, 9 or 17 blocks. Default: 9 × 9."
"sanctuary.shaders.water_blend_help": "Transition width between bed-dependent water colours: 1, 3, 5, 9 or 17 blocks. Default: 9 × 9.",
"sanctuary.shaders.ssr_distance": "SSR distance",
"sanctuary.shaders.ssr_distance_value": "SSR distance: %s blocks",
"sanctuary.shaders.ssr_distance_help": "Independent SSR range: 16, 32, 64, 128 or 256 blocks. Default: 64 blocks. Limited to visible terrain and mesh budget."
}
@@ -1,7 +1,7 @@
{
"sanctuary.shaders.ssr_intensity_value": "Intensité des reflets : %s %%",
"sanctuary.shaders.ssr_intensity_help": "Intensité des reflets du paysage visible, sur les solides et l’eau. Défaut : 20 %.",
"sanctuary.shaders.ssr": "Reflets du paysage — expérimental",
"sanctuary.shaders.ssr": "SSR",
"sanctuary.shaders.ssr_help": "Colore les pixels de matière avec le paysage visible. PBR requis ; les objets hors écran ne sont pas réfléchis.",
"sanctuary.shaders.ssr_intensity": "Intensité des reflets",
"sanctuary.graphics.default_vulkan": "Vulkan (par défaut)",
@@ -2303,7 +2303,7 @@
"sanctuary.shaders.pbr": "PBR · relief des matériaux",
"sanctuary.shaders.pbr_help": "Expérimental : normales procédurales et rugosité des blocs, reflets du soleil, de la lune et des lumières locales. Activer Lumières colorées pour la direction locale. Portée 32 blocs. Activé par défaut à 80 %.",
"sanctuary.shaders.pbr_intensity": "Intensité PBR",
"sanctuary.shaders.pbr_intensity_help": "Relief et éclats des matières, eau comprise. Défaut : 80 %.",
"sanctuary.shaders.pbr_intensity_help": "Relief et éclats des matières, eau comprise. Défaut : 50 %.",
"sanctuary.shaders.pbr_intensity_value": "Intensité PBR : %s %%",
"sanctuary.shaders.lens_flare": "Lens flare · rectangles doux",
"sanctuary.shaders.lens_flare_help": "Reflets rectangulaires du soleil et de la lune, aux bords dégradés. Les obstacles masquent les sources. Indépendant du halo de bloom.",
@@ -2330,10 +2330,13 @@
"sanctuary.shaders.water_intensity": "Intensité de l’eau",
"sanctuary.shaders.water_intensity_help": "À 0 %, conserve le rendu natif. À 100 %, applique les trois profils.",
"sanctuary.shaders.water_intensity_value": "Intensité de l’eau: %s%%",
"sanctuary.shaders.pbr_distance": "Distance PBR et SSR",
"sanctuary.shaders.pbr_distance_value": "Distance PBR et SSR : %s blocs",
"sanctuary.shaders.pbr_distance_help": "Portée commune du relief et des surfaces réfléchissantes. 16, 32, 64 ou 128 blocs. Les distances élevées coûtent davantage ; budget de maillage limité.",
"sanctuary.shaders.pbr_distance": "Distance PBR",
"sanctuary.shaders.pbr_distance_value": "Distance PBR: %s blocs",
"sanctuary.shaders.pbr_distance_help": "Portée indépendante du PBR : 16, 32, 64, 128 ou 256 blocs. Défaut : 64 blocs. Limitée au terrain visible et au budget de maillage.",
"sanctuary.shaders.water_blend": "Mélange des eaux",
"sanctuary.shaders.water_blend_value": "Mélange des eaux : %s × %s blocs",
"sanctuary.shaders.water_blend_help": "Étendue de la transition des teintes selon le fond : 1, 3, 5, 9 ou 17 blocs. Défaut : 9 × 9."
"sanctuary.shaders.water_blend_help": "Étendue de la transition des teintes selon le fond : 1, 3, 5, 9 ou 17 blocs. Défaut : 9 × 9.",
"sanctuary.shaders.ssr_distance": "Distance SSR",
"sanctuary.shaders.ssr_distance_value": "Distance SSR: %s blocs",
"sanctuary.shaders.ssr_distance_help": "Portée indépendante du SSR : 16, 32, 64, 128 ou 256 blocs. Défaut : 64 blocs. Limitée au terrain visible et au budget de maillage."
}
@@ -25,6 +25,8 @@ layout(std140) uniform PbrScene {
vec4 PbrParams;
vec4 PbrFog;
vec4 PbrEnvironment;
vec4 PbrSunColour;
vec4 PbrWaterOrigin;
};
layout(std140) uniform PixelShadows {
mat4 InverseViewProjection;
@@ -102,6 +104,8 @@ void main(){
// Derivatives precede divergent discard; UV-space tangents preserve rotated/mirrored block textures.
vec3 dx=dFdx(relativePosition),dy=dFdy(relativePosition);
vec2 tx=dFdx(blockUv),ty=dFdy(blockUv);
vec2 atlasSize=vec2(textureSize(BlockAtlas,0));
float texelFootprint=max(length(tx*atlasSize),length(ty*atlasSize));
float det=tx.x*ty.y-tx.y*ty.x;
vec3 baseNormal=normalize(cross(dx,dy));if(dot(baseNormal,-relativePosition)<0.0)baseNormal=-baseNormal;
// Exact receiver depth is tested by the GPU; final scene depth also masks entities.
@@ -119,6 +123,14 @@ void main(){
// RG stores signed height derivatives; B roughness, A metalness, not transparency.
float intensity=PbrParams.x;
vec2 slope=(material.rg*255.0-128.0)/127.0*intensity*(family>1.5?.24:2.0);
// Slight world-anchored variation avoids identical crests on every water block.
float waterVariation=1.0;
if(family>1.5){
uvec3 cell=uvec3(floor(mod(relativePosition+PbrWaterOrigin.xyz,256.0)*16.0+.001));
uint seed=cell.x*1597334677u^cell.y*3812015801u^cell.z*2798796415u;
seed=(seed^(seed>>16u))*2246822519u;seed^=seed>>13u;
waterVariation=.75+.25*float(seed&65535u)/65535.0;
}
vec3 n=normalize(baseNormal+tangent*slope.x+bitangent*slope.y);
// Reject degenerate/interpolated geometry without replacing the native scene with NaNs.
if(any(isnan(n))||any(isinf(n))||dot(relativePosition,relativePosition)<.000001)discard;
@@ -128,9 +140,12 @@ void main(){
// A broad solar lobe gives water an extended glitter path at grazing angles.
// Local lamps retain the material's original narrow lobe.
vec4 solarMaterial=material;
solarMaterial.b=max(material.b,family>1.5?.34:.24);
solarMaterial.b=max(material.b,family>1.5?.42:.24);
vec3 specular=localShine(n,v,l,solarMaterial,albedo);
float solarRadiance=family>1.5?20.0:4.0;
// PbrEnvironment.z identifies sunlight: moonlight keeps its modest base radiance.
float solarRadiance=family<.5?1.0:mix(1.0,family>1.5?6.0:4.0,PbrEnvironment.z);
// Same native sunset tint as the sun disc, converted to linear light.
vec3 sunColour=PbrSunColour.rgb;
float fog=max(clamp((max(length(relativePosition.xz),abs(relativePosition.y))-PbrFog.x)/max(PbrFog.y-PbrFog.x,.001),0.0,1.0),
clamp((length(relativePosition)-PbrFog.z)/max(PbrFog.w-PbrFog.z,.001),0.0,1.0));
float amount=PbrSun.w*pow(tint.a,8.0)*visibility(relativePosition,baseNormal)*(1.0-fog*PbrEnvironment.x)
@@ -146,9 +161,36 @@ void main(){
float localRelief=family<.5?(max(dot(n,arrival.xyz),0.0)-max(dot(baseNormal,arrival.xyz),0.0))*.7:0.0;
lit=clamp(lit*(1.0+localRelief*localAmount),0.0,1.0);
}
// Preserve bright solar highlights instead of capping them at a medium grey.
vec3 shine=min((specular*amount*solarRadiance+localShine(n,v,arrival.xyz,material,albedo)*localAmount)*intensity*5.0,vec3(8.0));
lit+=(1.0-lit)*(1.0-exp(-shine));
// Restore the pre-water response for opaque blocks. Water/glass retain their own highlights.
// Fully matte wood, soil, foliage and fibres receive normal relief, not a broad white specular veil.
// Keep normal relief independent from the additive specular response.
float fullyMatte=smoothstep(.9,.98,material.b)*(1.0-material.a);
float shineWeight=family<.5?1.0-fullyMatte:1.0;
float shineGain=family<.5?3.0:5.0;
vec3 skyShine=min(specular*amount*solarRadiance*intensity*shineGain*shineWeight,vec3(8.0));
vec3 lampShine=min(localShine(n,v,arrival.xyz,material,albedo)*localAmount*intensity*shineGain*shineWeight,vec3(8.0));
if(family<.5){
// Share the original 0.5 budget across sun and lamps, preserving the sunset tint.
vec3 total=skyShine+lampShine;
vec3 scale=min(vec3(1.0),vec3(.5)/max(total,vec3(.00001)));
skyShine*=scale;lampShine*=scale;
}
if(family>1.5&&PbrEnvironment.z>.5){
// Soft-light/screen blend per native texel: broad daylight lifts the water
// without replacing its colour and detail with a saturated white cone.
// Glints follow animated wave facets reflecting the sun towards the eye.
// Their pulse comes from the native wave animation, not an unrelated timer.
vec3 halfSum=l+v;
float alignment=pow(max(dot(n,halfSum*inversesqrt(max(dot(halfSum,halfSum),.00001))),0.0),384.0);
float crest=smoothstep(.025,.18,length((material.rg*255.0-128.0)/127.0));
float detail=1.0-smoothstep(.75,2.0,texelFootprint);
float glint=alignment*crest*waterVariation*detail;
vec3 glintShine=skyShine*(1.0+glint*1.2);
vec3 weight=glintShine/(1.0+glintShine);
vec3 highlight=max(lit,mix(sunColour,sqrt(lit)*sunColour,mix(.55,.30,glint)));
lit=mix(lit,highlight,weight);
}else lit+=(1.0-lit)*(1.0-exp(-skyShine*sunColour));
lit+=(1.0-lit)*(1.0-exp(-lampShine));
if(any(isnan(lit))||any(isinf(lit)))discard;
fragColor=vec4(sqrt(clamp(lit,0.0,1.0)),1.0);
}
@@ -9,9 +9,15 @@ layout(location=2) in vec2 maskUv;
layout(location=3) in vec4 tint;
void main(){
vec2 dx=dFdx(blockUv),dy=dFdy(blockUv);
if(length(relativePosition)>=ReflectionParams.w)discard;
if(length(relativePosition)>=ReflectionLimits.z)discard;
// Minecraft's world projection uses reverse depth: zero is the far plane.
float opaque=texelFetch(OpaqueDepth,ivec2(gl_FragCoord.xy),0).r;
if(gl_FragCoord.z+0.0000001<opaque)discard;
// Native and material transforms differ slightly in evaluation order. A fixed
// depth-buffer epsilon becomes too strict near the camera (reverse-Z).
// Compare world distances with the same bounded margin as the colour passes;
// the receiver attachment still selects the nearest material with exact depth.
vec2 uv=gl_FragCoord.xy/vec2(textureSize(OpaqueDepth,0));
vec4 visible=ReflectionInverseVP*vec4(uv*2.0-1.0,ReflectionParams.x>.5?opaque:opaque*2.0-1.0,1);
if(opaque>0.0&&length(visible.xyz/visible.w)+.005<length(relativePosition))discard;
if(maskUv.y<2.0&&textureGrad(BlockAtlas,blockUv,dx,dy).a<.99)discard;
}
@@ -213,6 +213,7 @@
"client.AbstractContainerScreenAccessor",
"client.SlotPositionAccessor",
"client.InventoryScreenMixin",
"client.InventoryRespawnMixin",
"client.InventoryEquipmentTooltipMixin",
"client.RecipeBookScreenMixin",
"client.RecipeBookComponentMixin",
@@ -263,6 +264,7 @@
"client.InventoryPointerAccess",
"client.OperatorWorldOptionsMixin",
"client.StarSkyMixin",
"client.SolarColourMixin",
"client.ProgressiveFogMixin",
"client.ShaderSaturationMixin",
"client.WeatherEnvironmentMixin",
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "beta.146"
version = "beta.151"
pack-format = "packwiz:1.1.0"
[index]