diff --git a/SVSim.Database/Models/Viewer.cs b/SVSim.Database/Models/Viewer.cs index 79691dad..5704a189 100644 --- a/SVSim.Database/Models/Viewer.cs +++ b/SVSim.Database/Models/Viewer.cs @@ -35,7 +35,7 @@ public class Viewer : BaseEntity /// /// UTC instant of the most recent daily-login-bonus claim. Null = never claimed. - /// Compared against JST 02:00 day boundary via JstPeriod.DayKey to decide eligibility. + /// Compared against the daily reset boundary via IGameCalendarService.ResetReady. /// public DateTime? LastLoginBonusClaimedAt { get; set; } diff --git a/SVSim.Database/Models/ViewerEventCounter.cs b/SVSim.Database/Models/ViewerEventCounter.cs index cdbc803c..cf495936 100644 --- a/SVSim.Database/Models/ViewerEventCounter.cs +++ b/SVSim.Database/Models/ViewerEventCounter.cs @@ -3,7 +3,8 @@ namespace SVSim.Database.Models; /// /// Per-viewer "how many times has this happened" counter. Composite PK /// (ViewerId, EventKey, Period). Period strings: "all-time", "month:YYYY-MM", -/// "week:YYYY-W##", "day:YYYY-MM-DD" — all JST-anchored with 02:00 day-boundary. +/// "week:YYYY-W##", "day:YYYY-MM-DD" — all UTC, with the reset-window boundary +/// set by GameCalendarConfig.DailyResetUtcHour. /// Single source of truth for total_count / done_number on every wire shape. /// public class ViewerEventCounter diff --git a/SVSim.EmulatedEntrypoint/Services/JstPeriod.cs b/SVSim.EmulatedEntrypoint/Services/JstPeriod.cs deleted file mode 100644 index 572fadb0..00000000 --- a/SVSim.EmulatedEntrypoint/Services/JstPeriod.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.Globalization; - -namespace SVSim.EmulatedEntrypoint.Services; - -/// -/// JST-anchored period bucketing for ViewerEventCounters. Day/week/month boundaries are -/// 02:00 JST (matching the real-game daily reset). Pure functions, no dependencies. -/// -public static class JstPeriod -{ - private static readonly TimeSpan Jst = TimeSpan.FromHours(9); - private const string DayPrefix = "day:"; - private const string WeekPrefix = "week:"; - private const string MonthPrefix = "month:"; - public const string AllTime = "all-time"; - - /// - /// Converts the given instant to a JST-anchored "effective date" by: - /// 1. Shifting to JST (+09:00) - /// 2. Subtracting 2 hours so anything before 02:00 JST belongs to the previous day - /// - private static DateTime EffectiveJstDate(DateTimeOffset utcOrAny) - { - var jst = utcOrAny.ToOffset(Jst); - return jst.AddHours(-2).Date; - } - - public static string DayKey(DateTimeOffset when) - { - var d = EffectiveJstDate(when); - return $"{DayPrefix}{d:yyyy-MM-dd}"; - } - - public static string WeekKey(DateTimeOffset when) - { - var d = EffectiveJstDate(when); - var iso = ISOWeek.GetWeekOfYear(d); - var year = ISOWeek.GetYear(d); - return $"{WeekPrefix}{year:D4}-W{iso:D2}"; - } - - public static string MonthKey(DateTimeOffset when) - { - var d = EffectiveJstDate(when); - return $"{MonthPrefix}{d:yyyy-MM}"; - } - - /// Returns [day, week, month, all-time] keys for the given instant. - public static IReadOnlyList AllPeriods(DateTimeOffset when) => new[] - { - DayKey(when), WeekKey(when), MonthKey(when), AllTime, - }; -} diff --git a/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs b/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs index a7a81e5a..87c63d8e 100644 --- a/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs +++ b/SVSim.UnitTests/Models/GameConfigurationJsonbTests.cs @@ -34,7 +34,7 @@ public class GameConfigurationJsonbTests "Player", "DefaultGrants", "DefaultLoadout", "Challenge", "Rotation", "PackRates", "MyRotationSchedule", "Story", "ResourceConfig", "Freeplay", "ArenaTwoPick", "Matching", "CardMasterConfig", "ColosseumSeason", "ColosseumRounds", "LoginBonus", "Guild", - "SkipTutorial", "BattleXp", + "SkipTutorial", "BattleXp", "GameCalendar", })); var resources = JsonSerializer.Deserialize(byName["ResourceConfig"].ValueJson)!; diff --git a/SVSim.UnitTests/Services/JstPeriodTests.cs b/SVSim.UnitTests/Services/JstPeriodTests.cs deleted file mode 100644 index f2436733..00000000 --- a/SVSim.UnitTests/Services/JstPeriodTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace SVSim.UnitTests.Services; - -public class JstPeriodTests -{ - private static DateTimeOffset Jst(int y, int m, int d, int h, int min, int s) => - new DateTimeOffset(y, m, d, h, min, s, TimeSpan.FromHours(9)); - - [Test] - public void DayKey_uses_jst_with_02_00_anchor() - { - // 01:59:59 JST on May 27 belongs to the May 26 "day". - Assert.That(SVSim.EmulatedEntrypoint.Services.JstPeriod.DayKey(Jst(2026, 5, 27, 1, 59, 59)), - Is.EqualTo("day:2026-05-26")); - - // 02:00:00 JST on May 27 belongs to the May 27 "day". - Assert.That(SVSim.EmulatedEntrypoint.Services.JstPeriod.DayKey(Jst(2026, 5, 27, 2, 0, 0)), - Is.EqualTo("day:2026-05-27")); - } - - [Test] - public void WeekKey_is_iso8601_monday_anchored() - { - // 2026-05-25 (Monday) 02:00 JST → ISO week 2026-W22. - Assert.That(SVSim.EmulatedEntrypoint.Services.JstPeriod.WeekKey(Jst(2026, 5, 25, 2, 0, 0)), - Is.EqualTo("week:2026-W22")); - - // The previous Sunday's late evening still belongs to W21 (week reset is Monday 02:00 JST). - Assert.That(SVSim.EmulatedEntrypoint.Services.JstPeriod.WeekKey(Jst(2026, 5, 25, 1, 59, 59)), - Is.EqualTo("week:2026-W21")); - } - - [Test] - public void MonthKey_uses_jst_day_anchor_for_month_rollover() - { - // 01:59 JST on June 1 still belongs to May (day-boundary is 02:00 JST). - Assert.That(SVSim.EmulatedEntrypoint.Services.JstPeriod.MonthKey(Jst(2026, 6, 1, 1, 59, 0)), - Is.EqualTo("month:2026-05")); - - Assert.That(SVSim.EmulatedEntrypoint.Services.JstPeriod.MonthKey(Jst(2026, 6, 1, 2, 0, 0)), - Is.EqualTo("month:2026-06")); - } - - [Test] - public void AllPeriods_returns_day_week_month_all_time() - { - var t = Jst(2026, 5, 27, 12, 0, 0); - var periods = SVSim.EmulatedEntrypoint.Services.JstPeriod.AllPeriods(t); - Assert.That(periods, Is.EquivalentTo(new[] { - "day:2026-05-27", "week:2026-W22", "month:2026-05", "all-time", - })); - } -}