Skip to content
Edit on GitHub

Author a deterministic weather regime with Weather Lab

verifiedAgainst e50826a · verifiedOn 2026-09-10.

In Voxamine, weather is not a dice roll or an arbitrary timer. It is governed by a continuous, deterministic atmospheric barometric pressure field that drifts across the world terrain along prevailing wind vectors (MASTERPLAN.md §13.2 / §23). A high-pressure system brings clear, sunny skies with sparse fair-weather clouds; a low-pressure trough brings overcast gloom, rainstorms, and mountain blizzards.

You will author and tune a biome weather regime by defining its precipitation susceptibility in BiomeWeatherProfile, evaluating barometric thresholds in WeatherModel.Classify, observing lapse-rate snow line elevation transitions ($Y \ge 72$), and inspecting barometric stability across biomes using the Weather Lab module in the Voxel Workshop (WeatherLabModule.cs) without entering Play Mode or dirtying world saves.

// Pure deterministic weather evaluation:
WeatherSnapshot snapshot = WeatherModel.Classify(pressureNormalized, elevation, snowLine, biome);
// snapshot.Type -> WeatherType.Clear, WeatherType.Rain, or WeatherType.Snow
// snapshot.CloudCover -> [0.12, 1.0]
// snapshot.LightingDimFactor -> [0.48, 1.0]

The weather subsystem connects procedural math, biome classification, physical presentation runtimes, and editor validation tools:

  • Atmospheric Pressure Field (World.Environment) samples 2D continuous noise drifting with the world’s weather clock and wind vector.
  • Weather Model (World.Environment) deterministically evaluates barometric lowness, biome susceptibility, and elevation lapse rates to produce an immutable WeatherSnapshot.
  • Biome Weather Profile (World.Environment) defines the precipitation threshold ($[0, 1]$) and freezing characteristics of each biome archetype.
  • Weather Lab (VoxelWorkshop.Modules.WeatherLab) provides an editor-only analysis tool to audit precipitation thresholds and wind-drifted pressures across biomes.
  • Presentation Layer (HdrpStormPresentationRuntime, VoxelCloudRuntime, RainAmbienceRuntime) consumes the resulting snapshot to modulate volumetric clouds, rain particle density, sky dimming, and thunder delays.
flowchart TD
  subgraph INPUTS["Simulation Inputs"]
    SEED["WorldSeed & WeatherClock\n(elapsed seconds)"]
    WIND["Prevailing Wind Field\n(drift direction & velocity)"]
    COL["World Column (X, Z)\nSurface elevation & BiomeType"]
  end

  subgraph SOLVER["VoxelSandbox.World.Environment"]
    FIELD["AtmosphericPressureField\n(2D drifting noise -> [-1, 1] normalized)"]
    PROFILE["BiomeWeatherProfile\n(PrecipitationSusceptibility & Freezes)"]
    MODEL["WeatherModel.Classify\n(Lowness, onset, lapse rate snow line)"]
    SNAP["WeatherSnapshot\n(WeatherType, Intensity, CloudCover, LightingDim)"]
  end

  subgraph WORKSHOP["Voxel Workshop"]
    LAB["WeatherLabModule\n(Interactive pressure field & biome audit)"]
  end

  subgraph RUNTIME["Presentation & Audio"]
    SKY["SkyCycleBlend & Sunlight\n(lightingDimFactor)"]
    CLOUDS["VoxelCloudRuntime\n(cloudCover density)"]
    STORM["HdrpStormPresentationRuntime\n(Rain / snow particles & lightning)"]
    AUDIO["RainAmbienceRuntime\n(Crossfaded rain & wind beds)"]
  end

  SEED --> FIELD
  WIND --> FIELD
  FIELD --> MODEL
  COL --> MODEL
  PROFILE --> MODEL
  MODEL --> SNAP
  FIELD --> LAB
  MODEL --> LAB
  SNAP --> SKY
  SNAP --> CLOUDS
  SNAP --> STORM
  SNAP --> AUDIO

Crucially, no random state is stored or mutated during weather evaluation. Given the same seed and weather clock, any column in the world yields the exact same weather snapshot across multiple clients or after reloading a save.

Read Docs/MASTERPLAN.md §13.2 (“Environmental systems”) and review:

Review these physical and mathematical constants:

Metric Value Meaning
Settled High ($P_{\text{high}}$) +0.15 normalized ($1018.5\text{ hPa}$) Clear skies, minimal clouds ($< 0.20$), maximum sunlight ($> 0.95$).
Deep Low ($P_{\text{low}}$) -0.78 normalized ($971.0\text{ hPa}$) Extreme cyclonic trough; precipitates across every biome.
Global Snow Line $Y = 72$ blocks Elevation threshold above which precipitation falls as snow regardless of ambient biome warmth.
Mean Sea Level Pressure $1013.25\text{ hPa}$ Atmospheric baseline.
Pressure Half-Range $50.0\text{ hPa}$ Total dynamic range spans $963.25\text{ hPa}$ to $1063.25\text{ hPa}$.

The barometric field is evaluated via AtmosphericPressureField.SampleNormalized:

// Drifts continuously with elapsed weather clock along prevailing wind vector
float pressureNormalized = AtmosphericPressureField.SampleNormalized(
worldX, worldZ, worldSeed, weatherClockSeconds);

The output is bounded in $[-1.0, 1.0]$. A value of $+1.0$ represents maximum anticyclonic high pressure; $-1.0$ represents a violent cyclonic low.

Open BiomeWeatherProfile.cs to tune how a biome responds to barometric dips:

public static BiomeWeatherProfile For(BiomeType biome)
{
return biome switch
{
BiomeType.Desert => new BiomeWeatherProfile(0.05f, false),
BiomeType.Badlands => new BiomeWeatherProfile(0.08f, false),
BiomeType.Savanna => new BiomeWeatherProfile(0.30f, false),
BiomeType.Plains => new BiomeWeatherProfile(0.62f, false),
BiomeType.Forest => new BiomeWeatherProfile(0.82f, false),
BiomeType.Jungle => new BiomeWeatherProfile(1.00f, false),
BiomeType.Mountains => new BiomeWeatherProfile(0.92f, true),
BiomeType.Taiga => new BiomeWeatherProfile(0.90f, true),
BiomeType.Tundra => new BiomeWeatherProfile(0.86f, true),
_ => new BiomeWeatherProfile(0.50f, false)
};
}
  • Arid Biomes (Desert, Badlands): Have near-zero susceptibility ($0.05$). They remain dry under ordinary troughs and only precipitate when a record-breaking low ($P \le -0.95$) passes through.
  • Humid Biomes (Jungle, Forest): Have high susceptibility ($1.0$). Even a mild barometric depression triggers cloud buildup and showers.
  • Freezing Biomes (Tundra, Taiga, Mountains): Mark Freezes: true. Any precipitation in these regions always falls as snow, regardless of elevation.

3. Classification and Elevation Lapse Rates

Section titled “3. Classification and Elevation Lapse Rates”

Inspect WeatherModel.Classify:

// 0 at a settled high (+0.15), 1 at a deep low (-0.78)
float lowness = Mathf.InverseLerp(SettledHighNormalized, DeepLowNormalized, pressureNormalized);
float cloudCover = Mathf.Clamp01(0.12f + 0.9f * lowness);
BiomeWeatherProfile profile = BiomeWeatherProfile.For(biome);
// Onset threshold: susceptible biomes start precipitating early (0.30)
float onset = Mathf.Clamp01(Mathf.Lerp(0.98f, 0.30f, profile.PrecipitationSusceptibility));
float intensity = Mathf.Clamp01(Mathf.InverseLerp(onset, 1f, lowness));
if (intensity <= 0f)
{
float dryDim = Mathf.Lerp(1f, 0.88f, cloudCover);
return WeatherSnapshot.ClearSky(pressureHectopascals, cloudCover, dryDim);
}
// Check freezing conditions: biome character OR high-altitude lapse rate
bool freezing = profile.Freezes || columnSurfaceHeight >= snowLineHeight;
WeatherType type = freezing ? WeatherType.Snow : WeatherType.Rain;
float precipitationDim = type == WeatherType.Snow
? Mathf.Lerp(0.90f, 0.72f, intensity)
: Mathf.Lerp(0.82f, 0.48f, intensity);
return new WeatherSnapshot(type, pressureHectopascals, intensity, cloudCover, precipitationDim);

When elevation reaches or exceeds snowLineHeight ($72$), precipitation converts to snow even in warm biomes (e.g. Plains or Forest), accurately modeling mountainous altitude cooling.

Open WeatherLabModule.cs. The module renders a live analysis table showing:

  1. Prevailing wind vector and drift distance for the chosen weather clock.
  2. Current barometric reading at mid-elevation ($Y=48$) in normalized units and hectopascals.
  3. A complete breakdown of every biome’s precipitation threshold, active state, intensity, and sunlight dim factor.

This allows designers to drag the Weather Clock slider and watch weather fronts sweep across biomes without launching the game.

Run the EditMode test suite to confirm the classification invariants hold:

Terminal window
/opt/unity/Editor/Unity -batchmode -nographics \
-projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \
-runTests -testPlatform EditMode \
-testFilter VoxelSandbox.Tests.WeatherModelTests

Confirm that:

  • Settled highs yield clear skies with high light factors.
  • Deep lows cause heavy rain in jungles.
  • Moderate lows rain in jungles but remain clear in deserts.
  • Cold biomes (Tundra, Taiga, Mountains) precipitate as snow at all elevations.
  • Elevations above the snow line ($Y \ge 72$) precipitate as snow in temperate biomes.
  1. Open Tools → Voxel Sandbox → Voxel Workshop → Weather Lab.
  2. Drag the Weather Clock slider between $0$ and $120\text{ minutes}$.
  3. Observe the barometric pressure fluctuate smoothly.
  4. Verify that when pressure drops below $980\text{ hPa}$, the Plains and Forest rows switch from Clear to Rain.
  5. Confirm the Desert row remains Clear until extreme lows below $970\text{ hPa}$.

To author weather for a newly created biome (e.g. Volcanic Caldera or Salt Flat):

  1. Decide susceptibility and freezing: Choose how readily the environment precipitates ($0.05$ for ultra-arid to $1.0$ for monsoon) and whether it freezes.
  2. Add case to BiomeWeatherProfile.For: Register the new BiomeType in the switch expression.
  3. Verify in Weather Lab: Open the Weather Lab module and verify the new biome row displays the expected onset threshold.
  4. Add EditMode test: Add test assertions in BiomeWeatherProfileTests.cs and WeatherModelTests.cs verifying classification under both high and low pressure.
Symptom Cause Fix
Weather desynchronizes or changes randomly upon loading a save. Weather code called UnityEngine.Random instead of sampling the deterministic pressure field. Only read AtmosphericPressureField.SampleNormalized using the persisted weather clock and world seed.
Mountain peaks rain instead of snowing. The column surface height was not passed to WeatherModel.Classify. Pass the column surface height and compare against snowLineHeight ($72$).
Deserts experience continuous, frequent rain. Precipitation susceptibility was set too high ($> 0.15$). Keep arid susceptibility low ($0.05$ to $0.08$) so only deep cyclonic lows trigger precipitation.
Sunlight dims instantly when a cloud appears. The presentation layer snapped the light dim factor instead of lerping. Let SkyCycleBlend smoothly interpolate light dim factors across frames using Time.deltaTime.
Excessive memory allocations during weather updates. WeatherSnapshot was implemented as a managed class. Keep WeatherSnapshot as an immutable, allocation-free readonly struct.

User-contributed notes

Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works: **bold**, *italic*, `code`, and links.

Notes policy

Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked nofollow.

  1. Loading notes…