mud/tests/test_weather.py
Jared Miller 4b0a7315c1
Add weather system with tests
Implements procedural weather transitions with:
- WeatherCondition enum (clear, cloudy, rain, storm, snow, fog)
- WeatherState dataclass (condition + intensity 0-1)
- get_weather_description() for atmospheric text varying by intensity
- advance_weather() with probabilistic transitions based on season/climate
- Climate profiles: temperate (balanced), arid (clear/rare rain),
  arctic (snow/fog/cloudy)
2026-02-14 16:18:03 -05:00

208 lines
7.2 KiB
Python

"""Tests for weather system."""
import random
from mudlib.weather import (
WeatherCondition,
WeatherState,
advance_weather,
get_weather_description,
)
def test_weather_condition_enum():
assert WeatherCondition.clear.value == "clear"
assert WeatherCondition.cloudy.value == "cloudy"
assert WeatherCondition.rain.value == "rain"
assert WeatherCondition.storm.value == "storm"
assert WeatherCondition.snow.value == "snow"
assert WeatherCondition.fog.value == "fog"
def test_weather_state_dataclass():
state = WeatherState(condition=WeatherCondition.rain, intensity=0.5)
assert state.condition == WeatherCondition.rain
assert state.intensity == 0.5
def test_get_weather_description_returns_non_empty():
state = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
description = get_weather_description(state)
assert isinstance(description, str)
assert len(description) > 0
def test_get_weather_description_varies_by_condition():
clear = get_weather_description(
WeatherState(condition=WeatherCondition.clear, intensity=0.5)
)
rain = get_weather_description(
WeatherState(condition=WeatherCondition.rain, intensity=0.5)
)
snow = get_weather_description(
WeatherState(condition=WeatherCondition.snow, intensity=0.5)
)
# Different conditions should produce different descriptions
assert clear != rain
assert rain != snow
assert clear != snow
def test_get_weather_description_varies_by_intensity():
light_rain = get_weather_description(
WeatherState(condition=WeatherCondition.rain, intensity=0.2)
)
heavy_rain = get_weather_description(
WeatherState(condition=WeatherCondition.rain, intensity=0.9)
)
# Different intensities should produce different descriptions
assert light_rain != heavy_rain
def test_advance_weather_returns_new_state():
current = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
rng = random.Random(42)
new_state = advance_weather(current, season="summer", rng=rng)
assert isinstance(new_state, WeatherState)
assert isinstance(new_state.condition, WeatherCondition)
assert 0.0 <= new_state.intensity <= 1.0
def test_advance_weather_is_deterministic_with_seed():
current = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
rng1 = random.Random(42)
state1 = advance_weather(current, season="summer", rng=rng1)
rng2 = random.Random(42)
state2 = advance_weather(current, season="summer", rng=rng2)
assert state1.condition == state2.condition
assert state1.intensity == state2.intensity
def test_advance_weather_transitions_naturally():
# Clear can become cloudy
rng = random.Random(42)
for _ in range(100):
current = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
new_state = advance_weather(current, season="summer", rng=rng)
if new_state.condition == WeatherCondition.cloudy:
break
else:
raise AssertionError("clear never transitioned to cloudy in 100 iterations")
# Cloudy can become rain or clear
rng = random.Random(43)
found_rain = False
found_clear = False
for _ in range(100):
current = WeatherState(condition=WeatherCondition.cloudy, intensity=0.5)
new_state = advance_weather(current, season="summer", rng=rng)
if new_state.condition == WeatherCondition.rain:
found_rain = True
if new_state.condition == WeatherCondition.clear:
found_clear = True
if found_rain and found_clear:
break
assert found_rain or found_clear
def test_storm_transitions_to_rain_or_cloudy():
# Storm should always transition away (doesn't last)
rng = random.Random(44)
found_non_storm = False
for _ in range(50):
current = WeatherState(condition=WeatherCondition.storm, intensity=0.8)
new_state = advance_weather(current, season="summer", rng=rng)
if new_state.condition in (WeatherCondition.rain, WeatherCondition.cloudy):
found_non_storm = True
break
assert found_non_storm, "storm should transition to rain or cloudy"
def test_snow_only_in_winter_autumn():
# Snow in winter
rng = random.Random(45)
found_snow = False
for _ in range(200):
current = WeatherState(condition=WeatherCondition.cloudy, intensity=0.5)
new_state = advance_weather(current, season="winter", rng=rng)
if new_state.condition == WeatherCondition.snow:
found_snow = True
break
assert found_snow, "snow should be possible in winter"
# Snow should be rare or impossible in summer
rng = random.Random(46)
for _ in range(100):
current = WeatherState(condition=WeatherCondition.cloudy, intensity=0.5)
new_state = advance_weather(current, season="summer", rng=rng)
# Should not produce snow in summer
assert new_state.condition != WeatherCondition.snow
def test_climate_temperate_default():
current = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
rng = random.Random(47)
# Should work without climate parameter (defaults to temperate)
new_state = advance_weather(current, season="summer", rng=rng)
assert isinstance(new_state, WeatherState)
def test_climate_arid_favors_clear():
# Arid should heavily favor clear weather
rng = random.Random(48)
clear_count = 0
for _ in range(100):
current = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
new_state = advance_weather(current, season="summer", rng=rng, climate="arid")
if new_state.condition == WeatherCondition.clear:
clear_count += 1
# Arid should stay clear most of the time
assert clear_count > 70, f"arid should favor clear, got {clear_count}/100"
def test_climate_arid_no_snow():
# Arid should never produce snow
rng = random.Random(49)
for _ in range(100):
current = WeatherState(condition=WeatherCondition.cloudy, intensity=0.5)
new_state = advance_weather(current, season="winter", rng=rng, climate="arid")
assert new_state.condition != WeatherCondition.snow
def test_climate_arctic_favors_snow_fog_cloudy():
# Arctic should produce snow, fog, or cloudy frequently
rng = random.Random(50)
arctic_conditions = 0
for _ in range(100):
current = WeatherState(condition=WeatherCondition.cloudy, intensity=0.5)
new_state = advance_weather(current, season="winter", rng=rng, climate="arctic")
if new_state.condition in (
WeatherCondition.snow,
WeatherCondition.fog,
WeatherCondition.cloudy,
):
arctic_conditions += 1
# Arctic should heavily favor snow/fog/cloudy
assert arctic_conditions > 70, (
f"arctic should favor snow/fog/cloudy, got {arctic_conditions}/100"
)
def test_advance_weather_accepts_all_seasons():
current = WeatherState(condition=WeatherCondition.clear, intensity=0.5)
rng = random.Random(51)
for season in ["spring", "summer", "autumn", "winter"]:
new_state = advance_weather(current, season=season, rng=rng)
assert isinstance(new_state, WeatherState)