Refactor weather system to consolidate weather data handling; remove redundant weather API endpoint and related components, ensuring a single weather reading represents the entire world. Update UI to toggle weather effects and enhance rendering logic for improved visual consistency. Adjust documentation to reflect changes in weather data structure and API responses.

This commit is contained in:
Leonid Pershin
2026-08-17 10:40:52 +03:00
parent c80c9f14e2
commit 062ab497db
22 changed files with 273 additions and 815 deletions
@@ -242,66 +242,27 @@ public sealed class WorldSimulationTests
Assert.Contains(stored.WeatherState.Systems, static system => system.AgeHours > 0f);
}
[Fact]
public void The_weather_field_covers_the_whole_map_and_varies_across_it()
{
using var simulation = WorldSimulation.Create(ReadySummary(), catchUp: false);
var field = simulation.SnapshotWeatherField();
Assert.Equal(ClimateKind.CentralEuropean, field.Climate);
Assert.Equal(WorldSimulation.WeatherGridSize, field.Size);
Assert.Equal(field.Size * field.Size, field.Nodes.Count);
// A drifting pressure system means the corners cannot all read the same pressure.
var pressures = field.Nodes.Select(static node => node.PressureHpa).Distinct().Count();
Assert.True(pressures > 1, "The field is uniform - the pressure systems are not being sampled.");
}
/// <summary>
/// The whole point of a field is that it has shape. A system wide enough to cover the map evenly reads
/// as one flat value with no edge, which is what makes a front invisible however carefully it is drawn.
/// The weather has to actually move. The pressure systems drift, so a world sampled hours apart must not
/// keep reporting the same sky - that was the failure that hid behind a field which never varied.
/// </summary>
[Fact]
public void A_stormy_world_has_real_structure_across_the_map()
public void The_weather_changes_as_the_systems_drift_over()
{
var summary = ReadySummary() with { Climate = ClimateKind.Oceanic, Latitude = 51.51 };
using var simulation = WorldSimulation.Create(summary, catchUp: false);
// Sample a few independent skies: any one roll can happen to be flat, a dozen cannot.
var structured = 0;
for (var attempt = 0; attempt < 12; attempt++)
var readings = new List<double>();
for (var step = 0; step < 8; step++)
{
using var simulation = WorldSimulation.Create(
summary with { Id = $"storm-{attempt:00000000}" }, catchUp: false);
var pressures = simulation.SnapshotWeatherField().Nodes
.Select(static node => node.PressureHpa)
.ToArray();
if (pressures.Max() - pressures.Min() > 2.0) structured++;
// Two game hours a step, which is a few real seconds of play at x1.
simulation.Tick(TimeSpan.FromSeconds(24));
readings.Add(simulation.SnapshotWeather().PressureHpa);
}
Assert.True(structured >= 8, $"Only {structured}/12 skies had any shape across the map.");
}
[Fact]
public void Neighbouring_field_nodes_stay_close_together()
{
using var simulation = WorldSimulation.Create(ReadySummary(), catchUp: false);
var field = simulation.SnapshotWeatherField();
// Gaussian bumps are smooth, so a coarse grid is safe to interpolate between on the client.
for (var row = 0; row < field.Size; row++)
{
for (var column = 1; column < field.Size; column++)
{
var left = field.Nodes[(row * field.Size) + column - 1];
var right = field.Nodes[(row * field.Size) + column];
Assert.True(
Math.Abs(left.TemperatureC - right.TemperatureC) < 6,
$"Nodes {column - 1} and {column} of row {row} jump by more than six degrees.");
}
}
Assert.True(
readings.Max() - readings.Min() > 0.5,
$"Pressure barely moved over sixteen game hours: {string.Join(", ", readings)}");
}
private static StoredWorldDto ReadySummary() => new()