namespace XFEEDToolBox.Core.Navigation;
public sealed record CruiseOverchargeGateFrame
{
public bool IdentityVerified{get;init;}
public bool GeometryVerified{get;init;}
public bool TargetVerified{get;init;}
public bool InSupercruise{get;init;}
public bool AssistEnabled{get;init;}
public bool CollisionCourse{get;init;}
public bool Emergency{get;init;}
public bool NativeCanEngage{get;init;}
public int RawFsdState{get;init;}
public int OverchargeState{get;init;}=-1;
public ulong ObservedAt{get;init;}
public double Hull{get;init;}=double.NaN;
public double Heat{get;init;}=double.NaN;
public double HeatRise{get;init;}=double.NaN;
public ScoThermalInput? ThermalInput{get;init;}
public double MainFuel{get;init;}=double.NaN;
public double FuelCapacity{get;init;}=double.NaN;
public double FuelRateUpper{get;init;}=double.NaN;
public double RequiredFuelReserve{get;init;}=double.NaN;
public double Distance{get;init;}=double.NaN;
public double Speed{get;init;}=double.NaN;
public double AimErrorDegrees{get;init;}=double.NaN;
public bool CruiseParametersVerified{get;init;}
public double CruiseApproachSeconds{get;init;}=double.NaN;
public ulong ShipEntity{get;init;}
public ulong ReferenceWorld{get;init;}
public int TargetKey{get;init;}=-1;
public ScoExitApproachDecision? ExitApproach{get;init;}
}
/// <summary>Additional per-update SCO interlock, evaluated inside the original owning thread.</summary>
public static class CruiseOverchargeNativeGuard
{
public static string? Refusal(CruiseOverchargeGateFrame value,ulong now,ulong deadline)
{
if(!value.IdentityVerified||!value.TargetVerified)return "identity-or-target";
if(value.ObservedAt>now||now-value.ObservedAt>200||deadline<=now||deadline-now>250)return "expired";
if(!value.InSupercruise||value.RawFsdState!=2||value.OverchargeState is <0 or >2)return "flight-state";
if(!value.GeometryVerified||value.CollisionCourse||value.AssistEnabled)return "geometry-or-assist";
if(value.Emergency||!double.IsFinite(value.Hull)||value.Hull is <=0 or >1)return "health";
if(!value.NativeCanEngage)return "native-refusal";
var active=value.OverchargeState is 1 or 2;
if(!double.IsFinite(value.Distance)||!double.IsFinite(value.Speed)||value.Speed<0||!double.IsFinite(value.AimErrorDegrees)
||(value.ExitApproach?.RequiresStop(value.Distance,value.Speed,active,value.CruiseParametersVerified,value.CruiseApproachSeconds,
value.ShipEntity,value.ReferenceWorld,value.TargetKey,now)
??ScoApproachEnvelope.RequiresStop(value.Distance,value.Speed,active,value.CruiseParametersVerified,value.CruiseApproachSeconds))
||value.AimErrorDegrees<0||value.AimErrorDegrees>(active?6:3))return "approach-or-alignment";
if(!double.IsFinite(value.Heat)||value.Heat<0||value.Heat>=100)return "heat";
if(value.ThermalInput is not {} thermal)return "thermal-model";
var forecast=ScoThermalPredictor.Predict(thermal with{HeatPercent=value.Heat,LeaseRemainingSeconds=(deadline-now)/1000d});
if(!forecast.Known)return "thermal-model";
if(forecast.ShouldStop)return "heat";
if(!double.IsFinite(value.MainFuel)||!double.IsFinite(value.FuelCapacity)||value.FuelCapacity<=0||value.MainFuel<0||value.MainFuel>value.FuelCapacity+.001
||!double.IsFinite(value.RequiredFuelReserve)||value.RequiredFuelReserve<=0||value.RequiredFuelReserve>=value.FuelCapacity
||!double.IsFinite(value.FuelRateUpper)||value.FuelRateUpper<=0
||value.MainFuel-value.RequiredFuelReserve-value.FuelRateUpper*2-Math.Max(.25,value.FuelCapacity*.01)<=0)return "fuel";
return null;
}
}
namespace XFEEDToolBox.Core.Navigation;
public sealed record CruiseOverchargeGateFrame
{
public bool IdentityVerified{get;init;}
public bool GeometryVerified{get;init;}
public bool TargetVerified{get;init;}
public bool InSupercruise{get;init;}
public bool AssistEnabled{get;init;}
public bool CollisionCourse{get;init;}
public bool Emergency{get;init;}
public bool NativeCanEngage{get;init;}
public int RawFsdState{get;init;}
public int OverchargeState{get;init;}=-1;
public ulong ObservedAt{get;init;}
public double Hull{get;init;}=double.NaN;
public double Heat{get;init;}=double.NaN;
public double HeatRise{get;init;}=double.NaN;
public ScoThermalInput? ThermalInput{get;init;}
public double MainFuel{get;init;}=double.NaN;
public double FuelCapacity{get;init;}=double.NaN;
public double FuelRateUpper{get;init;}=double.NaN;
public double RequiredFuelReserve{get;init;}=double.NaN;
public double Distance{get;init;}=double.NaN;
public double Speed{get;init;}=double.NaN;
public double AimErrorDegrees{get;init;}=double.NaN;
public bool CruiseParametersVerified{get;init;}
public double CruiseApproachSeconds{get;init;}=double.NaN;
public ulong ShipEntity{get;init;}
public ulong ReferenceWorld{get;init;}
public int TargetKey{get;init;}=-1;
public ScoExitApproachDecision? ExitApproach{get;init;}
}
/// <summary>Additional per-update SCO interlock, evaluated inside the original owning thread.</summary>
public static class CruiseOverchargeNativeGuard
{
public static string? Refusal(CruiseOverchargeGateFrame value,ulong now,ulong deadline)
{
if(!value.IdentityVerified||!value.TargetVerified)return "identity-or-target";
if(value.ObservedAt>now||now-value.ObservedAt>200||deadline<=now||deadline-now>250)return "expired";
if(!value.InSupercruise||value.RawFsdState!=2||value.OverchargeState is <0 or >2)return "flight-state";
if(!value.GeometryVerified||value.CollisionCourse||value.AssistEnabled)return "geometry-or-assist";
if(value.Emergency||!double.IsFinite(value.Hull)||value.Hull is <=0 or >1)return "health";
if(!value.NativeCanEngage)return "native-refusal";
var active=value.OverchargeState is 1 or 2;
if(!double.IsFinite(value.Distance)||!double.IsFinite(value.Speed)||value.Speed<0||!double.IsFinite(value.AimErrorDegrees)
||(value.ExitApproach?.RequiresStop(value.Distance,value.Speed,active,value.CruiseParametersVerified,value.CruiseApproachSeconds,
value.ShipEntity,value.ReferenceWorld,value.TargetKey,now)
??ScoApproachEnvelope.RequiresStop(value.Distance,value.Speed,active,value.CruiseParametersVerified,value.CruiseApproachSeconds))
||value.AimErrorDegrees<0||value.AimErrorDegrees>(active?6:3))return "approach-or-alignment";
if(!double.IsFinite(value.Heat)||value.Heat<0||value.Heat>=100)return "heat";
if(value.ThermalInput is not {} thermal)return "thermal-model";
var forecast=ScoThermalPredictor.Predict(thermal with{HeatPercent=value.Heat,LeaseRemainingSeconds=(deadline-now)/1000d});
if(!forecast.Known)return "thermal-model";
if(forecast.ShouldStop)return "heat";
if(!double.IsFinite(value.MainFuel)||!double.IsFinite(value.FuelCapacity)||value.FuelCapacity<=0||value.MainFuel<0||value.MainFuel>value.FuelCapacity+.001
||!double.IsFinite(value.RequiredFuelReserve)||value.RequiredFuelReserve<=0||value.RequiredFuelReserve>=value.FuelCapacity
||!double.IsFinite(value.FuelRateUpper)||value.FuelRateUpper<=0
||value.MainFuel-value.RequiredFuelReserve-value.FuelRateUpper*2-Math.Max(.25,value.FuelCapacity*.01)<=0)return "fuel";
return null;
}
}