using System.Text.Json; using XFEEDToolBox.Core.Data; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public sealed class GameJournalClockTests { private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-26T05:49:28.536Z"); private static PlayerState Ahead=>new(){InGame=true,Status=new(16777232,0,32,1,0,null,null,null,null,[]), LoadGameAt=Now.AddMinutes(-2),StatusAt=Now.AddSeconds(6.464),StatusWrittenAt=Now,StatusReadAt=Now.AddMilliseconds(10)}; [Test]public void AVerifiedLocalWriteExplainsTheRealSixSecondGameClockLead() { var now=Now.AddMilliseconds(20);Check.That(TravelPreparation.StatusConfirmed(Ahead,now)); Check.Equal(TimeSpan.FromSeconds(6.464),GameJournalClock.Offset(Ahead,now)!.Value); Check.That(!TravelPreparation.StatusConfirmed(Ahead with{StatusWrittenAt=null},now)); Check.That(!TravelPreparation.StatusConfirmed(Ahead with{StatusAt=Now.AddSeconds(31)},now)); Check.That(!TravelPreparation.StatusConfirmed(Ahead with{StatusReadAt=Now.AddSeconds(-3)},now)); Check.That(!TravelPreparation.StatusConfirmed(Ahead with{StatusWrittenAt=Now.AddSeconds(1)},now)); Check.That(!TravelPreparation.StatusConfirmed(Ahead with{LoadGameAt=Now.AddSeconds(7)},now)); } [Test]public void MatchingFinalJumpClearUsesTheJournalClockWithoutWeakeningWallClockFreshness() { NavWaypoint Point(long id)=>new(id,"System "+id,new(id,0,0),"K"); var player=Ahead with{CommanderId="F1",Navigation=new(){CurrentSystemAddress=1,Route=[Point(1),Point(2)],RouteAt=Now.AddMinutes(-1)}}; var mirror=new GameRouteMirror(()=>Now.AddMilliseconds(20));mirror.Observe(player); var jumping=player with{Navigation=player.Navigation with{Route=[],RouteAt=Now.AddSeconds(6.464),JumpStartedAt=Now.AddSeconds(3), JumpInProgress=true,JumpTargetSystemAddress=2,JumpTargetSystem="System 2"}}; Check.That(!mirror.Observe(jumping).Cleared); var arrived=jumping with{Navigation=jumping.Navigation with{CurrentSystemAddress=2,JumpInProgress=false,LastJumpAt=Now.AddSeconds(7)}}; Check.That(mirror.Observe(arrived).Arrived); } [Test]public void ArrivalTailWindowsUseOnlyTheVerifiedJournalClockCorrection() { var player=Ahead with{Navigation=new(){LastJumpAt=Now.AddSeconds(6.464)}};var now=Now.AddMilliseconds(20); Check.That(GameJournalClock.IsRecentArrival(player,now)); Check.That(!GameJournalClock.IsRecentArrival(player with{StatusWrittenAt=null},now)); Check.That(!GameJournalClock.IsRecentArrival(player with{Navigation=player.Navigation with{LastJumpAt=Now.AddSeconds(-25)}},now)); Check.That(!GameJournalClock.IsRecentArrival(player with{Navigation=player.Navigation with{LastJumpAt=Now.AddMinutes(1)}},now)); } [Test]public async Task StableFileMetadataIsRetainedAcrossUnchangedReadsWithoutInventingStatusEvents() { using var directory=new TemporaryDirectory();var path=directory.File("Status.json");var written=DateTimeOffset.UtcNow; await File.WriteAllTextAsync(path,JsonSerializer.Serialize(new{timestamp=written.AddSeconds(6.5),@event="Status",Flags=16777232})); File.SetLastWriteTimeUtc(path,written.UtcDateTime);await using var monitor=new JournalMonitor(directory.Path);await monitor.StartAsync(); var original=monitor.State.StatusWrittenAt;Check.Equal(written,original!.Value);Check.That(TravelPreparation.StatusConfirmed(monitor.State,DateTimeOffset.UtcNow)); await Task.Delay(850);Check.Equal(original,monitor.State.StatusWrittenAt);Check.That(TravelPreparation.StatusConfirmed(monitor.State,DateTimeOffset.UtcNow)); } }