namespace XFEEDToolBox.Core.Navigation; /// /// 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. /// 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; } /// /// 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. /// public static class ScoBrakingPredictor { /// /// 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. /// 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.CurrentOrdinarySpeedLimitMetresPerSecondselected.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&&criticaltotal, 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 }; } /// /// 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. /// 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.SpeedMetresPerSecondestimate.AssistSettleDeadlineSeconds&&(value.ScoState!=0||value.FlightAssistBlend!=1)) return new(false,false,"estimated-cooling-or-assist-settle-overrun"); if(value.SpeedMetresPerSecondpath.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"); } /// /// 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). /// 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&&speed0), 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; } /// /// 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. /// 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; }