Add Calendar: in-game days layered over GameClock
CI / build-test (push) Successful in 1m11s

Introduce a Calendar in Core that turns scaled clock time into whole
in-game days plus a fraction-of-day, with a configurable SecondsPerDay.
Pausing or changing TimeScale slows or stops it automatically. Pure
read-side and deterministic, registered via context.UseCalendar(...),
mirroring GameSpeed. Covered by xUnit tests; docs updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-12 09:46:53 +03:00
co-authored by Claude Opus 4.8
parent 53659450a6
commit f791ee6c95
4 changed files with 142 additions and 2 deletions
+70
View File
@@ -0,0 +1,70 @@
namespace MrGameEng.Core;
/// <summary>
/// In-game calendar layered over <see cref="GameClock"/>: turns scaled elapsed time into whole
/// days and a fraction-of-day (time of day). One in-game day spans <see cref="SecondsPerDay"/>
/// seconds of scaled clock time, so pausing or changing <see cref="GameClock.TimeScale"/> slows
/// or stops the calendar automatically. Pure read-side, GPU-free and deterministic; registered as
/// a service via <see cref="CalendarEngineExtensions.UseCalendar"/>.
/// </summary>
public sealed class Calendar
{
private readonly GameClock _clock;
private float _secondsPerDay;
/// <summary>
/// Creates a calendar reading <paramref name="clock"/>; one day spans
/// <paramref name="secondsPerDay"/> seconds of scaled time (must be positive).
/// </summary>
public Calendar(GameClock clock, float secondsPerDay)
{
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
SecondsPerDay = secondsPerDay;
}
/// <summary>Scaled seconds per in-game day. Must be positive; raising it slows the calendar.</summary>
public float SecondsPerDay
{
get => _secondsPerDay;
set =>
_secondsPerDay =
value > 0f
? value
: throw new ArgumentOutOfRangeException(
nameof(value),
"Seconds per day must be positive."
);
}
/// <summary>Total elapsed days as a continuous value (e.g. 3.5 = midday of day 4).</summary>
public double TotalDays => _clock.TotalTime / _secondsPerDay;
/// <summary>The current day number, counting from 1.</summary>
public int Day => (int)TotalDays + 1;
/// <summary>Progress through the current day in <c>[0, 1)</c> — 0 at dawn, ~0.5 at midday.</summary>
public float DayProgress
{
get
{
var days = TotalDays;
return (float)(days - Math.Floor(days));
}
}
}
/// <summary>Wires the in-game calendar into the engine.</summary>
public static class CalendarEngineExtensions
{
/// <summary>
/// Creates a <see cref="Calendar"/> bound to the context's clock and registers it as a
/// service. Call once per world. <paramref name="secondsPerDay"/> is the scaled-time length
/// of one in-game day (must be positive).
/// </summary>
public static Calendar UseCalendar(this EngineContext context, float secondsPerDay)
{
var calendar = new Calendar(context.Clock, secondsPerDay);
context.Services.Add(calendar);
return calendar;
}
}