using System.Globalization;
using System.Text.Json;
namespace XFEEDToolBox.Core.Navigation;
public sealed record ScoTrialContinuationContext(int ProcessId,string GameSha256,string LoadoutFingerprint,ulong Entity,ulong World,ulong ClockEpoch,
int TargetKey,ulong ConsumedReceiptSteps,ulong OriginalTrialDeadline,int CompletedFallbacks);
/// <summary>Read-only QA decision. It never arms a trial, changes a domain or extends a deadline.</summary>
public static class ScoTrialFallbackContinuation
{
public static bool CanContinue(JsonElement status,ScoTrialContinuationContext expected,DateTimeOffset now,out string reason)
{
static JsonElement O(JsonElement row,string key)=>row.ValueKind==JsonValueKind.Object&&row.TryGetProperty(key,out var item)?item:default;
static bool T(JsonElement row,string key)=>O(row,key).ValueKind==JsonValueKind.True;
static bool F(JsonElement row,string key)=>O(row,key).ValueKind==JsonValueKind.False;
static string S(JsonElement row,string key)=>O(row,key) is var item&&item.ValueKind==JsonValueKind.String?item.GetString()??"":"";
static double N(JsonElement row,string key)=>O(row,key) is var item&&item.ValueKind==JsonValueKind.Number&&item.TryGetDouble(out var value)?value:double.NaN;
static ulong U(JsonElement row,string key)=>O(row,key) is var item&&item.ValueKind==JsonValueKind.Number&&item.TryGetUInt64(out var value)?value:0;
static ulong I(JsonElement row,string key)=>ulong.TryParse(S(row,key),NumberStyles.None,CultureInfo.InvariantCulture,out var value)?value:0;
reason="fallback-already-consumed-or-invalid-context";
var at=now.ToUnixTimeMilliseconds();
if(expected.CompletedFallbacks!=0||expected.ProcessId<=0||expected.GameSha256.Length!=64||expected.LoadoutFingerprint.Length!=64
||expected.Entity==0||expected.World==0||expected.ClockEpoch==0||expected.TargetKey<0
||expected.OriginalTrialDeadline<=(ulong)Math.Max(0,at)||expected.OriginalTrialDeadline-(ulong)Math.Max(0,at)>600000)return false;
reason="foreign-service-or-loadout";
var policy=O(status,"scoExitEstimate");var sample=O(status,"sample");var receipt=O(policy,"lastExitReceipt");var stop=O(status,"lastOriginalStop");var gate=O(stop,"gate");
if(!T(status,"enabled")||N(status,"processId")!=expected.ProcessId||S(status,"service")!=CruiseControlProtocol.Service||S(status,"gameSha256")!=expected.GameSha256
||expected.LoadoutFingerprint.Length!=64||S(policy,"trustedLoadoutFingerprint")!=expected.LoadoutFingerprint)return false;
reason="no-remaining-original-trial-authorization";
if(S(policy,"mode")!="bounded-trial"||U(policy,"trialExpiresAtUnixMilliseconds")!=expected.OriginalTrialDeadline
||N(policy,"maximumTrialBurstsRemaining")!=1||!F(policy,"trialBurstActive")||!T(policy,"proofAvailable")
||O(policy,"lastPolicyRevocationFailure").ValueKind!=JsonValueKind.Null)return false;
reason="current-binding-or-source-unavailable";
var sampleAt=N(sample,"observedAtUnixMilliseconds");
if(!T(status,"sampleReady")||!F(status,"owned")||!T(sample,"verified")||!T(sample,"inSupercruise")||N(sample,"rawFsdState")!=2
||N(sample,"state")!=0||I(sample,"entityId")!=expected.Entity||I(sample,"referenceWorldAddress")!=expected.World
||U(sample,"originalUpdateClockEpoch")!=expected.ClockEpoch||!T(sample,"originalUpdateClockValid")
||!double.IsFinite(sampleAt)||sampleAt>at||at-sampleAt>200||!T(policy,"currentSourceDomainVerified")||!T(policy,"currentParameterBindingVerified")
||!F(sample,"playerOverride")||!F(sample,"healthEmergency")||N(O(sample,"target"),"key")!=expected.TargetKey)return false;
reason="exit-is-not-a-new-complete-shadow-pass";
if(!T(receipt,"pass")||!F(receipt,"trial")||!F(receipt,"production")||S(receipt,"reason")!="same-binding-shadow-completed"
||I(receipt,"entityId")!=expected.Entity||I(receipt,"referenceWorldAddress")!=expected.World||U(receipt,"clockEpoch")!=expected.ClockEpoch
||N(receipt,"targetKey")!=expected.TargetKey||U(receipt,"startCompletedSteps")<=expected.ConsumedReceiptSteps
||U(receipt,"endCompletedSteps")<=U(receipt,"startCompletedSteps")||U(sample,"originalUpdateCompletedSteps")<U(receipt,"endCompletedSteps")
||!double.IsFinite(N(receipt,"integratedPathDistanceMetres"))
||N(receipt,"integratedPathDistanceMetres") is not >0)return false;
reason="stop-is-not-an-explicit-domain-approach-fallback";
var choice=gate.ValueKind==JsonValueKind.Object?ScoExitApproachDecision.Read(gate):null;var stopAt=U(stop,"observedAtUnixMilliseconds");
if(!T(stop,"nativeMethodCalled")||!F(stop,"argument")||S(stop,"nativeMethodRva")!="2eaacd0"||S(stop,"reason")!="approach-or-alignment"
||N(stop,"stateBefore")!=2||N(stop,"stateAfter")!=3||I(stop,"entityId")!=expected.Entity||I(stop,"referenceWorldAddress")!=expected.World
||choice is not {UseEstimatedStop:false,Mode:"trial",Reason:ScoExitApproachDecision.OutsideCalibrationReason}
||choice.Entity!=expected.Entity||choice.World!=expected.World||choice.TargetKey!=expected.TargetKey
||choice.TrialExpiresAt!=expected.OriginalTrialDeadline||choice.At==0||stopAt<choice.At||stopAt-choice.At>200||stopAt>=expected.OriginalTrialDeadline)return false;
reason="not-a-confirmed-thirteen-second-distance-stop";
var aim=N(gate,"aimErrorDegrees");
if(!T(gate,"identityVerified")||!T(gate,"targetVerified")||!T(gate,"geometryVerified")||!F(gate,"emergency")
||!F(gate,"collisionCourse")||!F(gate,"assistEnabled")||!double.IsFinite(aim)||aim is <0 or >6
||!T(gate,"cruiseParametersVerified")||N(gate,"cruiseApproachSeconds")!=6
||!ScoApproachEnvelope.RequiresStop(N(gate,"distanceMetres"),N(gate,"speedMetresPerSecond"),true,true,6))return false;
reason="heat-or-fuel-interlock-would-stop";
var thermal=O(gate,"thermalForecast");var heat=N(gate,"heatPercent");var peak=N(gate,"currentFrameGrossHeatPeakPercent");
var fuel=N(gate,"mainFuelTonnes");var capacity=N(gate,"fuelCapacityTonnes");var reserve=N(gate,"requiredFuelReserveTonnes");var burn=N(gate,"fuelRateUpperTonnesPerSecond");
if(!double.IsFinite(heat)||heat is <0 or >=100||!T(thermal,"known")||!F(thermal,"shouldStop")
||!T(gate,"currentFrameGrossHeatBoundKnown")||!double.IsFinite(peak)||peak>=100
||!double.IsFinite(fuel)||!double.IsFinite(capacity)||capacity<=0||fuel<0||fuel>capacity+.001
||!double.IsFinite(reserve)||reserve<=0||reserve>=capacity||!double.IsFinite(burn)||burn<=0
||fuel-reserve-burn*2-Math.Max(.25,capacity*.01)<=0)return false;
reason="one-remaining-burst-with-original-authorization";return true;
}
}
using System.Globalization;
using System.Text.Json;
namespace XFEEDToolBox.Core.Navigation;
public sealed record ScoTrialContinuationContext(int ProcessId,string GameSha256,string LoadoutFingerprint,ulong Entity,ulong World,ulong ClockEpoch,
int TargetKey,ulong ConsumedReceiptSteps,ulong OriginalTrialDeadline,int CompletedFallbacks);
/// <summary>Read-only QA decision. It never arms a trial, changes a domain or extends a deadline.</summary>
public static class ScoTrialFallbackContinuation
{
public static bool CanContinue(JsonElement status,ScoTrialContinuationContext expected,DateTimeOffset now,out string reason)
{
static JsonElement O(JsonElement row,string key)=>row.ValueKind==JsonValueKind.Object&&row.TryGetProperty(key,out var item)?item:default;
static bool T(JsonElement row,string key)=>O(row,key).ValueKind==JsonValueKind.True;
static bool F(JsonElement row,string key)=>O(row,key).ValueKind==JsonValueKind.False;
static string S(JsonElement row,string key)=>O(row,key) is var item&&item.ValueKind==JsonValueKind.String?item.GetString()??"":"";
static double N(JsonElement row,string key)=>O(row,key) is var item&&item.ValueKind==JsonValueKind.Number&&item.TryGetDouble(out var value)?value:double.NaN;
static ulong U(JsonElement row,string key)=>O(row,key) is var item&&item.ValueKind==JsonValueKind.Number&&item.TryGetUInt64(out var value)?value:0;
static ulong I(JsonElement row,string key)=>ulong.TryParse(S(row,key),NumberStyles.None,CultureInfo.InvariantCulture,out var value)?value:0;
reason="fallback-already-consumed-or-invalid-context";
var at=now.ToUnixTimeMilliseconds();
if(expected.CompletedFallbacks!=0||expected.ProcessId<=0||expected.GameSha256.Length!=64||expected.LoadoutFingerprint.Length!=64
||expected.Entity==0||expected.World==0||expected.ClockEpoch==0||expected.TargetKey<0
||expected.OriginalTrialDeadline<=(ulong)Math.Max(0,at)||expected.OriginalTrialDeadline-(ulong)Math.Max(0,at)>600000)return false;
reason="foreign-service-or-loadout";
var policy=O(status,"scoExitEstimate");var sample=O(status,"sample");var receipt=O(policy,"lastExitReceipt");var stop=O(status,"lastOriginalStop");var gate=O(stop,"gate");
if(!T(status,"enabled")||N(status,"processId")!=expected.ProcessId||S(status,"service")!=CruiseControlProtocol.Service||S(status,"gameSha256")!=expected.GameSha256
||expected.LoadoutFingerprint.Length!=64||S(policy,"trustedLoadoutFingerprint")!=expected.LoadoutFingerprint)return false;
reason="no-remaining-original-trial-authorization";
if(S(policy,"mode")!="bounded-trial"||U(policy,"trialExpiresAtUnixMilliseconds")!=expected.OriginalTrialDeadline
||N(policy,"maximumTrialBurstsRemaining")!=1||!F(policy,"trialBurstActive")||!T(policy,"proofAvailable")
||O(policy,"lastPolicyRevocationFailure").ValueKind!=JsonValueKind.Null)return false;
reason="current-binding-or-source-unavailable";
var sampleAt=N(sample,"observedAtUnixMilliseconds");
if(!T(status,"sampleReady")||!F(status,"owned")||!T(sample,"verified")||!T(sample,"inSupercruise")||N(sample,"rawFsdState")!=2
||N(sample,"state")!=0||I(sample,"entityId")!=expected.Entity||I(sample,"referenceWorldAddress")!=expected.World
||U(sample,"originalUpdateClockEpoch")!=expected.ClockEpoch||!T(sample,"originalUpdateClockValid")
||!double.IsFinite(sampleAt)||sampleAt>at||at-sampleAt>200||!T(policy,"currentSourceDomainVerified")||!T(policy,"currentParameterBindingVerified")
||!F(sample,"playerOverride")||!F(sample,"healthEmergency")||N(O(sample,"target"),"key")!=expected.TargetKey)return false;
reason="exit-is-not-a-new-complete-shadow-pass";
if(!T(receipt,"pass")||!F(receipt,"trial")||!F(receipt,"production")||S(receipt,"reason")!="same-binding-shadow-completed"
||I(receipt,"entityId")!=expected.Entity||I(receipt,"referenceWorldAddress")!=expected.World||U(receipt,"clockEpoch")!=expected.ClockEpoch
||N(receipt,"targetKey")!=expected.TargetKey||U(receipt,"startCompletedSteps")<=expected.ConsumedReceiptSteps
||U(receipt,"endCompletedSteps")<=U(receipt,"startCompletedSteps")||U(sample,"originalUpdateCompletedSteps")<U(receipt,"endCompletedSteps")
||!double.IsFinite(N(receipt,"integratedPathDistanceMetres"))
||N(receipt,"integratedPathDistanceMetres") is not >0)return false;
reason="stop-is-not-an-explicit-domain-approach-fallback";
var choice=gate.ValueKind==JsonValueKind.Object?ScoExitApproachDecision.Read(gate):null;var stopAt=U(stop,"observedAtUnixMilliseconds");
if(!T(stop,"nativeMethodCalled")||!F(stop,"argument")||S(stop,"nativeMethodRva")!="2eaacd0"||S(stop,"reason")!="approach-or-alignment"
||N(stop,"stateBefore")!=2||N(stop,"stateAfter")!=3||I(stop,"entityId")!=expected.Entity||I(stop,"referenceWorldAddress")!=expected.World
||choice is not {UseEstimatedStop:false,Mode:"trial",Reason:ScoExitApproachDecision.OutsideCalibrationReason}
||choice.Entity!=expected.Entity||choice.World!=expected.World||choice.TargetKey!=expected.TargetKey
||choice.TrialExpiresAt!=expected.OriginalTrialDeadline||choice.At==0||stopAt<choice.At||stopAt-choice.At>200||stopAt>=expected.OriginalTrialDeadline)return false;
reason="not-a-confirmed-thirteen-second-distance-stop";
var aim=N(gate,"aimErrorDegrees");
if(!T(gate,"identityVerified")||!T(gate,"targetVerified")||!T(gate,"geometryVerified")||!F(gate,"emergency")
||!F(gate,"collisionCourse")||!F(gate,"assistEnabled")||!double.IsFinite(aim)||aim is <0 or >6
||!T(gate,"cruiseParametersVerified")||N(gate,"cruiseApproachSeconds")!=6
||!ScoApproachEnvelope.RequiresStop(N(gate,"distanceMetres"),N(gate,"speedMetresPerSecond"),true,true,6))return false;
reason="heat-or-fuel-interlock-would-stop";
var thermal=O(gate,"thermalForecast");var heat=N(gate,"heatPercent");var peak=N(gate,"currentFrameGrossHeatPeakPercent");
var fuel=N(gate,"mainFuelTonnes");var capacity=N(gate,"fuelCapacityTonnes");var reserve=N(gate,"requiredFuelReserveTonnes");var burn=N(gate,"fuelRateUpperTonnesPerSecond");
if(!double.IsFinite(heat)||heat is <0 or >=100||!T(thermal,"known")||!F(thermal,"shouldStop")
||!T(gate,"currentFrameGrossHeatBoundKnown")||!double.IsFinite(peak)||peak>=100
||!double.IsFinite(fuel)||!double.IsFinite(capacity)||capacity<=0||fuel<0||fuel>capacity+.001
||!double.IsFinite(reserve)||reserve<=0||reserve>=capacity||!double.IsFinite(burn)||burn<=0
||fuel-reserve-burn*2-Math.Max(.25,capacity*.01)<=0)return false;
reason="one-remaining-burst-with-original-authorization";return true;
}
}