From 97ff2b54cb63afe8fdd88bfa8f2ebb25dbe3b86d Mon Sep 17 00:00:00 2001 From: koka Date: Fri, 18 Sep 2026 12:41:27 +0200 Subject: [PATCH] Slow seasonal snow pacing in beta.143 --- README.md | 21 +++++-- docs/realtime-snow-melt-beta143.md | 56 +++++++++++++++++++ gradle.properties | 4 +- .../sanctuary/client/Melt109ClientChecks.java | 30 ++++++---- .../sanctuary/client/Snow108ClientChecks.java | 34 ++++++----- .../sanctuary/weather/SeasonalSnowMelt.java | 20 +++++-- .../sanctuary/weather/SnowAccumulation.java | 7 ++- packwiz/pack.toml | 2 +- 8 files changed, 131 insertions(+), 43 deletions(-) create mode 100644 docs/realtime-snow-melt-beta143.md diff --git a/README.md b/README.md index 31e1065..5db0d7a 100644 --- a/README.md +++ b/README.md @@ -20,13 +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 l’instance Sanctuary Beta ; -les 923 fichiers personnels suivis sont conservés. +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à l’atelier, les œufs et la neige saisonnière dans l’instance **Sanctuary Beta**. Les [synchronisations antérieures](docs/git-sync-beta092.md) restent documentées. @@ -37,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, diff --git a/docs/realtime-snow-melt-beta143.md b/docs/realtime-snow-melt-beta143.md new file mode 100644 index 0000000..5013fc3 --- /dev/null +++ b/docs/realtime-snow-melt-beta143.md @@ -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` | diff --git a/gradle.properties b/gradle.properties index 509bf20..f5825be 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java index 069ef5c..77a45c9 100644 --- a/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java +++ b/mods/sanctuary/src/clientGametest/java/fr/koka/sanctuary/client/Melt109ClientChecks.java @@ -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;ilayers(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 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()); } } diff --git a/mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SnowAccumulation.java b/mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SnowAccumulation.java index afc534d..1d31eef 100644 --- a/mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SnowAccumulation.java +++ b/mods/sanctuary/src/main/java/fr/koka/sanctuary/weather/SnowAccumulation.java @@ -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 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; } diff --git a/packwiz/pack.toml b/packwiz/pack.toml index 5b58c01..90775fe 100644 --- a/packwiz/pack.toml +++ b/packwiz/pack.toml @@ -1,6 +1,6 @@ name = "Sanctuary" author = "KOKA99CAB" -version = "beta.142" +version = "beta.143" pack-format = "packwiz:1.1.0" [index]