namespace Theriapolis.Core.Time; public enum Season : byte { Spring, Summer, Autumn, Winter } /// /// Single in-game time counter. Measured in whole seconds so it serializes /// trivially and stays deterministic — no floating-point drift over a long /// playthrough. /// /// Phase 4 callers advance the clock from world-map travel and tactical /// stepping. Phase 8 weather/seasons reads from it. /// public sealed class WorldClock { /// In-game seconds since world creation. Game time, not real time. public long InGameSeconds { get; private set; } // Calendar constants. A 96-day year (24 days × 4 seasons) keeps the math // tight; a real-world year would mean each season is a 90-hour playthrough. public const int SecondsPerMinute = 60; public const int SecondsPerHour = 3600; public const int SecondsPerDay = SecondsPerHour * 24; public const int DaysPerSeason = 24; public const int DaysPerYear = DaysPerSeason * 4; public int Day => (int)(InGameSeconds / SecondsPerDay); public int Hour => (int)((InGameSeconds % SecondsPerDay) / SecondsPerHour); public int Minute => (int)((InGameSeconds % SecondsPerHour) / SecondsPerMinute); public int Year => Day / DaysPerYear; public Season Season => (Season)((Day / DaysPerSeason) % 4); public void Advance(long seconds) { if (seconds < 0) throw new ArgumentOutOfRangeException(nameof(seconds)); InGameSeconds += seconds; } public WorldClockState CaptureState() => new() { InGameSeconds = InGameSeconds }; public void RestoreState(WorldClockState s) => InGameSeconds = s.InGameSeconds; /// Pretty-print like "Y0 Spring D5 14:23". public string Format() => $"Y{Year} {Season} D{Day % DaysPerSeason} {Hour:D2}:{Minute:D2}"; } public sealed class WorldClockState { public long InGameSeconds; }