using System.Globalization;
using System.Text.Json;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Navigation;
public sealed record NativeTargetSelectionReadiness(bool CanRequest,bool Reject,string Reason,JsonElement Diagnostics);
/// <summary>Read-only arrival-tail qualification; the native selector remains authoritative.</summary>
public static class NativeTargetSelectionRecovery
{
public static readonly TimeSpan MaximumWait=TimeSpan.FromSeconds(10);
private static bool Flag(JsonElement value,string key,out bool output)
{output=false;if(value.ValueKind!=JsonValueKind.Object||!value.TryGetProperty(key,out var field)||field.ValueKind is not(JsonValueKind.True or JsonValueKind.False))return false;output=field.GetBoolean();return true;}
private static bool True(JsonElement value,string key)=>Flag(value,key,out var result)&&result;
private static bool False(JsonElement value,string key)=>Flag(value,key,out var result)&&!result;
private static long Integer(JsonElement value,string key)=>value.ValueKind==JsonValueKind.Object&&value.TryGetProperty(key,out var field)&&field.ValueKind==JsonValueKind.Number&&field.TryGetInt64(out var number)?number:-1;
private static ulong Address(JsonElement value,string key)=>value.ValueKind==JsonValueKind.Object&&value.TryGetProperty(key,out var field)
&&field.ValueKind==JsonValueKind.String&&ulong.TryParse(field.GetString(),NumberStyles.None,CultureInfo.InvariantCulture,out var number)?number:0;
public static string? ContextFailure(PlayerState initial,PlayerState current,long target,DateTimeOffset now,TimeSpan elapsed)
{
if(elapsed<TimeSpan.Zero||elapsed>=MaximumWait)return "arrival-target-selection-deadline";
if(!initial.InGame||initial.CommanderId.Length==0||initial.CurrentShipId is not >=0||initial.LoadGameAt is null||initial.Navigation.CurrentSystemAddress is not >0
||initial.Navigation.LastJumpAt is not {} arrived||arrived<initial.LoadGameAt.Value||initial.Navigation.CurrentSystemAt!=arrived||initial.Navigation.JumpInProgress
||!GameJournalClock.IsRecentArrival(initial,now))return "arrival-identity-or-window-unverified";
if(!current.InGame||current.Docked||current.CommanderId!=initial.CommanderId||current.CurrentShipId!=initial.CurrentShipId
||current.LoadGameAt!=initial.LoadGameAt||current.GameMode!=initial.GameMode||current.CargoCount!=initial.CargoCount
||current.Navigation.CurrentSystemAddress!=initial.Navigation.CurrentSystemAddress||current.Navigation.LastJumpAt!=initial.Navigation.LastJumpAt
||current.Navigation.CurrentSystemAt!=initial.Navigation.CurrentSystemAt||current.Navigation.JumpInProgress
||!GameJournalClock.IsRecentArrival(current,now))return "arrival-flight-identity-changed";
if(NavigationSafety.UnhealthyHull(current))return "arrival-ship-health-failed";
if(NavigationSafety.HasExternalThreat(current))return "arrival-external-threat";
var planned=initial.Navigation.Route.Select(point=>point.SystemAddress).ToArray();
if(target<=0||!planned.Contains(target)||!NavigationRouteProgress.SameItinerary(planned,current.Navigation)
||current.Navigation.TargetSystemAddress is >0 and var selected&&selected!=target)return "arrival-route-or-selected-target-changed";
return null;
}
public static NativeTargetSelectionReadiness Inspect(JsonElement raw,JsonElement fsd,long target,long expectedSystem,DateTimeOffset now)
{
var entity=Address(raw,"entityId");var fsdEntity=Address(fsd,"entityId");var at=Integer(raw,"observedAtUnixMilliseconds");
var rawState=Integer(raw,"rawFsdState");var state=Integer(fsd,"state");
var health=raw.ValueKind==JsonValueKind.Object&&raw.TryGetProperty("shipHealth",out var h)?h:default;
var hull=health.ValueKind==JsonValueKind.Object&&health.TryGetProperty("hullFraction",out var hullNode)&&hullNode.ValueKind==JsonValueKind.Number?hullNode.GetDouble():double.NaN;
var sequence=Integer(raw,"sequence");var healthSequence=Integer(health,"sequence");var healthAt=Integer(health,"observedAtUnixMilliseconds");
var stamp=now.ToUnixTimeMilliseconds();var age=at>0?stamp-at:long.MaxValue;
var world=Address(raw,"referenceWorldAddress");
var identity=True(raw,"localPlayerShipVerified")&&entity!=0&&entity==fsdEntity&&entity==Address(health,"entityId")
&&world!=0&&expectedSystem>0&&NativeFrameProjector.SystemAddressFromBody(world)==expectedSystem;
var healthVerified=True(health,"verified")&&sequence>0&&healthSequence==sequence&&healthAt==at&&double.IsFinite(hull)&&hull is >0 and <=1&&False(health,"emergency")&&False(health,"damageObserved");
var fresh=age is >=-100 and <=300&&True(fsd,"fresh");
var locked=True(fsd,"targetValid")&&Address(fsd,"targetAddress")==unchecked((ulong)target);
var nativeIdle=rawState==2&&state==2;
var matchingExisting=locked&&(rawState is 0 or 1 or 2 or 5)&&(state is 0 or 1 or 2 or 5);
var negativeSafety=True(raw,"playerOverride")||True(health,"emergency")||True(health,"damageObserved")||double.IsFinite(hull)&&hull<=0;
var rejected=negativeSafety||False(raw,"inGame");
var canRequest=!rejected&&target>0&&fresh&&identity&&healthVerified&&True(raw,"inGame")&&False(raw,"playerOverride")
&&False(fsd,"ownedCharging")&&(nativeIdle||matchingExisting);
var reason=negativeSafety?"native-manual-or-health-rejection":False(raw,"inGame")?"native-session-ended":canRequest?"native-idle-or-exact-existing-target-qualified"
:!fresh?"awaiting-fresh-arrival-snapshots":!identity?"awaiting-same-main-ship-identity":!healthVerified?"awaiting-verified-healthy-arrival"
:!False(fsd,"ownedCharging")||rawState is 3 or 4||state is 3 or 4?"awaiting-original-fsd-transition-without-input":"awaiting-native-target-selection-eligibility";
var diagnostics=JsonSerializer.SerializeToElement(new{reason,targetSystemAddress=target,entityId=entity.ToString(CultureInfo.InvariantCulture),fsdEntityId=fsdEntity.ToString(CultureInfo.InvariantCulture),
referenceWorldAddress=world.ToString(CultureInfo.InvariantCulture),expectedSystemAddress=expectedSystem,observedAtUnixMilliseconds=at,ageMilliseconds=at>0?(long?)age:null,rawState,fsdState=state,fsdFresh=True(fsd,"fresh"),ownedCharging=Flag(fsd,"ownedCharging",out var owned)?(bool?)owned:null,
targetValid=True(fsd,"targetValid"),targetAddress=Address(fsd,"targetAddress").ToString(CultureInfo.InvariantCulture),rawInGame=Flag(raw,"inGame",out var inGame)?(bool?)inGame:null,
playerOverride=Flag(raw,"playerOverride",out var manual)?(bool?)manual:null,identityVerified=identity,healthVerified,hullFraction=double.IsFinite(hull)?(double?)hull:null,
sequence,healthSequence,healthObservedAtUnixMilliseconds=healthAt,
emergency=Flag(health,"emergency",out var emergency)?(bool?)emergency:null,damageObserved=Flag(health,"damageObserved",out var damage)?(bool?)damage:null,
nativeReady=canRequest,mutationsSent=0});
return new(canRequest,rejected,reason,diagnostics);
}
}
using System.Globalization;
using System.Text.Json;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Navigation;
public sealed record NativeTargetSelectionReadiness(bool CanRequest,bool Reject,string Reason,JsonElement Diagnostics);
/// <summary>Read-only arrival-tail qualification; the native selector remains authoritative.</summary>
public static class NativeTargetSelectionRecovery
{
public static readonly TimeSpan MaximumWait=TimeSpan.FromSeconds(10);
private static bool Flag(JsonElement value,string key,out bool output)
{output=false;if(value.ValueKind!=JsonValueKind.Object||!value.TryGetProperty(key,out var field)||field.ValueKind is not(JsonValueKind.True or JsonValueKind.False))return false;output=field.GetBoolean();return true;}
private static bool True(JsonElement value,string key)=>Flag(value,key,out var result)&&result;
private static bool False(JsonElement value,string key)=>Flag(value,key,out var result)&&!result;
private static long Integer(JsonElement value,string key)=>value.ValueKind==JsonValueKind.Object&&value.TryGetProperty(key,out var field)&&field.ValueKind==JsonValueKind.Number&&field.TryGetInt64(out var number)?number:-1;
private static ulong Address(JsonElement value,string key)=>value.ValueKind==JsonValueKind.Object&&value.TryGetProperty(key,out var field)
&&field.ValueKind==JsonValueKind.String&&ulong.TryParse(field.GetString(),NumberStyles.None,CultureInfo.InvariantCulture,out var number)?number:0;
public static string? ContextFailure(PlayerState initial,PlayerState current,long target,DateTimeOffset now,TimeSpan elapsed)
{
if(elapsed<TimeSpan.Zero||elapsed>=MaximumWait)return "arrival-target-selection-deadline";
if(!initial.InGame||initial.CommanderId.Length==0||initial.CurrentShipId is not >=0||initial.LoadGameAt is null||initial.Navigation.CurrentSystemAddress is not >0
||initial.Navigation.LastJumpAt is not {} arrived||arrived<initial.LoadGameAt.Value||initial.Navigation.CurrentSystemAt!=arrived||initial.Navigation.JumpInProgress
||!GameJournalClock.IsRecentArrival(initial,now))return "arrival-identity-or-window-unverified";
if(!current.InGame||current.Docked||current.CommanderId!=initial.CommanderId||current.CurrentShipId!=initial.CurrentShipId
||current.LoadGameAt!=initial.LoadGameAt||current.GameMode!=initial.GameMode||current.CargoCount!=initial.CargoCount
||current.Navigation.CurrentSystemAddress!=initial.Navigation.CurrentSystemAddress||current.Navigation.LastJumpAt!=initial.Navigation.LastJumpAt
||current.Navigation.CurrentSystemAt!=initial.Navigation.CurrentSystemAt||current.Navigation.JumpInProgress
||!GameJournalClock.IsRecentArrival(current,now))return "arrival-flight-identity-changed";
if(NavigationSafety.UnhealthyHull(current))return "arrival-ship-health-failed";
if(NavigationSafety.HasExternalThreat(current))return "arrival-external-threat";
var planned=initial.Navigation.Route.Select(point=>point.SystemAddress).ToArray();
if(target<=0||!planned.Contains(target)||!NavigationRouteProgress.SameItinerary(planned,current.Navigation)
||current.Navigation.TargetSystemAddress is >0 and var selected&&selected!=target)return "arrival-route-or-selected-target-changed";
return null;
}
public static NativeTargetSelectionReadiness Inspect(JsonElement raw,JsonElement fsd,long target,long expectedSystem,DateTimeOffset now)
{
var entity=Address(raw,"entityId");var fsdEntity=Address(fsd,"entityId");var at=Integer(raw,"observedAtUnixMilliseconds");
var rawState=Integer(raw,"rawFsdState");var state=Integer(fsd,"state");
var health=raw.ValueKind==JsonValueKind.Object&&raw.TryGetProperty("shipHealth",out var h)?h:default;
var hull=health.ValueKind==JsonValueKind.Object&&health.TryGetProperty("hullFraction",out var hullNode)&&hullNode.ValueKind==JsonValueKind.Number?hullNode.GetDouble():double.NaN;
var sequence=Integer(raw,"sequence");var healthSequence=Integer(health,"sequence");var healthAt=Integer(health,"observedAtUnixMilliseconds");
var stamp=now.ToUnixTimeMilliseconds();var age=at>0?stamp-at:long.MaxValue;
var world=Address(raw,"referenceWorldAddress");
var identity=True(raw,"localPlayerShipVerified")&&entity!=0&&entity==fsdEntity&&entity==Address(health,"entityId")
&&world!=0&&expectedSystem>0&&NativeFrameProjector.SystemAddressFromBody(world)==expectedSystem;
var healthVerified=True(health,"verified")&&sequence>0&&healthSequence==sequence&&healthAt==at&&double.IsFinite(hull)&&hull is >0 and <=1&&False(health,"emergency")&&False(health,"damageObserved");
var fresh=age is >=-100 and <=300&&True(fsd,"fresh");
var locked=True(fsd,"targetValid")&&Address(fsd,"targetAddress")==unchecked((ulong)target);
var nativeIdle=rawState==2&&state==2;
var matchingExisting=locked&&(rawState is 0 or 1 or 2 or 5)&&(state is 0 or 1 or 2 or 5);
var negativeSafety=True(raw,"playerOverride")||True(health,"emergency")||True(health,"damageObserved")||double.IsFinite(hull)&&hull<=0;
var rejected=negativeSafety||False(raw,"inGame");
var canRequest=!rejected&&target>0&&fresh&&identity&&healthVerified&&True(raw,"inGame")&&False(raw,"playerOverride")
&&False(fsd,"ownedCharging")&&(nativeIdle||matchingExisting);
var reason=negativeSafety?"native-manual-or-health-rejection":False(raw,"inGame")?"native-session-ended":canRequest?"native-idle-or-exact-existing-target-qualified"
:!fresh?"awaiting-fresh-arrival-snapshots":!identity?"awaiting-same-main-ship-identity":!healthVerified?"awaiting-verified-healthy-arrival"
:!False(fsd,"ownedCharging")||rawState is 3 or 4||state is 3 or 4?"awaiting-original-fsd-transition-without-input":"awaiting-native-target-selection-eligibility";
var diagnostics=JsonSerializer.SerializeToElement(new{reason,targetSystemAddress=target,entityId=entity.ToString(CultureInfo.InvariantCulture),fsdEntityId=fsdEntity.ToString(CultureInfo.InvariantCulture),
referenceWorldAddress=world.ToString(CultureInfo.InvariantCulture),expectedSystemAddress=expectedSystem,observedAtUnixMilliseconds=at,ageMilliseconds=at>0?(long?)age:null,rawState,fsdState=state,fsdFresh=True(fsd,"fresh"),ownedCharging=Flag(fsd,"ownedCharging",out var owned)?(bool?)owned:null,
targetValid=True(fsd,"targetValid"),targetAddress=Address(fsd,"targetAddress").ToString(CultureInfo.InvariantCulture),rawInGame=Flag(raw,"inGame",out var inGame)?(bool?)inGame:null,
playerOverride=Flag(raw,"playerOverride",out var manual)?(bool?)manual:null,identityVerified=identity,healthVerified,hullFraction=double.IsFinite(hull)?(double?)hull:null,
sequence,healthSequence,healthObservedAtUnixMilliseconds=healthAt,
emergency=Flag(health,"emergency",out var emergency)?(bool?)emergency:null,damageObserved=Flag(health,"damageObserved",out var damage)?(bool?)damage:null,
nativeReady=canRequest,mutationsSent=0});
return new(canRequest,rejected,reason,diagnostics);
}
}