Add Climate and day/night ambient lighting
CI / build-test (push) Successful in 1m11s

Climate (Core) layers a continuous seasonal-plus-daily temperature and the
current season over the Calendar, registered via context.UseClimate. A new
Lighting module (Graphics) adds DayNight: a daylight factor and ambient
color from the calendar's time of day, pushed into the renderer's new
AmbientLight (multiplied into world-space sprites only, so the scene
darkens at night while screen-space overlays stay readable) and exposed as
a sampleable SampleAt for the simulation. Both deterministic and GPU-free,
covered by tests; docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-12 16:45:49 +03:00
co-authored by Claude Opus 4.8
parent f39ee9e787
commit a684c23cd9
8 changed files with 413 additions and 5 deletions
@@ -0,0 +1,84 @@
using MrGameEng.Core;
using Xunit;
namespace MrGameEng.Core.Tests;
public class ClimateTests
{
private static (GameClock Clock, Climate Climate) Make(ClimateSettings settings)
{
var clock = new GameClock();
var calendar = new Calendar(clock, secondsPerDay: 10f);
return (clock, new Climate(calendar, settings));
}
private static ClimateSettings Seasonal =>
new()
{
DaysPerYear = 60,
MeanTemperature = 10f,
SeasonalAmplitude = 20f,
DailyAmplitude = 0f,
WarmestDay = 15,
};
[Fact]
public void Constructor_NonPositiveYear_Throws()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
new Climate(new Calendar(new GameClock(), 10f), Seasonal with { DaysPerYear = 0 })
);
}
[Fact]
public void Temperature_PeaksOnWarmestDay_AndBottomsHalfYearLater()
{
var (clock, climate) = Make(Seasonal);
clock.Advance(15 * 10f); // day 15 = warmest
Assert.Equal(30f, climate.Temperature, 2); // mean 10 + amplitude 20
clock.Advance(30 * 10f); // +30 days = half a 60-day year later
Assert.Equal(-10f, climate.Temperature, 2); // mean 10 - amplitude 20
}
[Fact]
public void DailySwing_WarmerAtNoonThanMidnight()
{
var settings = Seasonal with { SeasonalAmplitude = 0f, DailyAmplitude = 6f };
var (clock, climate) = Make(settings);
clock.Advance(5f); // day 0, noon (DayProgress 0.5)
Assert.Equal(16f, climate.Temperature, 2); // mean 10 + daily 6
clock.Advance(5f); // day 1, midnight (DayProgress 0)
Assert.Equal(4f, climate.Temperature, 2); // mean 10 - daily 6
}
[Fact]
public void Season_FollowsTheQuarterOfTheYear()
{
var (clock, climate) = Make(Seasonal); // 60-day year → 15 days per season
Assert.Equal(Season.Spring, climate.Season);
clock.Advance(20 * 10f);
Assert.Equal(Season.Summer, climate.Season);
clock.Advance(15 * 10f);
Assert.Equal(Season.Autumn, climate.Season);
clock.Advance(15 * 10f);
Assert.Equal(Season.Winter, climate.Season);
}
[Fact]
public void YearAndDayOfYear_RollOver()
{
var (clock, climate) = Make(Seasonal);
Assert.Equal(1, climate.Year);
Assert.Equal(0, climate.DayOfYear);
clock.Advance(60 * 10f); // exactly one year
Assert.Equal(2, climate.Year);
Assert.Equal(0, climate.DayOfYear);
}
}