namespace XFEEDToolBox.Core.Navigation;
/// <summary>
/// Bounds supplied by a separately qualified original ordinary-supercruise
/// controller. Merely reading these numbers does not qualify a brake response.
/// Speeds are m/s, accelerations m/s², distances m and times seconds.
/// </summary>
public sealed record ScoBrakingInput
{
public bool OriginalParametersVerified{get;init;}
public bool OrdinaryArrivalControllerVerified{get;init;}
public bool OverspeedBrakeBoundVerified{get;init;}
public double RemainingDistanceMetres{get;init;}=double.NaN;
public double CurrentSpeedMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinarySpeedLimitUpperMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseOverspeedBrakeAcceleration{get;init;}=double.NaN;
public int OriginalBrakeScalingMode{get;init;}=-1;
// Modes 0/1 get weaker as the ordinary scale falls. Their lower bound
// must cover the whole exit, not just the current provider snapshot.
public double OriginalOrdinaryBrakeScaleLower{get;init;}=double.NaN;
public double OriginalExtraBrakeFactorLower{get;init;}=double.NaN;
public double OriginalArrivalWindowSeconds{get;init;}=double.NaN;
public double StopResponseSecondsUpper{get;init;}=double.NaN;
public double ActiveAccelerationMetresPerSecondSquaredUpper{get;init;}=double.NaN;
public double PositionUncertaintyMetres{get;init;}=double.NaN;
public double SafeDropDistanceMetres{get;init;}=double.NaN;
}
public sealed record ScoBrakingForecast(bool Known,string Reason)
{
public bool ContinueOvercharge{get;init;}
public double ResponseDistanceMetres{get;init;}=double.NaN;
public double SpeedAtOriginalStopMetresPerSecond{get;init;}=double.NaN;
public double OverspeedDistanceMetres{get;init;}=double.NaN;
public double OverspeedSeconds{get;init;}=double.NaN;
public double OrdinaryEntrySpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinaryArrivalEnvelopeMetres{get;init;}=double.NaN;
public double StopDistanceMetres{get;init;}=double.NaN;
public bool CoolingDragUsed=>false;
}
/// <summary>
/// Conditional integrals and an explicitly estimated, unconnected exit model.
/// Predict requires an independently established effective brake factor: the
/// native .25 extra brake is NOT an unconditional ordinary-supercruise force.
/// Cooling's additional -lambda*v force is deliberately ignored until its
/// duration is qualified; the current SCO speed is never held for the whole
/// approach. This is an opt-in mathematical forecast, not live qualification.
/// </summary>
public static class ScoBrakingPredictor
{
/// <summary>
/// An estimate for a bounded trial, never a live qualification. Cooling gets
/// no braking credit. Once Off and fully assisted, native mode 2 supplies
/// A(v,U) >= (A_base/V_base)*v ONLY WHILE v>U. The forward servo request has
/// the same lower bound above its bias-dependent floor; jerk starts at zero.
/// The budget maximises D+window*v across possible ordinary handoff speeds.
/// That union is not a physical exponential continued below the ordinary cap.
/// </summary>
public static ScoExitEstimate EstimateExit(ScoExitEstimateInput input)
{
if(!input.OriginalParametersVerified)return EstimateUnknown("original-parameters-unverified");
if(!input.ForwardServoDomainVerified)return EstimateUnknown("forward-servo-domain-unverified");
if(!input.OrdinaryArrivalControllerVerified)return EstimateUnknown("ordinary-arrival-controller-unverified");
if(input.OriginalBrakeScalingMode!=2)return EstimateUnknown("mode-2-required");
var v=input.CurrentSpeedMetresPerSecond;var maximum=input.OriginalBaseMaximumSpeedMetresPerSecond;
var brake=input.OriginalBaseBrakeAcceleration;var jerk=input.OriginalJerkMetresPerSecondCubed;
var throttle=input.ForwardThrottle;var bias=input.OriginalForwardTargetBiasMetresPerSecond;
var response=input.StopResponseSecondsUpper;var settle=input.EstimatedCoolingAndAssistSettleSeconds;
var physicsStep=input.MaximumPhysicsStepSeconds;var window=input.OriginalArrivalWindowSeconds;
if(!Positive(v)||!Positive(maximum)||v>=maximum||!Positive(brake)||!Positive(jerk)
||!Nonnegative(throttle)||throttle>.75||!Nonnegative(bias)||!Positive(input.OriginalResponseTimeSeconds)
||!Positive(input.CurrentOrdinarySpeedLimitMetresPerSecond)||input.CurrentOrdinarySpeedLimitMetresPerSecond>maximum
||!Nonnegative(response)||response>1||!Nonnegative(settle)||settle>30
||!Positive(physicsStep)||physicsStep>1||!Nonnegative(input.AdditionalResponseMarginSeconds)
||!Nonnegative(input.ActiveAccelerationUpper)||!Positive(window)||window>60
||!Positive(input.MinimumOrdinaryHandoffSpeedMetresPerSecond)
||!Nonnegative(input.DistanceUncertaintyMetres)||!Nonnegative(input.SafeDropDistanceMetres)
||!Nonnegative(input.RelativeDistanceMargin)||input.RelativeDistanceMargin>1
||!Positive(input.RemainingDistanceMetres))return EstimateUnknown("invalid-exit-estimate-input");
var initial=v+input.ActiveAccelerationUpper*response;
if(!Positive(initial)||initial>=maximum)return EstimateUnknown("below-base-speed-domain-exceeded");
if(v<=input.CurrentOrdinarySpeedLimitMetresPerSecond)return EstimateUnknown("forward-overspeed-domain-required");
var tauUpper=Math.Max(Math.Max(.5,input.OriginalResponseTimeSeconds),physicsStep);
var sigma=(1-throttle)/tauUpper;
// If the native clamp's rate is too large for the bias servo, half of
// sigma is a mathematical lower bound above 2*bias, not a fitted brake.
var rate=brake/maximum;
if(rate>=sigma)rate=sigma/2;
var floor=Math.Max(input.MinimumOrdinaryHandoffSpeedMetresPerSecond,bias*sigma/(sigma-rate));
if(!Positive(rate)||!Positive(floor)||floor>=initial
||input.CurrentOrdinarySpeedLimitMetresPerSecond<floor)return EstimateUnknown("ordinary-handoff-floor-unavailable");
var path=CreatePath(initial,rate,jerk,floor);
if(path is null)return EstimateUnknown("exit-budget-overflow");
// For any actual earlier handoff the budget is D(t)+W*v(t).
// In the exponential domain its derivative is v*(1-W*rate).
// In the jerk ramp it is v-W*J*t and has one positive root.
var selected=path.At(0);var selectedTime=0d;
void Consider(double time)
{
var point=path.At(time);
if(point.Distance+window*point.Speed>selected.Distance+window*selected.Speed)
{selected=point;selectedTime=time;}
}
Consider(path.FloorSeconds);
var critical=2*initial/jerk/(Math.Sqrt(window*window+2*initial/jerk)+window);
if(critical>0&&critical<Math.Min(path.RampSeconds,path.FloorSeconds))Consider(critical);
var responseDistance=v*response+.5*input.ActiveAccelerationUpper*response*response;
var noBrakeDelay=settle+physicsStep+input.AdditionalResponseMarginSeconds;
var transitionDistance=responseDistance+initial*noBrakeDelay;
var ordinaryEnvelope=window*selected.Speed;
var overspeedBudget=selected.Distance+ordinaryEnvelope;
var uncertainty=input.DistanceUncertaintyMetres+input.SafeDropDistanceMetres+input.RelativeDistanceMargin*overspeedBudget;
var total=Math.BitIncrement(transitionDistance+overspeedBudget+uncertainty);
if(!Positive(total))return EstimateUnknown("exit-budget-overflow");
return new(true,"estimated-no-cooling-credit-jerk-and-native-mode-2-handoff-union")
{
RemainingDistanceExceedsEstimate=input.RemainingDistanceMetres>total,
EstimatedStopDistanceMetres=total,NoBrakeTransitionDistanceMetres=transitionDistance,
OverspeedBudgetDistanceMetres=selected.Distance,OrdinaryArrivalEnvelopeMetres=ordinaryEnvelope,
MarginDistanceMetres=uncertainty,OffBudgetInitialSpeedMetresPerSecond=initial,
DecayRatePerSecond=rate,ServoDomainFloorMetresPerSecond=floor,
JerkRampSeconds=path.RampSeconds,DomainFloorSeconds=path.FloorSeconds,
BudgetHandoffSeconds=selectedTime,BudgetHandoffSpeedMetresPerSecond=selected.Speed,
NoBrakeDelaySeconds=response+noBrakeDelay,AssistSettleDeadlineSeconds=response+settle
};
}
/// <summary>
/// Guards a future bounded trial using fresh, independently verified native
/// observations. It sends no command. A real cap crossing ends this model;
/// ordinary cruise may then accelerate and remains the arrival controller's
/// job. A missed settle deadline or a slower brake invalidates the estimate.
/// </summary>
public static ScoExitEstimateCheck CheckExitEstimate(ScoExitEstimateInput input,ScoExitEstimate estimate,ScoExitEstimateObservation value)
{
if(!estimate.Known)return new(false,false,"estimate-unavailable");
if(!value.FreshBindingAndSourceGatesVerified)return new(false,false,"native-binding-or-source-gates-changed");
if(!Nonnegative(value.ElapsedSeconds)||!Nonnegative(value.SpeedMetresPerSecond)
||!Positive(value.OrdinarySpeedLimitMetresPerSecond)||value.OrdinarySpeedLimitMetresPerSecond>input.OriginalBaseMaximumSpeedMetresPerSecond
||!Positive(value.NativeStepSeconds)
||value.NativeStepSeconds>input.MaximumPhysicsStepSeconds
||!Nonnegative(value.FlightAssistBlend)||value.FlightAssistBlend>1
||!Nonnegative(value.AllowedSpeedResidualFraction)||value.AllowedSpeedResidualFraction>1
||!Nonnegative(value.AllowedSpeedResidualMetresPerSecond))
return new(false,false,"invalid-exit-observation");
if(value.ElapsedSeconds>input.StopResponseSecondsUpper&&value.ScoState is not 0 and not 3)
return new(false,false,"original-stop-not-observed");
if(value.FlightAssistBlend<1&&value.SpeedMetresPerSecond<value.OrdinarySpeedLimitMetresPerSecond)
return new(false,false,"mixed-assist-below-ordinary-cap");
if(value.ScoState==0&&value.FlightAssistBlend==1&&value.SpeedMetresPerSecond<=value.OrdinarySpeedLimitMetresPerSecond)
return new(true,true,"actual-ordinary-cap-handoff");
if(value.ElapsedSeconds>estimate.AssistSettleDeadlineSeconds&&(value.ScoState!=0||value.FlightAssistBlend!=1))
return new(false,false,"estimated-cooling-or-assist-settle-overrun");
if(value.SpeedMetresPerSecond<estimate.ServoDomainFloorMetresPerSecond
||value.OrdinarySpeedLimitMetresPerSecond<estimate.ServoDomainFloorMetresPerSecond)
return new(false,false,"actual-ordinary-handoff-required-at-domain-floor");
var path=CreatePath(estimate.OffBudgetInitialSpeedMetresPerSecond,estimate.DecayRatePerSecond,
input.OriginalJerkMetresPerSecondCubed,estimate.ServoDomainFloorMetresPerSecond);
if(path is null)return new(false,false,"exit-budget-overflow");
var elapsed=Math.Max(0,value.ElapsedSeconds-estimate.NoBrakeDelaySeconds);
if(elapsed>path.FloorSeconds)return new(false,false,"overspeed-domain-time-exceeded");
var upper=path.At(elapsed).Speed;
upper+=upper*value.AllowedSpeedResidualFraction+value.AllowedSpeedResidualMetresPerSecond;
return value.SpeedMetresPerSecond>upper
?new(false,false,"observed-brake-slower-than-estimate")
:new(true,false,"within-estimated-overspeed-domain");
}
/// <summary>
/// One scalar original-physics replay step. Caller must supply the actual
/// physics dt and current cap; sparse sample dt values cannot be summed to
/// reconstruct unobserved frames. No quarter-brake shortcut is used.
/// Valid for full FA, or mixed FA while forward speed stays above cap and
/// below base maximum (so the unassisted forward/extra-brake terms are zero).
/// </summary>
public static ScoServoStepResult StepOriginalServo(ScoServoParameters parameters,ScoServoStepInput value)
{
var speed=value.SpeedMetresPerSecond;var maximum=parameters.BaseMaximumSpeedMetresPerSecond;
var cap=value.OrdinaryPhysicsMaximumSpeedMetresPerSecond;var blend=value.FlightAssistBlend;
if(!parameters.PinnedSourceDomainVerified)return ServoUnknown("pinned-scalar-source-domain-unverified");
if(!Positive(maximum)||!Positive(parameters.BaseForwardAcceleration)||!Positive(parameters.BaseBrakeAcceleration)
||!Positive(parameters.ResponseTimeSeconds)||!Positive(parameters.JerkMetresPerSecondCubed)
||!Nonnegative(parameters.ForwardTargetBiasMetresPerSecond)||!Nonnegative(parameters.ForwardThrottle)||parameters.ForwardThrottle>.75
||!Nonnegative(speed)||speed>=maximum||!Positive(cap)||cap>maximum
||!Nonnegative(blend)||blend>1||!Nonnegative(value.PreviousServoAccelerationNorm)
||!Nonnegative(value.CoolingDragPerSecond)||!Positive(value.NativeStepSeconds)||value.NativeStepSeconds>1
||(blend<1&&speed<cap))return ServoUnknown("outside-original-scalar-domain");
var scale=cap/maximum;var ratio=speed/maximum;
var brake=parameters.BaseBrakeAcceleration*Math.Max(scale,ratio*Math.Max(1,Math.Sqrt(ratio/scale)));
var target=(cap-parameters.ForwardTargetBiasMetresPerSecond)*parameters.ForwardThrottle+parameters.ForwardTargetBiasMetresPerSecond;
var tau=Math.Max(.5,parameters.ResponseTimeSeconds*Math.Max(Math.Clamp(ratio,0,1)*4/3-1d/3,0));
var requested=(target-speed)/Math.Max(tau,value.NativeStepSeconds);
var clamped=Math.Clamp(requested,-brake,parameters.BaseForwardAcceleration*scale);
var norm=Math.Min(Math.Abs(clamped),value.PreviousServoAccelerationNorm+parameters.JerkMetresPerSecondCubed*value.NativeStepSeconds);
var acceleration=Math.CopySign(norm,clamped)*blend;
// The original lambda force is inside if(B>0), not multiplied by B.
if(value.Cooling&&blend>0)acceleration-=value.CoolingDragPerSecond*speed;
var next=Math.Max(0,speed+acceleration*value.NativeStepSeconds);
return new(true,"original-forward-servo-scalar-step")
{NextSpeedMetresPerSecond=next,NextServoAccelerationNorm=norm,AppliedAcceleration=acceleration,
NativeBrakeClampAcceleration=brake,TargetSpeedMetresPerSecond=target,EffectiveResponseTimeSeconds=tau,
ApproximateDistanceMetres=(speed+next)*.5*value.NativeStepSeconds};
}
private static ScoExitEstimate EstimateUnknown(string reason)=>new(false,reason);
private static ScoServoStepResult ServoUnknown(string reason)=>new(false,reason);
private sealed record BudgetPoint(double Speed,double Distance);
private sealed record BudgetPath(double Initial,double Rate,double Jerk,double Floor,double RampSeconds,double RampEndSpeed,double FloorSeconds)
{
public BudgetPoint At(double seconds)
{
if(seconds<=RampSeconds)return new(Initial-.5*Jerk*seconds*seconds,Initial*seconds-Jerk*seconds*seconds*seconds/6);
var speed=RampEndSpeed*Math.Exp(-Rate*(seconds-RampSeconds));
return new(speed,Initial*RampSeconds-Jerk*RampSeconds*RampSeconds*RampSeconds/6+(RampEndSpeed-speed)/Rate);
}
}
private static BudgetPath? CreatePath(double initial,double rate,double jerk,double floor)
{
// J*t = rate*(v0-.5*J*t²), evaluated without subtracting close roots.
var ramp=2*rate*initial/jerk/(1+Math.Sqrt(1+2*rate*rate*initial/jerk));
var end=initial-.5*jerk*ramp*ramp;
var floorTime=floor>=end?Math.Sqrt(2*(initial-floor)/jerk):ramp+Math.Log(end/floor)/rate;
return Positive(ramp)&&Positive(end)&&Nonnegative(floorTime)
?new(initial,rate,jerk,floor,ramp,end,floorTime):null;
}
public static ScoBrakingForecast Predict(ScoBrakingInput value)
{
if(!value.OriginalParametersVerified)return Unknown("original-parameters-unverified");
if(!value.OrdinaryArrivalControllerVerified)return Unknown("ordinary-arrival-controller-unverified");
if(!value.OverspeedBrakeBoundVerified)return Unknown("ordinary-overspeed-brake-bound-unverified");
var remaining=value.RemainingDistanceMetres;var current=value.CurrentSpeedMetresPerSecond;
var maximum=value.OriginalBaseMaximumSpeedMetresPerSecond;var ordinary=value.OrdinarySpeedLimitUpperMetresPerSecond;
var acceleration=value.OriginalBaseOverspeedBrakeAcceleration;var factor=value.OriginalExtraBrakeFactorLower;
var window=value.OriginalArrivalWindowSeconds;var response=value.StopResponseSecondsUpper;
var activeAcceleration=value.ActiveAccelerationMetresPerSecondSquaredUpper;
if(!Positive(remaining)||!Nonnegative(current)||!Positive(maximum)||!Positive(ordinary)||ordinary>maximum
||!Positive(acceleration)||!Positive(factor)||factor>.25||value.OriginalBrakeScalingMode is <0 or >2
||!Positive(window)||window>60||!Nonnegative(response)||response>1||!Nonnegative(activeAcceleration)
||!Nonnegative(value.PositionUncertaintyMetres)||!Nonnegative(value.SafeDropDistanceMetres))return Unknown("invalid-original-braking-input");
var speed=current+activeAcceleration*response;
var responseDistance=current*response+.5*activeAcceleration*response*response;
if(!Nonnegative(speed)||!Nonnegative(responseDistance))return Unknown("response-overflow");
var scale=ordinary/maximum;
if(!Positive(scale)||scale>1)return Unknown("ordinary-scale-underflow");
if(value.OriginalBrakeScalingMode is 0 or 1
&&(!Positive(value.OriginalOrdinaryBrakeScaleLower)||value.OriginalOrdinaryBrakeScaleLower>scale))
return Unknown("ordinary-brake-scale-lower-bound-unverified");
var entry=Math.Min(speed,ordinary);var overspeedDistance=0d;var overspeedSeconds=0d;
if(speed>ordinary)
{
var delta=speed-ordinary;
if(value.OriginalBrakeScalingMode==2)
{
// Above Vordinary, the original mode-2 maximum selects
// a(v)=factor*A_base*(v/V_base)^(3/2)/sqrt(scale).
// Its exact integrals avoid both time stepping and treating
// the much higher SCO speed as a constant coast speed.
var rootScale=Math.Sqrt(scale);var rootHigh=Math.Sqrt(speed/maximum);
var denominator=factor*acceleration*(rootHigh+rootScale);
overspeedDistance=2*maximum*rootScale*delta/denominator;
overspeedSeconds=2*delta/(denominator*rootHigh);
}
else
{
var lower=value.OriginalOrdinaryBrakeScaleLower;
var deceleration=factor*acceleration*(value.OriginalBrakeScalingMode==0?lower:Math.Sqrt(lower));
overspeedDistance=delta*(speed+ordinary)/(2*deceleration);
overspeedSeconds=delta/deceleration;
}
}
var ordinaryEnvelope=entry*window;
var total=responseDistance+overspeedDistance+ordinaryEnvelope+value.PositionUncertaintyMetres+value.SafeDropDistanceMetres;
if(!Nonnegative(overspeedDistance)||!Nonnegative(overspeedSeconds)||!Nonnegative(ordinaryEnvelope)||!Positive(total))return Unknown("braking-integration-overflow");
// Round away from a later stop when summing very different magnitudes.
total=Math.BitIncrement(total);
if(!Positive(total))return Unknown("braking-integration-overflow");
return new(true,"original-overspeed-integral-and-ordinary-arrival-envelope")
{
ContinueOvercharge=remaining>total,ResponseDistanceMetres=responseDistance,SpeedAtOriginalStopMetresPerSecond=speed,
OverspeedDistanceMetres=overspeedDistance,OverspeedSeconds=overspeedSeconds,OrdinaryEntrySpeedMetresPerSecond=entry,
OrdinaryArrivalEnvelopeMetres=ordinaryEnvelope,StopDistanceMetres=total
};
}
private static ScoBrakingForecast Unknown(string reason)=>new(false,reason);
private static bool Positive(double value)=>double.IsFinite(value)&&value>0;
private static bool Nonnegative(double value)=>double.IsFinite(value)&&value>=0;
}
/// <summary>
/// All units SI. Verified domain means fresh pinned identity/mode 2, straight
/// positive local-Z, u<=.75, unit translation scale, disabled external motion
/// and target-velocity correction, full negative clamp, and an ordinary arrival
/// controller ready to accept actual v<=cap. Future Cooling/assist settlement
/// is deliberately an estimate with an online deadline, never a proven bound.
/// </summary>
public sealed record ScoExitEstimateInput
{
public bool OriginalParametersVerified{get;init;}
public bool ForwardServoDomainVerified{get;init;}
public bool OrdinaryArrivalControllerVerified{get;init;}
public int OriginalBrakeScalingMode{get;init;}=-1;
public double RemainingDistanceMetres{get;init;}=double.NaN;
public double CurrentSpeedMetresPerSecond{get;init;}=double.NaN;
public double CurrentOrdinarySpeedLimitMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseBrakeAcceleration{get;init;}=double.NaN;
public double OriginalResponseTimeSeconds{get;init;}=double.NaN;
public double OriginalJerkMetresPerSecondCubed{get;init;}=double.NaN;
public double OriginalForwardTargetBiasMetresPerSecond{get;init;}=double.NaN;
public double ForwardThrottle{get;init;}=double.NaN;
public double StopResponseSecondsUpper{get;init;}=double.NaN;
public double ActiveAccelerationUpper{get;init;}=double.NaN;
public double EstimatedCoolingAndAssistSettleSeconds{get;init;}=double.NaN;
public double MaximumPhysicsStepSeconds{get;init;}=double.NaN;
public double AdditionalResponseMarginSeconds{get;init;}=double.NaN;
public double MinimumOrdinaryHandoffSpeedMetresPerSecond{get;init;}=double.NaN;
public double OriginalArrivalWindowSeconds{get;init;}=double.NaN;
public double RelativeDistanceMargin{get;init;}=double.NaN;
public double DistanceUncertaintyMetres{get;init;}=double.NaN;
public double SafeDropDistanceMetres{get;init;}=double.NaN;
}
public sealed record ScoExitEstimate(bool Known,string Reason)
{
public bool Estimated=>true;
public bool QualifiedForControl=>false;
public bool CoolingDragUsed=>false;
public bool RequiresActualOrdinaryHandoff=>true;
public bool RemainingDistanceExceedsEstimate{get;init;}
public double EstimatedStopDistanceMetres{get;init;}=double.NaN;
public double NoBrakeTransitionDistanceMetres{get;init;}=double.NaN;
public double OverspeedBudgetDistanceMetres{get;init;}=double.NaN;
public double OrdinaryArrivalEnvelopeMetres{get;init;}=double.NaN;
public double MarginDistanceMetres{get;init;}=double.NaN;
public double OffBudgetInitialSpeedMetresPerSecond{get;init;}=double.NaN;
public double DecayRatePerSecond{get;init;}=double.NaN;
public double ServoDomainFloorMetresPerSecond{get;init;}=double.NaN;
public double JerkRampSeconds{get;init;}=double.NaN;
public double DomainFloorSeconds{get;init;}=double.NaN;
public double BudgetHandoffSeconds{get;init;}=double.NaN;
public double BudgetHandoffSpeedMetresPerSecond{get;init;}=double.NaN;
public double NoBrakeDelaySeconds{get;init;}=double.NaN;
public double AssistSettleDeadlineSeconds{get;init;}=double.NaN;
}
public sealed record ScoExitEstimateObservation
{
public bool FreshBindingAndSourceGatesVerified{get;init;}
public double ElapsedSeconds{get;init;}=double.NaN;
public double SpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinarySpeedLimitMetresPerSecond{get;init;}=double.NaN;
public double NativeStepSeconds{get;init;}=double.NaN;
public double FlightAssistBlend{get;init;}=double.NaN;
public int ScoState{get;init;}=-1;
public double AllowedSpeedResidualFraction{get;init;}=double.NaN;
public double AllowedSpeedResidualMetresPerSecond{get;init;}=double.NaN;
}
public sealed record ScoExitEstimateCheck(bool WithinEstimatedDomain,bool OrdinaryHandoffReady,string Reason)
{
public bool QualifiedForControl=>false;
}
public sealed record ScoServoParameters
{
public bool PinnedSourceDomainVerified{get;init;}
public double BaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double BaseForwardAcceleration{get;init;}=double.NaN;
public double BaseBrakeAcceleration{get;init;}=double.NaN;
public double ResponseTimeSeconds{get;init;}=double.NaN;
public double JerkMetresPerSecondCubed{get;init;}=double.NaN;
public double ForwardTargetBiasMetresPerSecond{get;init;}=double.NaN;
public double ForwardThrottle{get;init;}=double.NaN;
}
public sealed record ScoServoStepInput
{
public double SpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinaryPhysicsMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double PreviousServoAccelerationNorm{get;init;}=double.NaN;
public double FlightAssistBlend{get;init;}=double.NaN;
public bool Cooling{get;init;}
public double CoolingDragPerSecond{get;init;}=double.NaN;
public double NativeStepSeconds{get;init;}=double.NaN;
}
public sealed record ScoServoStepResult(bool Known,string Reason)
{
public bool QualifiedForControl=>false;
public double NextSpeedMetresPerSecond{get;init;}=double.NaN;
public double NextServoAccelerationNorm{get;init;}=double.NaN;
public double AppliedAcceleration{get;init;}=double.NaN;
public double NativeBrakeClampAcceleration{get;init;}=double.NaN;
public double TargetSpeedMetresPerSecond{get;init;}=double.NaN;
public double EffectiveResponseTimeSeconds{get;init;}=double.NaN;
public double ApproximateDistanceMetres{get;init;}=double.NaN;
}
namespace XFEEDToolBox.Core.Navigation;
/// <summary>
/// Bounds supplied by a separately qualified original ordinary-supercruise
/// controller. Merely reading these numbers does not qualify a brake response.
/// Speeds are m/s, accelerations m/s², distances m and times seconds.
/// </summary>
public sealed record ScoBrakingInput
{
public bool OriginalParametersVerified{get;init;}
public bool OrdinaryArrivalControllerVerified{get;init;}
public bool OverspeedBrakeBoundVerified{get;init;}
public double RemainingDistanceMetres{get;init;}=double.NaN;
public double CurrentSpeedMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinarySpeedLimitUpperMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseOverspeedBrakeAcceleration{get;init;}=double.NaN;
public int OriginalBrakeScalingMode{get;init;}=-1;
// Modes 0/1 get weaker as the ordinary scale falls. Their lower bound
// must cover the whole exit, not just the current provider snapshot.
public double OriginalOrdinaryBrakeScaleLower{get;init;}=double.NaN;
public double OriginalExtraBrakeFactorLower{get;init;}=double.NaN;
public double OriginalArrivalWindowSeconds{get;init;}=double.NaN;
public double StopResponseSecondsUpper{get;init;}=double.NaN;
public double ActiveAccelerationMetresPerSecondSquaredUpper{get;init;}=double.NaN;
public double PositionUncertaintyMetres{get;init;}=double.NaN;
public double SafeDropDistanceMetres{get;init;}=double.NaN;
}
public sealed record ScoBrakingForecast(bool Known,string Reason)
{
public bool ContinueOvercharge{get;init;}
public double ResponseDistanceMetres{get;init;}=double.NaN;
public double SpeedAtOriginalStopMetresPerSecond{get;init;}=double.NaN;
public double OverspeedDistanceMetres{get;init;}=double.NaN;
public double OverspeedSeconds{get;init;}=double.NaN;
public double OrdinaryEntrySpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinaryArrivalEnvelopeMetres{get;init;}=double.NaN;
public double StopDistanceMetres{get;init;}=double.NaN;
public bool CoolingDragUsed=>false;
}
/// <summary>
/// Conditional integrals and an explicitly estimated, unconnected exit model.
/// Predict requires an independently established effective brake factor: the
/// native .25 extra brake is NOT an unconditional ordinary-supercruise force.
/// Cooling's additional -lambda*v force is deliberately ignored until its
/// duration is qualified; the current SCO speed is never held for the whole
/// approach. This is an opt-in mathematical forecast, not live qualification.
/// </summary>
public static class ScoBrakingPredictor
{
/// <summary>
/// An estimate for a bounded trial, never a live qualification. Cooling gets
/// no braking credit. Once Off and fully assisted, native mode 2 supplies
/// A(v,U) >= (A_base/V_base)*v ONLY WHILE v>U. The forward servo request has
/// the same lower bound above its bias-dependent floor; jerk starts at zero.
/// The budget maximises D+window*v across possible ordinary handoff speeds.
/// That union is not a physical exponential continued below the ordinary cap.
/// </summary>
public static ScoExitEstimate EstimateExit(ScoExitEstimateInput input)
{
if(!input.OriginalParametersVerified)return EstimateUnknown("original-parameters-unverified");
if(!input.ForwardServoDomainVerified)return EstimateUnknown("forward-servo-domain-unverified");
if(!input.OrdinaryArrivalControllerVerified)return EstimateUnknown("ordinary-arrival-controller-unverified");
if(input.OriginalBrakeScalingMode!=2)return EstimateUnknown("mode-2-required");
var v=input.CurrentSpeedMetresPerSecond;var maximum=input.OriginalBaseMaximumSpeedMetresPerSecond;
var brake=input.OriginalBaseBrakeAcceleration;var jerk=input.OriginalJerkMetresPerSecondCubed;
var throttle=input.ForwardThrottle;var bias=input.OriginalForwardTargetBiasMetresPerSecond;
var response=input.StopResponseSecondsUpper;var settle=input.EstimatedCoolingAndAssistSettleSeconds;
var physicsStep=input.MaximumPhysicsStepSeconds;var window=input.OriginalArrivalWindowSeconds;
if(!Positive(v)||!Positive(maximum)||v>=maximum||!Positive(brake)||!Positive(jerk)
||!Nonnegative(throttle)||throttle>.75||!Nonnegative(bias)||!Positive(input.OriginalResponseTimeSeconds)
||!Positive(input.CurrentOrdinarySpeedLimitMetresPerSecond)||input.CurrentOrdinarySpeedLimitMetresPerSecond>maximum
||!Nonnegative(response)||response>1||!Nonnegative(settle)||settle>30
||!Positive(physicsStep)||physicsStep>1||!Nonnegative(input.AdditionalResponseMarginSeconds)
||!Nonnegative(input.ActiveAccelerationUpper)||!Positive(window)||window>60
||!Positive(input.MinimumOrdinaryHandoffSpeedMetresPerSecond)
||!Nonnegative(input.DistanceUncertaintyMetres)||!Nonnegative(input.SafeDropDistanceMetres)
||!Nonnegative(input.RelativeDistanceMargin)||input.RelativeDistanceMargin>1
||!Positive(input.RemainingDistanceMetres))return EstimateUnknown("invalid-exit-estimate-input");
var initial=v+input.ActiveAccelerationUpper*response;
if(!Positive(initial)||initial>=maximum)return EstimateUnknown("below-base-speed-domain-exceeded");
if(v<=input.CurrentOrdinarySpeedLimitMetresPerSecond)return EstimateUnknown("forward-overspeed-domain-required");
var tauUpper=Math.Max(Math.Max(.5,input.OriginalResponseTimeSeconds),physicsStep);
var sigma=(1-throttle)/tauUpper;
// If the native clamp's rate is too large for the bias servo, half of
// sigma is a mathematical lower bound above 2*bias, not a fitted brake.
var rate=brake/maximum;
if(rate>=sigma)rate=sigma/2;
var floor=Math.Max(input.MinimumOrdinaryHandoffSpeedMetresPerSecond,bias*sigma/(sigma-rate));
if(!Positive(rate)||!Positive(floor)||floor>=initial
||input.CurrentOrdinarySpeedLimitMetresPerSecond<floor)return EstimateUnknown("ordinary-handoff-floor-unavailable");
var path=CreatePath(initial,rate,jerk,floor);
if(path is null)return EstimateUnknown("exit-budget-overflow");
// For any actual earlier handoff the budget is D(t)+W*v(t).
// In the exponential domain its derivative is v*(1-W*rate).
// In the jerk ramp it is v-W*J*t and has one positive root.
var selected=path.At(0);var selectedTime=0d;
void Consider(double time)
{
var point=path.At(time);
if(point.Distance+window*point.Speed>selected.Distance+window*selected.Speed)
{selected=point;selectedTime=time;}
}
Consider(path.FloorSeconds);
var critical=2*initial/jerk/(Math.Sqrt(window*window+2*initial/jerk)+window);
if(critical>0&&critical<Math.Min(path.RampSeconds,path.FloorSeconds))Consider(critical);
var responseDistance=v*response+.5*input.ActiveAccelerationUpper*response*response;
var noBrakeDelay=settle+physicsStep+input.AdditionalResponseMarginSeconds;
var transitionDistance=responseDistance+initial*noBrakeDelay;
var ordinaryEnvelope=window*selected.Speed;
var overspeedBudget=selected.Distance+ordinaryEnvelope;
var uncertainty=input.DistanceUncertaintyMetres+input.SafeDropDistanceMetres+input.RelativeDistanceMargin*overspeedBudget;
var total=Math.BitIncrement(transitionDistance+overspeedBudget+uncertainty);
if(!Positive(total))return EstimateUnknown("exit-budget-overflow");
return new(true,"estimated-no-cooling-credit-jerk-and-native-mode-2-handoff-union")
{
RemainingDistanceExceedsEstimate=input.RemainingDistanceMetres>total,
EstimatedStopDistanceMetres=total,NoBrakeTransitionDistanceMetres=transitionDistance,
OverspeedBudgetDistanceMetres=selected.Distance,OrdinaryArrivalEnvelopeMetres=ordinaryEnvelope,
MarginDistanceMetres=uncertainty,OffBudgetInitialSpeedMetresPerSecond=initial,
DecayRatePerSecond=rate,ServoDomainFloorMetresPerSecond=floor,
JerkRampSeconds=path.RampSeconds,DomainFloorSeconds=path.FloorSeconds,
BudgetHandoffSeconds=selectedTime,BudgetHandoffSpeedMetresPerSecond=selected.Speed,
NoBrakeDelaySeconds=response+noBrakeDelay,AssistSettleDeadlineSeconds=response+settle
};
}
/// <summary>
/// Guards a future bounded trial using fresh, independently verified native
/// observations. It sends no command. A real cap crossing ends this model;
/// ordinary cruise may then accelerate and remains the arrival controller's
/// job. A missed settle deadline or a slower brake invalidates the estimate.
/// </summary>
public static ScoExitEstimateCheck CheckExitEstimate(ScoExitEstimateInput input,ScoExitEstimate estimate,ScoExitEstimateObservation value)
{
if(!estimate.Known)return new(false,false,"estimate-unavailable");
if(!value.FreshBindingAndSourceGatesVerified)return new(false,false,"native-binding-or-source-gates-changed");
if(!Nonnegative(value.ElapsedSeconds)||!Nonnegative(value.SpeedMetresPerSecond)
||!Positive(value.OrdinarySpeedLimitMetresPerSecond)||value.OrdinarySpeedLimitMetresPerSecond>input.OriginalBaseMaximumSpeedMetresPerSecond
||!Positive(value.NativeStepSeconds)
||value.NativeStepSeconds>input.MaximumPhysicsStepSeconds
||!Nonnegative(value.FlightAssistBlend)||value.FlightAssistBlend>1
||!Nonnegative(value.AllowedSpeedResidualFraction)||value.AllowedSpeedResidualFraction>1
||!Nonnegative(value.AllowedSpeedResidualMetresPerSecond))
return new(false,false,"invalid-exit-observation");
if(value.ElapsedSeconds>input.StopResponseSecondsUpper&&value.ScoState is not 0 and not 3)
return new(false,false,"original-stop-not-observed");
if(value.FlightAssistBlend<1&&value.SpeedMetresPerSecond<value.OrdinarySpeedLimitMetresPerSecond)
return new(false,false,"mixed-assist-below-ordinary-cap");
if(value.ScoState==0&&value.FlightAssistBlend==1&&value.SpeedMetresPerSecond<=value.OrdinarySpeedLimitMetresPerSecond)
return new(true,true,"actual-ordinary-cap-handoff");
if(value.ElapsedSeconds>estimate.AssistSettleDeadlineSeconds&&(value.ScoState!=0||value.FlightAssistBlend!=1))
return new(false,false,"estimated-cooling-or-assist-settle-overrun");
if(value.SpeedMetresPerSecond<estimate.ServoDomainFloorMetresPerSecond
||value.OrdinarySpeedLimitMetresPerSecond<estimate.ServoDomainFloorMetresPerSecond)
return new(false,false,"actual-ordinary-handoff-required-at-domain-floor");
var path=CreatePath(estimate.OffBudgetInitialSpeedMetresPerSecond,estimate.DecayRatePerSecond,
input.OriginalJerkMetresPerSecondCubed,estimate.ServoDomainFloorMetresPerSecond);
if(path is null)return new(false,false,"exit-budget-overflow");
var elapsed=Math.Max(0,value.ElapsedSeconds-estimate.NoBrakeDelaySeconds);
if(elapsed>path.FloorSeconds)return new(false,false,"overspeed-domain-time-exceeded");
var upper=path.At(elapsed).Speed;
upper+=upper*value.AllowedSpeedResidualFraction+value.AllowedSpeedResidualMetresPerSecond;
return value.SpeedMetresPerSecond>upper
?new(false,false,"observed-brake-slower-than-estimate")
:new(true,false,"within-estimated-overspeed-domain");
}
/// <summary>
/// One scalar original-physics replay step. Caller must supply the actual
/// physics dt and current cap; sparse sample dt values cannot be summed to
/// reconstruct unobserved frames. No quarter-brake shortcut is used.
/// Valid for full FA, or mixed FA while forward speed stays above cap and
/// below base maximum (so the unassisted forward/extra-brake terms are zero).
/// </summary>
public static ScoServoStepResult StepOriginalServo(ScoServoParameters parameters,ScoServoStepInput value)
{
var speed=value.SpeedMetresPerSecond;var maximum=parameters.BaseMaximumSpeedMetresPerSecond;
var cap=value.OrdinaryPhysicsMaximumSpeedMetresPerSecond;var blend=value.FlightAssistBlend;
if(!parameters.PinnedSourceDomainVerified)return ServoUnknown("pinned-scalar-source-domain-unverified");
if(!Positive(maximum)||!Positive(parameters.BaseForwardAcceleration)||!Positive(parameters.BaseBrakeAcceleration)
||!Positive(parameters.ResponseTimeSeconds)||!Positive(parameters.JerkMetresPerSecondCubed)
||!Nonnegative(parameters.ForwardTargetBiasMetresPerSecond)||!Nonnegative(parameters.ForwardThrottle)||parameters.ForwardThrottle>.75
||!Nonnegative(speed)||speed>=maximum||!Positive(cap)||cap>maximum
||!Nonnegative(blend)||blend>1||!Nonnegative(value.PreviousServoAccelerationNorm)
||!Nonnegative(value.CoolingDragPerSecond)||!Positive(value.NativeStepSeconds)||value.NativeStepSeconds>1
||(blend<1&&speed<cap))return ServoUnknown("outside-original-scalar-domain");
var scale=cap/maximum;var ratio=speed/maximum;
var brake=parameters.BaseBrakeAcceleration*Math.Max(scale,ratio*Math.Max(1,Math.Sqrt(ratio/scale)));
var target=(cap-parameters.ForwardTargetBiasMetresPerSecond)*parameters.ForwardThrottle+parameters.ForwardTargetBiasMetresPerSecond;
var tau=Math.Max(.5,parameters.ResponseTimeSeconds*Math.Max(Math.Clamp(ratio,0,1)*4/3-1d/3,0));
var requested=(target-speed)/Math.Max(tau,value.NativeStepSeconds);
var clamped=Math.Clamp(requested,-brake,parameters.BaseForwardAcceleration*scale);
var norm=Math.Min(Math.Abs(clamped),value.PreviousServoAccelerationNorm+parameters.JerkMetresPerSecondCubed*value.NativeStepSeconds);
var acceleration=Math.CopySign(norm,clamped)*blend;
// The original lambda force is inside if(B>0), not multiplied by B.
if(value.Cooling&&blend>0)acceleration-=value.CoolingDragPerSecond*speed;
var next=Math.Max(0,speed+acceleration*value.NativeStepSeconds);
return new(true,"original-forward-servo-scalar-step")
{NextSpeedMetresPerSecond=next,NextServoAccelerationNorm=norm,AppliedAcceleration=acceleration,
NativeBrakeClampAcceleration=brake,TargetSpeedMetresPerSecond=target,EffectiveResponseTimeSeconds=tau,
ApproximateDistanceMetres=(speed+next)*.5*value.NativeStepSeconds};
}
private static ScoExitEstimate EstimateUnknown(string reason)=>new(false,reason);
private static ScoServoStepResult ServoUnknown(string reason)=>new(false,reason);
private sealed record BudgetPoint(double Speed,double Distance);
private sealed record BudgetPath(double Initial,double Rate,double Jerk,double Floor,double RampSeconds,double RampEndSpeed,double FloorSeconds)
{
public BudgetPoint At(double seconds)
{
if(seconds<=RampSeconds)return new(Initial-.5*Jerk*seconds*seconds,Initial*seconds-Jerk*seconds*seconds*seconds/6);
var speed=RampEndSpeed*Math.Exp(-Rate*(seconds-RampSeconds));
return new(speed,Initial*RampSeconds-Jerk*RampSeconds*RampSeconds*RampSeconds/6+(RampEndSpeed-speed)/Rate);
}
}
private static BudgetPath? CreatePath(double initial,double rate,double jerk,double floor)
{
// J*t = rate*(v0-.5*J*t²), evaluated without subtracting close roots.
var ramp=2*rate*initial/jerk/(1+Math.Sqrt(1+2*rate*rate*initial/jerk));
var end=initial-.5*jerk*ramp*ramp;
var floorTime=floor>=end?Math.Sqrt(2*(initial-floor)/jerk):ramp+Math.Log(end/floor)/rate;
return Positive(ramp)&&Positive(end)&&Nonnegative(floorTime)
?new(initial,rate,jerk,floor,ramp,end,floorTime):null;
}
public static ScoBrakingForecast Predict(ScoBrakingInput value)
{
if(!value.OriginalParametersVerified)return Unknown("original-parameters-unverified");
if(!value.OrdinaryArrivalControllerVerified)return Unknown("ordinary-arrival-controller-unverified");
if(!value.OverspeedBrakeBoundVerified)return Unknown("ordinary-overspeed-brake-bound-unverified");
var remaining=value.RemainingDistanceMetres;var current=value.CurrentSpeedMetresPerSecond;
var maximum=value.OriginalBaseMaximumSpeedMetresPerSecond;var ordinary=value.OrdinarySpeedLimitUpperMetresPerSecond;
var acceleration=value.OriginalBaseOverspeedBrakeAcceleration;var factor=value.OriginalExtraBrakeFactorLower;
var window=value.OriginalArrivalWindowSeconds;var response=value.StopResponseSecondsUpper;
var activeAcceleration=value.ActiveAccelerationMetresPerSecondSquaredUpper;
if(!Positive(remaining)||!Nonnegative(current)||!Positive(maximum)||!Positive(ordinary)||ordinary>maximum
||!Positive(acceleration)||!Positive(factor)||factor>.25||value.OriginalBrakeScalingMode is <0 or >2
||!Positive(window)||window>60||!Nonnegative(response)||response>1||!Nonnegative(activeAcceleration)
||!Nonnegative(value.PositionUncertaintyMetres)||!Nonnegative(value.SafeDropDistanceMetres))return Unknown("invalid-original-braking-input");
var speed=current+activeAcceleration*response;
var responseDistance=current*response+.5*activeAcceleration*response*response;
if(!Nonnegative(speed)||!Nonnegative(responseDistance))return Unknown("response-overflow");
var scale=ordinary/maximum;
if(!Positive(scale)||scale>1)return Unknown("ordinary-scale-underflow");
if(value.OriginalBrakeScalingMode is 0 or 1
&&(!Positive(value.OriginalOrdinaryBrakeScaleLower)||value.OriginalOrdinaryBrakeScaleLower>scale))
return Unknown("ordinary-brake-scale-lower-bound-unverified");
var entry=Math.Min(speed,ordinary);var overspeedDistance=0d;var overspeedSeconds=0d;
if(speed>ordinary)
{
var delta=speed-ordinary;
if(value.OriginalBrakeScalingMode==2)
{
// Above Vordinary, the original mode-2 maximum selects
// a(v)=factor*A_base*(v/V_base)^(3/2)/sqrt(scale).
// Its exact integrals avoid both time stepping and treating
// the much higher SCO speed as a constant coast speed.
var rootScale=Math.Sqrt(scale);var rootHigh=Math.Sqrt(speed/maximum);
var denominator=factor*acceleration*(rootHigh+rootScale);
overspeedDistance=2*maximum*rootScale*delta/denominator;
overspeedSeconds=2*delta/(denominator*rootHigh);
}
else
{
var lower=value.OriginalOrdinaryBrakeScaleLower;
var deceleration=factor*acceleration*(value.OriginalBrakeScalingMode==0?lower:Math.Sqrt(lower));
overspeedDistance=delta*(speed+ordinary)/(2*deceleration);
overspeedSeconds=delta/deceleration;
}
}
var ordinaryEnvelope=entry*window;
var total=responseDistance+overspeedDistance+ordinaryEnvelope+value.PositionUncertaintyMetres+value.SafeDropDistanceMetres;
if(!Nonnegative(overspeedDistance)||!Nonnegative(overspeedSeconds)||!Nonnegative(ordinaryEnvelope)||!Positive(total))return Unknown("braking-integration-overflow");
// Round away from a later stop when summing very different magnitudes.
total=Math.BitIncrement(total);
if(!Positive(total))return Unknown("braking-integration-overflow");
return new(true,"original-overspeed-integral-and-ordinary-arrival-envelope")
{
ContinueOvercharge=remaining>total,ResponseDistanceMetres=responseDistance,SpeedAtOriginalStopMetresPerSecond=speed,
OverspeedDistanceMetres=overspeedDistance,OverspeedSeconds=overspeedSeconds,OrdinaryEntrySpeedMetresPerSecond=entry,
OrdinaryArrivalEnvelopeMetres=ordinaryEnvelope,StopDistanceMetres=total
};
}
private static ScoBrakingForecast Unknown(string reason)=>new(false,reason);
private static bool Positive(double value)=>double.IsFinite(value)&&value>0;
private static bool Nonnegative(double value)=>double.IsFinite(value)&&value>=0;
}
/// <summary>
/// All units SI. Verified domain means fresh pinned identity/mode 2, straight
/// positive local-Z, u<=.75, unit translation scale, disabled external motion
/// and target-velocity correction, full negative clamp, and an ordinary arrival
/// controller ready to accept actual v<=cap. Future Cooling/assist settlement
/// is deliberately an estimate with an online deadline, never a proven bound.
/// </summary>
public sealed record ScoExitEstimateInput
{
public bool OriginalParametersVerified{get;init;}
public bool ForwardServoDomainVerified{get;init;}
public bool OrdinaryArrivalControllerVerified{get;init;}
public int OriginalBrakeScalingMode{get;init;}=-1;
public double RemainingDistanceMetres{get;init;}=double.NaN;
public double CurrentSpeedMetresPerSecond{get;init;}=double.NaN;
public double CurrentOrdinarySpeedLimitMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double OriginalBaseBrakeAcceleration{get;init;}=double.NaN;
public double OriginalResponseTimeSeconds{get;init;}=double.NaN;
public double OriginalJerkMetresPerSecondCubed{get;init;}=double.NaN;
public double OriginalForwardTargetBiasMetresPerSecond{get;init;}=double.NaN;
public double ForwardThrottle{get;init;}=double.NaN;
public double StopResponseSecondsUpper{get;init;}=double.NaN;
public double ActiveAccelerationUpper{get;init;}=double.NaN;
public double EstimatedCoolingAndAssistSettleSeconds{get;init;}=double.NaN;
public double MaximumPhysicsStepSeconds{get;init;}=double.NaN;
public double AdditionalResponseMarginSeconds{get;init;}=double.NaN;
public double MinimumOrdinaryHandoffSpeedMetresPerSecond{get;init;}=double.NaN;
public double OriginalArrivalWindowSeconds{get;init;}=double.NaN;
public double RelativeDistanceMargin{get;init;}=double.NaN;
public double DistanceUncertaintyMetres{get;init;}=double.NaN;
public double SafeDropDistanceMetres{get;init;}=double.NaN;
}
public sealed record ScoExitEstimate(bool Known,string Reason)
{
public bool Estimated=>true;
public bool QualifiedForControl=>false;
public bool CoolingDragUsed=>false;
public bool RequiresActualOrdinaryHandoff=>true;
public bool RemainingDistanceExceedsEstimate{get;init;}
public double EstimatedStopDistanceMetres{get;init;}=double.NaN;
public double NoBrakeTransitionDistanceMetres{get;init;}=double.NaN;
public double OverspeedBudgetDistanceMetres{get;init;}=double.NaN;
public double OrdinaryArrivalEnvelopeMetres{get;init;}=double.NaN;
public double MarginDistanceMetres{get;init;}=double.NaN;
public double OffBudgetInitialSpeedMetresPerSecond{get;init;}=double.NaN;
public double DecayRatePerSecond{get;init;}=double.NaN;
public double ServoDomainFloorMetresPerSecond{get;init;}=double.NaN;
public double JerkRampSeconds{get;init;}=double.NaN;
public double DomainFloorSeconds{get;init;}=double.NaN;
public double BudgetHandoffSeconds{get;init;}=double.NaN;
public double BudgetHandoffSpeedMetresPerSecond{get;init;}=double.NaN;
public double NoBrakeDelaySeconds{get;init;}=double.NaN;
public double AssistSettleDeadlineSeconds{get;init;}=double.NaN;
}
public sealed record ScoExitEstimateObservation
{
public bool FreshBindingAndSourceGatesVerified{get;init;}
public double ElapsedSeconds{get;init;}=double.NaN;
public double SpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinarySpeedLimitMetresPerSecond{get;init;}=double.NaN;
public double NativeStepSeconds{get;init;}=double.NaN;
public double FlightAssistBlend{get;init;}=double.NaN;
public int ScoState{get;init;}=-1;
public double AllowedSpeedResidualFraction{get;init;}=double.NaN;
public double AllowedSpeedResidualMetresPerSecond{get;init;}=double.NaN;
}
public sealed record ScoExitEstimateCheck(bool WithinEstimatedDomain,bool OrdinaryHandoffReady,string Reason)
{
public bool QualifiedForControl=>false;
}
public sealed record ScoServoParameters
{
public bool PinnedSourceDomainVerified{get;init;}
public double BaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double BaseForwardAcceleration{get;init;}=double.NaN;
public double BaseBrakeAcceleration{get;init;}=double.NaN;
public double ResponseTimeSeconds{get;init;}=double.NaN;
public double JerkMetresPerSecondCubed{get;init;}=double.NaN;
public double ForwardTargetBiasMetresPerSecond{get;init;}=double.NaN;
public double ForwardThrottle{get;init;}=double.NaN;
}
public sealed record ScoServoStepInput
{
public double SpeedMetresPerSecond{get;init;}=double.NaN;
public double OrdinaryPhysicsMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
public double PreviousServoAccelerationNorm{get;init;}=double.NaN;
public double FlightAssistBlend{get;init;}=double.NaN;
public bool Cooling{get;init;}
public double CoolingDragPerSecond{get;init;}=double.NaN;
public double NativeStepSeconds{get;init;}=double.NaN;
}
public sealed record ScoServoStepResult(bool Known,string Reason)
{
public bool QualifiedForControl=>false;
public double NextSpeedMetresPerSecond{get;init;}=double.NaN;
public double NextServoAccelerationNorm{get;init;}=double.NaN;
public double AppliedAcceleration{get;init;}=double.NaN;
public double NativeBrakeClampAcceleration{get;init;}=double.NaN;
public double TargetSpeedMetresPerSecond{get;init;}=double.NaN;
public double EffectiveResponseTimeSeconds{get;init;}=double.NaN;
public double ApproximateDistanceMetres{get;init;}=double.NaN;
}