Implement climate and weather features in world simulation; enhance API with weather retrieval and climate selection options, update UI to support climate selection during world creation, and improve weather display in the game interface.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
using TheLivingWorld.Core.Simulation;
|
||||
|
||||
namespace TheLivingWorld.Tests;
|
||||
|
||||
public sealed class ClimateTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(1.35, ClimateKind.Equatorial)] // Singapore
|
||||
[InlineData(13.75, ClimateKind.Savanna)] // Bangkok
|
||||
[InlineData(30.05, ClimateKind.HotDesert)] // Cairo
|
||||
[InlineData(37.98, ClimateKind.Mediterranean)] // Athens
|
||||
[InlineData(51.51, ClimateKind.Oceanic)] // London
|
||||
[InlineData(55.75, ClimateKind.CentralEuropean)] // Moscow
|
||||
[InlineData(62.03, ClimateKind.Siberian)] // Yakutsk
|
||||
[InlineData(78.22, ClimateKind.Tundra)] // Svalbard
|
||||
public void FromLatitude_lands_on_the_expected_band(double latitude, ClimateKind expected)
|
||||
{
|
||||
Assert.Equal(expected, ClimateCatalog.FromLatitude(latitude));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromLatitude_ignores_the_hemisphere()
|
||||
{
|
||||
for (var degrees = 0.0; degrees <= 90.0; degrees += 0.5)
|
||||
Assert.Equal(ClimateCatalog.FromLatitude(degrees), ClimateCatalog.FromLatitude(-degrees));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FromLatitude_is_monotonic_from_the_equator_to_the_pole()
|
||||
{
|
||||
// The bands must not interleave: walking north should never hand back a warmer preset than the last.
|
||||
var previous = -1;
|
||||
|
||||
for (var degrees = 0.0; degrees <= 90.0; degrees += 0.25)
|
||||
{
|
||||
var index = ClimateCatalog.All
|
||||
.Select(static (preset, i) => (preset.Kind, Index: i))
|
||||
.First(entry => entry.Kind == ClimateCatalog.FromLatitude(degrees))
|
||||
.Index;
|
||||
|
||||
Assert.True(index >= previous, $"Latitude {degrees} stepped backwards in the catalogue order.");
|
||||
previous = index;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_preset_is_reachable_and_self_consistent()
|
||||
{
|
||||
Assert.Equal(12, ClimateCatalog.All.Count);
|
||||
|
||||
foreach (var kind in Enum.GetValues<ClimateKind>())
|
||||
{
|
||||
var preset = ClimateCatalog.Get(kind);
|
||||
Assert.Equal(kind, preset.Kind);
|
||||
Assert.False(string.IsNullOrWhiteSpace(preset.Label));
|
||||
Assert.False(string.IsNullOrWhiteSpace(preset.KoppenCode));
|
||||
Assert.False(string.IsNullOrWhiteSpace(preset.Example));
|
||||
Assert.InRange(preset.Humidity, 0f, 1f);
|
||||
Assert.InRange(preset.Wetness, 0f, 1f);
|
||||
Assert.InRange(preset.Storminess, 1, WeatherSystem.MaxSystems);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Presets_the_latitude_rule_cannot_reach_are_reported_as_such()
|
||||
{
|
||||
var reachable = Enumerable.Range(0, 91)
|
||||
.Select(static degrees => ClimateCatalog.FromLatitude(degrees))
|
||||
.ToHashSet();
|
||||
|
||||
foreach (var preset in ClimateCatalog.All)
|
||||
Assert.Equal(reachable.Contains(preset.Kind), ClimateCatalog.IsInferable(preset.Kind));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Band_limits_are_ordered_and_reproduce_the_latitude_rule()
|
||||
{
|
||||
// The create form re-derives FromLatitude on the client from exactly these limits, walking the list
|
||||
// in order, so they have to be ascending and they have to agree at every latitude.
|
||||
var banded = ClimateCatalog.All
|
||||
.Select(static preset => (preset.Kind, Limit: ClimateCatalog.BandLimit(preset.Kind)))
|
||||
.Where(static entry => entry.Limit is not null)
|
||||
.Select(static entry => (entry.Kind, Limit: entry.Limit!.Value))
|
||||
.ToArray();
|
||||
|
||||
Assert.NotEmpty(banded);
|
||||
Assert.Equal(banded.OrderBy(static entry => entry.Limit).ToArray(), banded);
|
||||
|
||||
for (var degrees = 0.0; degrees <= 90.0; degrees += 0.25)
|
||||
{
|
||||
var expected = banded.FirstOrDefault(entry => degrees < entry.Limit, banded[^1]).Kind;
|
||||
Assert.Equal(expected, ClimateCatalog.FromLatitude(degrees));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Get_rejects_an_undefined_climate()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => ClimateCatalog.Get((ClimateKind)99));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user