Compare commits

...
2 Commits
Author SHA1 Message Date
koka 97ff2b54cb Slow seasonal snow pacing in beta.143
Build Sanctuary / build (push) Waiting to run
2026-09-18 12:41:27 +02:00
koka 59346d7f0a Document verified beta.142 publication and Prism synchronization
Build Sanctuary / build (push) Waiting to run
2026-09-18 05:08:09 +02:00
10 changed files with 165 additions and 42 deletions
+16 -5
View File
@@ -20,11 +20,14 @@ La [vision complète](docs/vision.md) conserve les intentions ; le
L’économie, les claims et les dimensions décrits dans la vision restent
à implémenter.
La version **beta.142** utilise Vulkan comme moteur graphique par défaut,
stabilise les rafraîchissements du relief PBR et règle son intensité initiale
à 80 %. Les lectures de textures et calculs de reflets sont sécurisés contre
les résultats indéfinis. Le [ticket](docs/pbr-refresh-beta142.md) distingue les
corrections vérifiées du bruit signalé en mouvement sur le pilote de l'utilisateur.
La version **beta.143** garde le sampler natif de précipitation pour la neige
saisonnière, mais ralentit son rythme : l'accumulation est espacée par un
facteur 12 et la fonte par le facteur temps réel 72. Les protections existantes
restent inchangées : neige construite, biomes froids, hiver, profils neigeux
et météo Vanilla. Le [ticket](docs/realtime-snow-melt-beta143.md) documente le
contrat et les vérifications.
La beta.142 est publiée et synchronisée dans linstance Sanctuary Beta ;
les 923 fichiers personnels suivis sont conservés jusqu'à la prochaine livraison.
La [livraison précédente](docs/clay-workshop-integration-beta110.md) réunit déjà
latelier, les œufs et la neige saisonnière dans linstance **Sanctuary Beta**.
Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées.
@@ -35,6 +38,14 @@ La [carte native beta.005](docs/atlas-beta005.md) prend le relais après
l’étude de Xaero et des alternatives libres.
## beta.143 — Neige saisonnière en temps réel
Accumulation ralentie par un facteur 12 et fonte saisonnière ralentie par le
facteur naturel 72, tout en conservant le sampler natif des colonnes de
précipitation. Deux blocs de neige fondent en environ 2,7 jours réels en été,
5,4 au printemps et 11 en automne.
[Contrat et vérifications](docs/realtime-snow-melt-beta143.md).
## beta.142 — Vulkan par défaut et stabilité PBR
Moteur natif Vulkan prioritaire, relief conservé pendant les reconstructions,
+14
View File
@@ -14,6 +14,20 @@ 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.142 — Vulkan par défaut et stabilité PBR
Vulkan natif prioritaire, rafraîchissements PBR stables et intensité initiale
80 %, calculs de reflets bornés. [Contrat et vérifications](pbr-refresh-beta142.md).
[Release beta.142](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.142)
publiée ; canal `d661260e2bbabb0326c86d6cc543daf986979596`. Deux synchronisations isolées
puis deux dans l'instance Sanctuary Beta réussies. Les 923 fichiers personnels
suivis gardent leurs hashes. Aucun monde personnel ouvert ; sauvegarde préalable
`sanctuary-backups/before-beta.142/`. Archives vérifiées dans `sanctuary-beta/build/`.
Un choix OpenGL explicite reste conservé : sélectionner Vulkan dans les réglages
vidéo et redémarrer pour changer ce choix. La capture Windows de bruit en
mouvement reste à reconfirmer sur son pilote ; les contrôles OpenGL/Vulkan Mac
et la correction du domaine numérique du reflet sont documentés dans le ticket.
## beta.141 — Direction des lumières pour les normales PBR
Directions locales mises en cache, sources portées, confiance réduite lorsque
+19
View File
@@ -152,3 +152,22 @@ SHA-256 du JAR : `0bc2da9df2ef004e6dc78932cfde7b31b7717c63c83ceed789d151177c41bc
- `Sanctuary-Test-beta.142.mrpack` : 10483672 octets ; SHA-256 `d56b81a4aea6649a40a3c49dedee904989943a518ad0ae7c520b808dd0f9b9a7`.
Reçu détaillé ignoré : `build/shader142-artifact.json`.
## Livraison réalisée
[Release beta.142](https://git.botsu.net/koka/sanctuary-beta/releases/tag/beta.142)
publiée depuis `ea151dd5df8c7c5abd0107656f40c723efbf2ebe`. Canal stable :
`d661260e2bbabb0326c86d6cc543daf986979596`. Archives normale/Test, ZIP d'amorçage Prism et template
publiés avec vérification de leurs téléchargements ; tag et artefacts immuables.
Deux synchronisations isolées puis deux synchronisations de l'instance
**Sanctuary Beta** réussies. Les 923 fichiers personnels suivis ont conservé
leurs hashes, notamment mondes, réglages et packs personnels. Aucun monde
personnel ouvert. Sauvegarde préalable : `sanctuary-backups/before-beta.142/`.
L'instance locale conserve son réglage graphique natif « default », qui démarre
désormais avec Vulkan prioritaire. Un Windows explicitement réglé sur OpenGL
doit sélectionner Vulkan dans les réglages vidéo et redémarrer.
Archives locales également dans `sanctuary-beta/build/`. Le checkout de travail
historique modifié `sanctuary-beta` n'a pas été réinitialisé ; seules ces archives
ignorées y ont été déposées. Sources sur `codex/pbr-refresh-beta142`.
+56
View File
@@ -0,0 +1,56 @@
# SNOW-143 — Neige saisonnière en temps réel
Branche `codex/realtime-snow-melt-beta143`, socle beta.142. Minecraft 26.3 /
Java 25. Livraison beta.143.
## Comportement
L'accumulation et la fonte saisonnière conservent le sampler natif de
précipitation `ServerLevel.tickPrecipitation`. Sanctuary ne scanne donc toujours
pas les chunks et ne force aucun chargement de terrain. Le changement porte
seulement sur la probabilité des mutations de neige.
Pendant une chute de neige, Sanctuary ajoute désormais une couche une fois sur
12 échantillons natifs en moyenne. La neige reste visible pendant une session,
mais les colonnes ne montent plus instantanément. À `randomTickSpeed = 3`, une
couche apparaît en moyenne après environ 40 minutes de neige continue.
Pour la fonte, chaque tirage saisonnier est multiplié par `72`, le rapport
entre une journée réelle de 24 heures et une journée Minecraft de 20 minutes.
Une couche fond désormais en moyenne en 4 h 05 en été,
8 h 10 au printemps et 16 h 25 en automne. Avec le plafond actuel de deux blocs,
une colonne météo suivie met environ 2,7 jours réels à disparaître en été,
5,4 jours au printemps et 11 jours en automne. L'hiver ne fond toujours pas.
Les règles de protection restent celles de beta.109 : seule la neige déposée
par la météo Sanctuary depuis l'ajout du suivi `sanctuary:natural_snow_v1`
peut fondre. La neige construite, l'ancienne neige non suivie, les couches
manuelles, les biomes froids, les profils neigeux et la météo Vanilla restent
préservés. Aucun format de sauvegarde ne change.
## Vérifications
Vérifié le 18 septembre 2026 sur macOS, Java 25.
- `./gradlew :sanctuary:runClientGameTest -PsanctuaryClientTests=true
-PsanctuaryMelt109ClientTests=true -PsanctuaryClientNoVsync=true
-PsanctuaryQuickTests=true` : réussi en 57 s, avec le marqueur
`MELT109_NATIVE_PASS`. Le test couvre l'accumulation beta.108 ralentie,
la fonte couche par couche, la provenance persistante, les saisons, la
gamerule, la météo Vanilla, les profils neigeux, les biomes froids et le
rechargement de monde.
- `./gradlew check build assemblePack assembleTestPack -x :sanctuary:runGameTest` :
réussi en 2 min 31 s, 126 tâches. Le GameTest serveur dédié reste exclu
conformément au refus antérieur de son EULA ; les contrôles natifs utilisent
le serveur intégré.
- `python3 scripts/pack.py check` : versions, icône et index packwiz vérifiés.
- Exports MRpack produits avec `packwiz modrinth export`, intégrité ZIP vérifiée.
Les deux MRpack embarquent exactement le JAR Sanctuary construit.
| Artefact | Taille | SHA-256 |
| --- | ---: | --- |
| `sanctuary-beta.143.jar` | 11 323 665 octets | `fd4b663ac2b2a106624c42b09997db229cc14d6c39d44ae83079bc6aa9d39a08` |
| `Sanctuary-beta.143.mrpack` | 10 464 931 octets | `e55caaa41c5a3a2666ff5415846e18f44d504ac34b698f3d974091cfb52364c5` |
| `Sanctuary-Test-beta.143.mrpack` | 10 483 858 octets | `b14bca04c4a48771209d0f7e234ff9ef6b800eb236fb969a6f0dfcee5620dafc` |
| `Sanctuary-Template-beta.143.zip` | 4 354 153 octets | `071fd8da67d1d0224b89889a5fcbc4cd269673dddb7a52f89aa78802f154605a` |
+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.142
pack_version=beta.142
mod_version=beta.143
pack_version=beta.143
resource_pack_version=beta.121
maven_group=fr.koka.sanctuary
jei_version=30.32.0-sanctuary.3
@@ -12,6 +12,7 @@ import java.time.Instant;
import java.time.ZoneOffset;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
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;
@@ -50,24 +51,29 @@ public final class Melt109ClientChecks implements FabricClientGameTest {
for(int x=-15;x<=15;x++)for(int z=-15;z<=15;z++)l.setBlockAndUpdate(new BlockPos(x,79,z),Blocks.STONE.defaultBlockState());
clock(l,"2026-07-10T12:00:00Z",SeasonalWeather.Season.SUMMER);
check(l.getGameRules().get(SeasonalSnowMelt.ENABLED),"Melt enabled by default");
check(SeasonalSnowMelt.REALTIME_SCALE==72,"Real-time snow melt scale is one real day per 72 native days");
check(SeasonalSnowMelt.chance(SeasonalWeather.Season.SUMMER)==72
&&SeasonalSnowMelt.chance(SeasonalWeather.Season.SPRING)==144
&&SeasonalSnowMelt.chance(SeasonalWeather.Season.AUTUMN)==288
&&SeasonalSnowMelt.chance(SeasonalWeather.Season.WINTER)==0,"Seasonal melt chances include the 72x real-time scale");
deposit(l,AT,16);check(l.getBlockState(AT.above()).is(Blocks.SNOW_BLOCK),"Default two-block accumulation retained");
clear(l);step(l,AT,1);
check(layers(l,AT.above())==7&&layers(l,AT)==8,"Summer melts only one top layer");
step(l,AT,7);check(l.getBlockState(AT.above()).isAir()&&layers(l,AT)==8,"Top block melts completely before the lower block");
step(l,AT,8);check(l.getBlockState(AT).isAir()&&!tracked(l,AT),"Natural column vanishes without water; receipt removed");
clear(l);
until(l,AT,5000,()->layers(l,AT.above())==7&&layers(l,AT)==8,"Summer melts only one top layer");
until(l,AT,20000,()->l.getBlockState(AT.above()).isAir()&&layers(l,AT)==8,"Top block melts completely before the lower block");
until(l,AT,20000,()->l.getBlockState(AT).isAir()&&!tracked(l,AT),"Natural column vanishes without water; receipt removed");
var built=new BlockPos(7,80,4);l.setBlockAndUpdate(built,Blocks.SNOW_BLOCK.defaultBlockState());step(l,built,64);check(layers(l,built)==8,"Untracked snow block preserved");
var old=new BlockPos(8,80,4);snow(l,old,5);step(l,old,64);check(layers(l,old)==5,"Untracked old layers preserved");
var base=new BlockPos(9,80,4);snow(l,base,3);deposit(l,base,5);clear(l);step(l,base,16);check(layers(l,base)==3&&!tracked(l,base),"Weather above manual layers melts only back to its base");
var atop=new BlockPos(9,80,6);l.setBlockAndUpdate(atop,Blocks.SNOW_BLOCK.defaultBlockState());deposit(l,atop,8);clear(l);step(l,atop,16);check(layers(l,atop)==8&&l.getBlockState(atop.above()).isAir(),"Weather on a snow construction melts; construction stays");
var base=new BlockPos(9,80,4);snow(l,base,3);deposit(l,base,5);clear(l);until(l,base,20000,()->layers(l,base)==3&&!tracked(l,base),"Weather above manual layers melts only back to its base");
var atop=new BlockPos(9,80,6);l.setBlockAndUpdate(atop,Blocks.SNOW_BLOCK.defaultBlockState());deposit(l,atop,8);clear(l);until(l,atop,20000,()->layers(l,atop)==8&&l.getBlockState(atop.above()).isAir(),"Weather on a snow construction melts; construction stays");
var replaced=new BlockPos(10,80,4);deposit(l,replaced,8);l.setBlockAndUpdate(replaced,Blocks.AIR.defaultBlockState());l.setBlockAndUpdate(replaced,Blocks.SNOW_BLOCK.defaultBlockState());clear(l);step(l,replaced,32);check(layers(l,replaced)==8&&!tracked(l,replaced),"Break and replacement cannot inherit melt permission");
var thickened=new BlockPos(10,80,6);deposit(l,thickened,2);snow(l,thickened,6);clear(l);step(l,thickened,32);check(layers(l,thickened)==6&&!tracked(l,thickened),"Manual layer edit relinquishes natural provenance");
var capped=new BlockPos(12,80,6);deposit(l,capped,8);snow(l,capped.above(),1);clear(l);step(l,capped,32);check(layers(l,capped)==8&&layers(l,capped.above())==1,"Manual thin snow cap protects its natural support");
var roof=new BlockPos(11,80,4);deposit(l,roof,8);l.setBlockAndUpdate(roof.above(3),Blocks.STONE.defaultBlockState());clear(l);step(l,roof,32);check(layers(l,roof)==8,"Covered snow remains untouched");l.setBlockAndUpdate(roof.above(3),Blocks.AIR.defaultBlockState());step(l,roof,1);check(layers(l,roof)==7,"Uncovered natural snow resumes melt");
var roof=new BlockPos(11,80,4);deposit(l,roof,8);l.setBlockAndUpdate(roof.above(3),Blocks.STONE.defaultBlockState());clear(l);step(l,roof,32);check(layers(l,roof)==8,"Covered snow remains untouched");l.setBlockAndUpdate(roof.above(3),Blocks.AIR.defaultBlockState());until(l,roof,10000,()->layers(l,roof)==7,"Uncovered natural snow resumes melt");
command(l,"gamerule sanctuary:seasonal_snow_melt false");step(l,roof,32);check(layers(l,roof)==7,"Operator disables melting");command(l,"gamerule sanctuary:seasonal_snow_melt true");
clock(l,"2026-01-10T12:00:00Z",SeasonalWeather.Season.WINTER);clear(l);step(l,roof,64);check(layers(l,roof)==7,"Winter never melts even with clear weather");
clock(l,"2026-04-10T12:00:00Z",SeasonalWeather.Season.SPRING);clear(l);step(l,roof,64);check(l.getBlockState(roof).isAir(),"Spring progressively melts");
deposit(l,roof,8);clock(l,"2026-10-10T12:00:00Z",SeasonalWeather.Season.AUTUMN);clear(l);step(l,roof,128);check(l.getBlockState(roof).isAir(),"Autumn progressively melts");
clock(l,"2026-04-10T12:00:00Z",SeasonalWeather.Season.SPRING);clear(l);until(l,roof,30000,()->l.getBlockState(roof).isAir(),"Spring progressively melts");
deposit(l,roof,8);clock(l,"2026-10-10T12:00:00Z",SeasonalWeather.Season.AUTUMN);clear(l);until(l,roof,50000,()->l.getBlockState(roof).isAir(),"Autumn progressively melts");
deposit(l,roof,8);clock(l,"2026-07-10T12:00:00Z",SeasonalWeather.Season.SUMMER);
command(l,"sanctuary weather snow");l.getWeatherData().setRaining(false);l.setRainLevel(0);step(l,roof,64);check(layers(l,roof)==8,"Forced summer snowfall blocks melting");
command(l,"sanctuary weather snow_showers");l.getWeatherData().setRaining(false);l.setRainLevel(0);step(l,roof,64);check(layers(l,roof)==8,"Pauses in snow showers also block melting");
@@ -84,7 +90,7 @@ public final class Melt109ClientChecks implements FabricClientGameTest {
var l=server.overworld();clock(l,"2026-07-10T12:00:00Z",SeasonalWeather.Season.SUMMER);clear(l);
check(!l.getGameRules().get(SeasonalSnowMelt.ENABLED),"Melt rule persists across restart");
check(layers(l,PERSIST)==8&&layers(l,PERSIST.above())==8&&tracked(l,PERSIST)&&tracked(l,PERSIST.above()),"Chunk attachment survives native save/reload");
command(l,"gamerule sanctuary:seasonal_snow_melt true");step(l,PERSIST,16);
command(l,"gamerule sanctuary:seasonal_snow_melt true");until(l,PERSIST,30000,()->l.getBlockState(PERSIST).isAir()&&l.getBlockState(PERSIST.above()).isAir()&&!tracked(l,PERSIST), "Loaded natural snow melts and clears provenance");
check(l.getBlockState(PERSIST).isAir()&&l.getBlockState(PERSIST.above()).isAir()&&!tracked(l,PERSIST),"Loaded natural snow melts and clears provenance: base="+l.getBlockState(PERSIST)+", top="+l.getBlockState(PERSIST.above())+", sky="+l.canSeeSky(PERSIST.above(2))+", active="+WeatherService.active(l)+", biome="+l.getBiome(PERSIST)+", season="+WeatherService.season(server));
check(layers(l,new BlockPos(7,80,4))==8&&layers(l,new BlockPos(9,80,4))==3,"Built and preexisting snow preserved across restart");
});
@@ -92,13 +98,15 @@ public final class Melt109ClientChecks implements FabricClientGameTest {
System.out.println("MELT109_NATIVE_PASS: accumulation regression, top-down melt, seasons, profiles, cold biomes, constructions, edit invalidation, gamerule, native chunk save/reload");
}finally{intro.set(false);}
}
private static void deposit(ServerLevel l,BlockPos pos,int count){l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,1,l.getServer());command(l,"sanctuary weather snow");l.getWeatherData().setRaining(true);l.setRainLevel(1);step(l,pos,count);l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,0,l.getServer());}
private static void deposit(ServerLevel l,BlockPos pos,int count){l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,1,l.getServer());command(l,"sanctuary weather snow");l.getWeatherData().setRaining(true);l.setRainLevel(1);int target=depth(l,pos)+count;until(l,pos,Math.max(1000,count*200),()->depth(l,pos)>=target,"Weather snow reaches depth "+target+" at "+pos);l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,0,l.getServer());}
private static void clear(ServerLevel l){command(l,"sanctuary weather clear");l.getWeatherData().setRaining(false);l.setRainLevel(0);}
private static void clock(ServerLevel l,String time,SeasonalWeather.Season expected){try{var field=RealtimeService.Session.class.getDeclaredField("clock");field.setAccessible(true);field.set(RealtimeService.session(l.getServer()),Clock.fixed(Instant.parse(time),ZoneOffset.UTC));check(WeatherService.season(l.getServer())==expected,"Test calendar: "+expected);}catch(ReflectiveOperationException e){throw new AssertionError(e);}}
private static boolean tracked(ServerLevel l,BlockPos p){var m=l.getChunkAt(p).getAttached(SeasonalSnowMelt.NATURAL);return m!=null&&m.containsKey(Long.toString(p.asLong()));}
private static void snow(ServerLevel l,BlockPos p,int layers){l.setBlockAndUpdate(p,Blocks.SNOW.defaultBlockState().setValue(SnowLayerBlock.LAYERS,layers));}
private static int layers(ServerLevel l,BlockPos p){return SeasonalSnowMelt.layers(l.getBlockState(p));}
private static int depth(ServerLevel l,BlockPos p){int total=0;for(int i=0;i<32;i++){int layers=layers(l,p.above(i));if(layers==0)break;total+=layers;}return total;}
private static void step(ServerLevel l,BlockPos p,int n){for(int i=0;i<n;i++)l.tickPrecipitation(p);}
private static void until(ServerLevel l,BlockPos p,int max,BooleanSupplier condition,String message){for(int i=0;i<max&&!condition.getAsBoolean();i++)l.tickPrecipitation(p);check(condition.getAsBoolean(),message);}
private static void command(ServerLevel l,String s){l.getServer().getCommands().performPrefixedCommand(l.getServer().createCommandSourceStack(),s);}
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);}
@@ -6,6 +6,7 @@ import fr.koka.sanctuary.progression.SanctuaryGameplay;
import fr.koka.sanctuary.weather.SnowAccumulation;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
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;
@@ -45,42 +46,45 @@ public final class Snow108ClientChecks implements FabricClientGameTest {
if(enabled) {
command(l,"sanctuary weather snow");l.setRainLevel(1);
check(l.getGameRules().get(SnowAccumulation.MAX_HEIGHT)==2,"Default two blocks");
for(int i=1;i<=7;i++){l.tickPrecipitation(AT);check(layers(l,AT)==i,"One layer per precipitation event: "+i);}
check(SnowAccumulation.REALTIME_SCALE==12,"Snow accumulation uses the 12x real-time arrival scale");
for(int i=1;i<=7;i++){int expected=i;until(l,AT,1000,()->layers(l,AT)==expected,"Layer "+i+" arrives through the native sampler");}
var pig=EntityTypes.PIG.create(l,EntitySpawnReason.COMMAND);pig.setNoAi(true);pig.setNoGravity(true);pig.setPos(4.5,80.75,4.5);l.addFreshEntity(pig);
l.tickPrecipitation(AT);check(l.getBlockState(AT).is(Blocks.SNOW_BLOCK),"Eighth layer becomes solid snow block");
until(l,AT,1000,()->l.getBlockState(AT).is(Blocks.SNOW_BLOCK),"Eighth layer becomes solid snow block");
check(l.getBlockState(AT).getCollisionShape(l,AT,CollisionContext.empty()).bounds().maxY==1,"Full block collision");
check(pig.getY()>=81,"Growth lifts a standing entity");pig.discard();
l.tickPrecipitation(AT);check(layers(l,AT.above())==1,"New layer begins above solid snow");
for(int i=0;i<30;i++)l.tickPrecipitation(AT);
until(l,AT,1000,()->layers(l,AT.above())==1,"New layer begins above solid snow");
until(l,AT,2000,()->l.getBlockState(AT.above()).is(Blocks.SNOW_BLOCK),"Default cap fills a second block");
check(l.getBlockState(AT.above()).is(Blocks.SNOW_BLOCK)&&l.getBlockState(AT.above(2)).isAir(),"Default cap at two blocks");
command(l,"gamerule sanctuary:snow_accumulation_height 4");
for(int i=0;i<30;i++)l.tickPrecipitation(AT);
until(l,AT,5000,()->l.getBlockState(AT.above(3)).is(Blocks.SNOW_BLOCK),"Raised cap fills the fourth block");
check(l.getBlockState(AT.above(3)).is(Blocks.SNOW_BLOCK)&&l.getBlockState(AT.above(4)).isAir(),"Operator raises cap to four blocks");
command(l,"gamerule sanctuary:snow_accumulation_height -1");
for(int i=0;i<16;i++)l.tickPrecipitation(AT);
until(l,AT,3000,()->l.getBlockState(AT.above(5)).is(Blocks.SNOW_BLOCK),"Unlimited mode fills beyond the cap");
check(l.getBlockState(AT.above(5)).is(Blocks.SNOW_BLOCK),"Unlimited mode continues upward");
var fresh=new BlockPos(-5,80,-5);command(l,"gamerule sanctuary:snow_accumulation_height 0");l.tickPrecipitation(fresh);check(l.getBlockState(fresh).isAir(),"Sanctuary zero stops deposits");
command(l,"gamerule sanctuary:snow_accumulation_height 2");l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,0,server);l.tickPrecipitation(fresh);check(l.getBlockState(fresh).isAir(),"Native zero remains authoritative");l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,1,server);
l.setRainLevel(0);l.getWeatherData().setRaining(false);l.tickPrecipitation(fresh);check(l.getBlockState(fresh).isAir(),"No accumulation between showers");
command(l,"sanctuary weather snow_showers");l.setRainLevel(1);l.getWeatherData().setRaining(true);l.tickPrecipitation(fresh);check(layers(l,fresh)==1,"Snow showers accumulate while falling");
var flower=new BlockPos(-6,80,-6);l.setBlockAndUpdate(flower.below(),Blocks.GRASS_BLOCK.defaultBlockState());l.setBlockAndUpdate(flower,Blocks.DANDELION.defaultBlockState());l.tickPrecipitation(flower);check(l.getBlockState(flower).is(Blocks.DANDELION),"Plant preserved");
var water=new BlockPos(-7,79,-7);l.setBlockAndUpdate(water,Blocks.WATER.defaultBlockState());l.tickPrecipitation(water);check(l.getBlockState(water).is(Blocks.WATER)&&l.getBlockState(water.above()).isAir(),"Open water not filled with snow");
var roof=new BlockPos(7,83,-7);l.setBlockAndUpdate(roof,Blocks.STONE.defaultBlockState());l.tickPrecipitation(roof);check(layers(l,roof.above())==1&&l.getBlockState(roof.below()).isAir(),"Snow on roof, not indoors");
var fresh=new BlockPos(-5,80,-5);command(l,"gamerule sanctuary:snow_accumulation_height 0");step(l,fresh,200);check(l.getBlockState(fresh).isAir(),"Sanctuary zero stops deposits");
command(l,"gamerule sanctuary:snow_accumulation_height 2");l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,0,server);step(l,fresh,200);check(l.getBlockState(fresh).isAir(),"Native zero remains authoritative");l.getGameRules().set(GameRules.MAX_SNOW_ACCUMULATION_HEIGHT,1,server);
l.setRainLevel(0);l.getWeatherData().setRaining(false);step(l,fresh,200);check(l.getBlockState(fresh).isAir(),"No accumulation between showers");
command(l,"sanctuary weather snow_showers");l.setRainLevel(1);l.getWeatherData().setRaining(true);until(l,fresh,1000,()->layers(l,fresh)==1,"Snow showers accumulate while falling");
var flower=new BlockPos(-6,80,-6);l.setBlockAndUpdate(flower.below(),Blocks.GRASS_BLOCK.defaultBlockState());l.setBlockAndUpdate(flower,Blocks.DANDELION.defaultBlockState());step(l,flower,200);check(l.getBlockState(flower).is(Blocks.DANDELION),"Plant preserved");
var water=new BlockPos(-7,79,-7);l.setBlockAndUpdate(water,Blocks.WATER.defaultBlockState());step(l,water,200);check(l.getBlockState(water).is(Blocks.WATER)&&l.getBlockState(water.above()).isAir(),"Open water not filled with snow");
var roof=new BlockPos(7,83,-7);l.setBlockAndUpdate(roof,Blocks.STONE.defaultBlockState());until(l,roof,1000,()->layers(l,roof.above())==1,"Snow on roof arrives");check(layers(l,roof.above())==1&&l.getBlockState(roof.below()).isAir(),"Snow on roof, not indoors");
var cauldron=new BlockPos(8,80,-8);l.setBlockAndUpdate(cauldron,Blocks.CAULDRON.defaultBlockState());for(int i=0;i<200;i++)l.tickPrecipitation(cauldron);check(l.getBlockState(cauldron).is(Blocks.POWDER_SNOW_CAULDRON),"Native cauldron snow filling preserved");
command(l,"sanctuary weather rain");l.setRainLevel(1);var rainy=new BlockPos(10,80,0);l.tickPrecipitation(rainy);check(l.getBlockState(rainy).isAir(),"Temperate rain is not snow");
command(l,"sanctuary weather vanilla");cold(l);for(int i=0;i<8;i++)l.tickPrecipitation(new BlockPos(0,80,0));check(l.getBlockState(new BlockPos(0,80,0)).is(Blocks.SNOW_BLOCK),"Cold biome also accumulates with vanilla weather");
command(l,"sanctuary weather vanilla");cold(l);until(l,new BlockPos(0,80,0),2000,()->l.getBlockState(new BlockPos(0,80,0)).is(Blocks.SNOW_BLOCK),"Cold biome also accumulates with vanilla weather");
var light=new BlockPos(11,80,11);l.setBlockAndUpdate(light.below(),Blocks.GLOWSTONE.defaultBlockState());command(l,"sanctuary weather snow");l.setRainLevel(1);
} else {
cold(l);for(int i=0;i<40;i++)l.tickPrecipitation(AT);check(layers(l,AT)==1&&l.getBlockState(AT.above()).isAir(),"Non-Sanctuary world keeps native one-layer limit");
}
});
if(enabled){c.waitTicks(8);world.getServer().runOnServer(s->{var l=s.overworld();var light=new BlockPos(11,80,11);check(l.getBrightness(LightLayer.BLOCK,light)>=10,"Test light propagated");for(int i=0;i<16;i++)l.tickPrecipitation(light);check(l.getBlockState(light).isAir(),"Bright surfaces do not accumulate");});}
if(enabled){c.waitTicks(8);world.getServer().runOnServer(s->{var l=s.overworld();var light=new BlockPos(11,80,11);check(l.getBrightness(LightLayer.BLOCK,light)>=10,"Test light propagated");step(l,light,400);check(l.getBlockState(light).isAir(),"Bright surfaces do not accumulate");});}
}
System.out.println("SNOW108_NATIVE_PASS: layers, solid blocks, vertical stacking, entity lift, caps and commands, weather, light, supports, cauldron and vanilla world");
}finally{intro.set(false);}
}
private static void cold(ServerLevel l){command(l,"fillbiome -8 76 -8 8 88 8 minecraft:snowy_plains");l.getWeatherData().setRaining(true);l.setRainLevel(1);}
private static int layers(ServerLevel l,BlockPos p){var s=l.getBlockState(p);return s.is(Blocks.SNOW)?s.getValue(SnowLayerBlock.LAYERS):0;}
private static void step(ServerLevel l,BlockPos p,int n){for(int i=0;i<n;i++)l.tickPrecipitation(p);}
private static void until(ServerLevel l,BlockPos p,int max,BooleanSupplier condition,String message){for(int i=0;i<max&&!condition.getAsBoolean();i++)l.tickPrecipitation(p);check(condition.getAsBoolean(),message);}
private static void command(ServerLevel l,String s){l.getServer().getCommands().performPrefixedCommand(l.getServer().createCommandSourceStack(),s);}
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);}
@@ -21,6 +21,9 @@ import net.minecraft.world.level.levelgen.Heightmap;
/** Melt only known weather deposits, using the existing precipitation column sampler. */
public final class SeasonalSnowMelt {
/** One real day lasts 72 native Minecraft days. Keep the native sampler, slow only its melt chance. */
public static final int REALTIME_SCALE = 72;
public static final GameRule<Boolean> ENABLED = GameRuleBuilder.forBoolean(true)
.category(GameRuleCategory.UPDATES).buildAndRegister(SanctuaryMod.id("seasonal_snow_melt"));
@@ -77,12 +80,7 @@ public final class SeasonalSnowMelt {
var chunk = level.getChunkAt(column);
var entries = chunk.getAttached(NATURAL);
if (entries == null || entries.isEmpty()) return;
int chance = switch (WeatherService.season(level.getServer())) {
case WINTER -> 0;
case SPRING -> 2;
case SUMMER -> 1;
case AUTUMN -> 4;
};
int chance = chance(WeatherService.season(level.getServer()));
if (chance == 0 || level.getRandom().nextInt(chance) != 0) return;
var pos = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, column);
// Thin layers are above the heightmap; a full snow block is just below it.
@@ -103,5 +101,15 @@ public final class SeasonalSnowMelt {
if (level.setBlockAndUpdate(pos, after)) remember(level, pos, entry.base, after);
}
public static int chance(SeasonalWeather.Season season) {
int seasonal = switch (season) {
case WINTER -> 0;
case SPRING -> 2;
case SUMMER -> 1;
case AUTUMN -> 4;
};
return seasonal == 0 ? 0 : seasonal * REALTIME_SCALE;
}
private static String key(BlockPos pos) { return Long.toString(pos.asLong()); }
}
@@ -16,8 +16,11 @@ import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.gamerules.GameRule;
import net.minecraft.world.level.gamerules.GameRuleCategory;
/** One real layer per native precipitation tick; no additional chunk or entity scan. */
/** Weather snow accumulation, using the native precipitation sampler without chunk scans. */
public final class SnowAccumulation {
/** Snowfall should remain visible during a session, but no longer stack instantly. */
public static final int REALTIME_SCALE = 12;
public static final GameRule<Integer> MAX_HEIGHT = GameRuleBuilder.forInteger(2)
.range(-1, 128).category(GameRuleCategory.UPDATES)
.buildAndRegister(SanctuaryMod.id("snow_accumulation_height"));
@@ -38,7 +41,7 @@ public final class SnowAccumulation {
/** Return whether vanilla should perform its own deposit. */
public static boolean replaceNative(ServerLevel level, BlockPos pos, boolean canSnow) {
if (!canSnow || !ENABLED.contains(level.getServer())) return canSnow;
if (canSnow) deposit(level, pos);
if (level.getRandom().nextInt(REALTIME_SCALE) == 0) deposit(level, pos);
return false;
}
+1 -1
View File
@@ -1,6 +1,6 @@
name = "Sanctuary"
author = "KOKA99CAB"
version = "beta.142"
version = "beta.143"
pack-format = "packwiz:1.1.0"
[index]