Files
the-living-world/tests/TheLivingWorld.Tests/WorldSimulationTests.cs
T

106 lines
3.4 KiB
C#

using TheLivingWorld.Api.Simulation;
using TheLivingWorld.Core.Contracts;
using TheLivingWorld.Core.Simulation;
namespace TheLivingWorld.Tests;
public sealed class WorldSimulationTests
{
[Fact]
public void Create_starts_at_default_morning_and_ticks_at_x1()
{
using var simulation = WorldSimulation.Create(ReadySummary(), catchUp: false);
var before = simulation.SnapshotClock();
Assert.Equal(GameTime.DefaultStart, before.GameTime);
Assert.Equal(1, before.TimeScale);
Assert.False(before.Paused);
simulation.Tick(TimeSpan.FromSeconds(2));
var after = simulation.SnapshotClock();
Assert.Equal(GameTime.DefaultStart.AddMinutes(10), after.GameTime);
Assert.True(simulation.IsDirty);
}
[Fact]
public void Tick_does_not_advance_while_paused()
{
var summary = ReadySummary() with
{
Clock = WorldSimulation.DefaultClock() with { Paused = true },
};
using var simulation = WorldSimulation.Create(summary, catchUp: false);
simulation.Tick(TimeSpan.FromSeconds(5));
Assert.Equal(GameTime.DefaultStart, simulation.SnapshotClock().GameTime);
Assert.False(simulation.IsDirty);
}
[Fact]
public void Update_bakes_elapsed_time_before_changing_scale()
{
using var simulation = WorldSimulation.Create(ReadySummary(), catchUp: false);
simulation.Tick(TimeSpan.FromSeconds(1));
var clock = simulation.Update(new UpdateClockRequest { TimeScale = 2 });
Assert.Equal(2, clock.TimeScale);
// Tick advanced 5 game minutes; Update may bake a few extra wall-clock milliseconds.
Assert.InRange(
clock.GameTime,
GameTime.DefaultStart.AddMinutes(5),
GameTime.DefaultStart.AddMinutes(5).AddSeconds(30));
Assert.True(simulation.IsDirty);
}
[Fact]
public void Update_rejects_invalid_time_scale()
{
using var simulation = WorldSimulation.Create(ReadySummary(), catchUp: false);
Assert.Throws<ArgumentOutOfRangeException>(() =>
simulation.Update(new UpdateClockRequest { TimeScale = 5 }));
}
[Fact]
public void Catch_up_advances_from_last_ticked_at()
{
var lastTickedAt = DateTimeOffset.UtcNow - TimeSpan.FromSeconds(3);
var summary = ReadySummary() with { LastTickedAt = lastTickedAt };
using var simulation = WorldSimulation.Create(summary, catchUp: true);
// ~3 real seconds at x1 → ~15 game minutes (allow a little wall-clock drift).
var actual = simulation.SnapshotClock().GameTime;
Assert.InRange(
actual,
GameTime.DefaultStart.AddMinutes(14),
GameTime.DefaultStart.AddMinutes(17));
}
[Fact]
public void OverlayForApi_strips_last_ticked_at()
{
using var simulation = WorldSimulation.Create(ReadySummary(), catchUp: false);
var overlaid = simulation.OverlayForApi(ReadySummary());
Assert.NotNull(overlaid.Clock);
Assert.Null(overlaid.LastTickedAt);
}
private static WorldSummaryDto ReadySummary() => new()
{
Id = "town-aaaaaaaa",
Name = "Town",
Latitude = 31.9,
Longitude = -100.5,
SizeMeters = 10_000,
Status = WorldStatus.Ready,
CreatedAt = DateTimeOffset.UtcNow,
Clock = WorldSimulation.DefaultClock(),
LastTickedAt = DateTimeOffset.UtcNow,
};
}