using System.Text.Json; using System.Text.Json.Nodes; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public sealed class NativeTargetSelectionRecoveryTests { private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-27T07:06:03+08:00"); private static JsonObject Raw()=>new() { ["sequence"]=7,["observedAtUnixMilliseconds"]=Now.ToUnixTimeMilliseconds(),["entityId"]="42",["referenceWorldAddress"]="1", ["localPlayerShipVerified"]=true,["inGame"]=true,["playerOverride"]=false,["rawFsdState"]=2, ["shipHealth"]=new JsonObject{["verified"]=true,["entityId"]="42",["sequence"]=7,["observedAtUnixMilliseconds"]=Now.ToUnixTimeMilliseconds(), ["hullFraction"]=1,["emergency"]=false,["damageObserved"]=false} }; private static JsonObject Fsd()=>new(){["fresh"]=true,["entityId"]="42",["ownedCharging"]=false,["state"]=2,["targetValid"]=true,["targetAddress"]="2"}; private static NativeTargetSelectionReadiness Inspect(JsonObject raw,JsonObject fsd)=>NativeTargetSelectionRecovery.Inspect(JsonSerializer.SerializeToElement(raw),JsonSerializer.SerializeToElement(fsd),2,1,Now); private static PlayerState Arrived=>new(){InGame=true,CommanderId="F1",CurrentShipId=8,GameMode="Solo",LoadGameAt=Now.AddHours(-1),CargoCount=1200, Navigation=new(){CurrentSystemAddress=1,TargetSystemAddress=2,CurrentSystemAt=Now.AddSeconds(-6),LastJumpAt=Now.AddSeconds(-6),Route=[new(1,"Here",new(0,0,0),"M"),new(2,"Next",new(1,0,0),"M")]}}; [Test]public void FreshIdleCanRequestButDoesNotItselfAcknowledgeADestination() { var check=Inspect(Raw(),Fsd());Check.That(check.CanRequest&&!check.Reject); var raw=Raw();var fsd=Fsd();raw["rawFsdState"]=1;fsd["state"]=1; Check.That(Inspect(raw,fsd).CanRequest);fsd["targetAddress"]="3";Check.That(!Inspect(raw,fsd).CanRequest); } [Test]public void OwnedChargingJumpingAndDifferentNativePhasesOnlyWaitWithoutRetargeting() { foreach(var (rawState,state,owned) in new[]{(3,3,false),(4,4,false),(2,2,true),(4,2,false),(2,4,false),(99,99,false)}) { var raw=Raw();var fsd=Fsd();raw["rawFsdState"]=rawState;fsd["state"]=state;fsd["ownedCharging"]=owned; var check=Inspect(raw,fsd);Check.That(!check.CanRequest&&!check.Reject); } } [Test]public void SourceIdentityWorldFreshnessAndHealthMustAllMatchTheSameRawFrame() { foreach(Action alter in new Action[] { (r,f)=>f["entityId"]="43",(r,f)=>r["referenceWorldAddress"]="3",(r,f)=>f["fresh"]=false, (r,f)=>r["observedAtUnixMilliseconds"]=Now.AddSeconds(-1).ToUnixTimeMilliseconds(), (r,f)=>r["shipHealth"]!["sequence"]=6,(r,f)=>r["shipHealth"]!["observedAtUnixMilliseconds"]=Now.AddMilliseconds(-1).ToUnixTimeMilliseconds(), (r,f)=>r["shipHealth"]!["entityId"]="43",(r,f)=>r["localPlayerShipVerified"]=false }) {var raw=Raw();var fsd=Fsd();alter(raw,fsd);Check.That(!Inspect(raw,fsd).CanRequest);} } [Test]public void ExplicitManualDamageHealthAndSessionEndRemainImmediateRejections() { foreach(Action alter in new Action[] {r=>r["playerOverride"]=true,r=>r["shipHealth"]!["emergency"]=true,r=>r["shipHealth"]!["damageObserved"]=true,r=>r["shipHealth"]!["hullFraction"]=0,r=>r["inGame"]=false}) {var raw=Raw();alter(raw);var result=Inspect(raw,Fsd());Check.That(result.Reject&&!result.CanRequest);} } [Test]public void MissingSafetyOrOwnershipFieldsNeverDefaultToFalse() { foreach(var field in new[]{"playerOverride","inGame","localPlayerShipVerified","shipHealth"}) {var raw=Raw();raw.Remove(field);Check.That(!Inspect(raw,Fsd()).CanRequest);} var fsd=Fsd();fsd.Remove("ownedCharging");Check.That(!Inspect(Raw(),fsd).CanRequest); foreach(var field in new[]{"emergency","damageObserved","sequence","observedAtUnixMilliseconds"}) {var raw=Raw();raw["shipHealth"]!.AsObject().Remove(field);Check.That(!Inspect(raw,Fsd()).CanRequest);} } [Test]public void RecentArrivalMustBelongToTheSameLoadedSessionAndCurrentSystemEvent() { var initial=Arrived;Check.That(NativeTargetSelectionRecovery.ContextFailure(initial,initial,2,Now,TimeSpan.Zero) is null); var zero=initial with{CurrentShipId=0};Check.That(NativeTargetSelectionRecovery.ContextFailure(zero,zero,2,Now,TimeSpan.Zero) is null); foreach(var altered in new[]{initial with{LoadGameAt=Now},initial with{Navigation=initial.Navigation with{CurrentSystemAt=Now.AddSeconds(-5)}}, initial with{Navigation=initial.Navigation with{JumpInProgress=true}},initial with{CommanderId=""}}) Check.That(NativeTargetSelectionRecovery.ContextFailure(altered,altered,2,Now,TimeSpan.Zero) is not null); } [Test]public void TenSecondDeadlineAndChangesToCargoRouteShipSessionOrSystemAreTerminalContextFailures() { var initial=Arrived;Check.That(NativeTargetSelectionRecovery.ContextFailure(initial,initial,2,Now,TimeSpan.FromMilliseconds(9999)) is null); Check.Equal("arrival-target-selection-deadline",NativeTargetSelectionRecovery.ContextFailure(initial,initial,2,Now,TimeSpan.FromSeconds(10))!); foreach(var current in new[]{initial with{CurrentShipId=9},initial with{CommanderId="F2"},initial with{CargoCount=1199},initial with{GameMode="Open"}, initial with{LoadGameAt=Now},initial with{Docked=true},initial with{Navigation=initial.Navigation with{CurrentSystemAddress=3}}, initial with{Navigation=initial.Navigation with{TargetSystemAddress=3}}, initial with{Navigation=initial.Navigation with{Route=[new(3,"Other",new(1,0,0),"M")]}}}) Check.That(NativeTargetSelectionRecovery.ContextFailure(initial,current,2,Now,TimeSpan.FromSeconds(1)) is not null); } [Test]public void FailureDiagnosticsAreBoundedAndKeepBothNativePhasesInsteadOfCopyingFullTelemetry() { var raw=Raw();raw["unrelatedLargePayload"]=new string('x',100000);raw["rawFsdState"]=4; var result=Inspect(raw,Fsd());Check.That(!result.CanRequest&&result.Diagnostics.GetRawText().Length<2048); Check.Equal(4L,result.Diagnostics.GetProperty("rawState").GetInt64());Check.Equal(2L,result.Diagnostics.GetProperty("fsdState").GetInt64()); Check.Equal(0,result.Diagnostics.GetProperty("mutationsSent").GetInt32()); } }