XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEEDToolBox

Prepare conditional SCO exit estimates and observed curve replay

ea24078
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

3 个文件 +461 -2
Modified XFEEDToolBox.Core/Navigation/ScoBrakingPredictor.cs +283 -2
@@ -41,14 +41,191 @@ public sealed record ScoBrakingForecast(bool Known,string Reason)
41 41 }
42 42
43 43 /// <summary>
44 /// Integrates the pinned 1B2D8F0 ordinary overspeed law after removing the
45 /// SCO multiplier, then joins the verified ordinary arrival-window controller.
44 /// Conditional integrals and an explicitly estimated, unconnected exit model.
45 /// Predict requires an independently established effective brake factor: the
46 /// native .25 extra brake is NOT an unconditional ordinary-supercruise force.
46 47 /// Cooling's additional -lambda*v force is deliberately ignored until its
47 48 /// duration is qualified; the current SCO speed is never held for the whole
48 49 /// approach. This is an opt-in mathematical forecast, not live qualification.
49 50 /// </summary>
50 51 public static class ScoBrakingPredictor
51 52 {
53 /// <summary>
54 /// An estimate for a bounded trial, never a live qualification. Cooling gets
55 /// no braking credit. Once Off and fully assisted, native mode 2 supplies
56 /// A(v,U) >= (A_base/V_base)*v ONLY WHILE v>U. The forward servo request has
57 /// the same lower bound above its bias-dependent floor; jerk starts at zero.
58 /// The budget maximises D+window*v across possible ordinary handoff speeds.
59 /// That union is not a physical exponential continued below the ordinary cap.
60 /// </summary>
61 public static ScoExitEstimate EstimateExit(ScoExitEstimateInput input)
62 {
63 if(!input.OriginalParametersVerified)return EstimateUnknown("original-parameters-unverified");
64 if(!input.ForwardServoDomainVerified)return EstimateUnknown("forward-servo-domain-unverified");
65 if(!input.OrdinaryArrivalControllerVerified)return EstimateUnknown("ordinary-arrival-controller-unverified");
66 if(input.OriginalBrakeScalingMode!=2)return EstimateUnknown("mode-2-required");
67 var v=input.CurrentSpeedMetresPerSecond;var maximum=input.OriginalBaseMaximumSpeedMetresPerSecond;
68 var brake=input.OriginalBaseBrakeAcceleration;var jerk=input.OriginalJerkMetresPerSecondCubed;
69 var throttle=input.ForwardThrottle;var bias=input.OriginalForwardTargetBiasMetresPerSecond;
70 var response=input.StopResponseSecondsUpper;var settle=input.EstimatedCoolingAndAssistSettleSeconds;
71 var physicsStep=input.MaximumPhysicsStepSeconds;var window=input.OriginalArrivalWindowSeconds;
72 if(!Positive(v)||!Positive(maximum)||v>=maximum||!Positive(brake)||!Positive(jerk)
73 ||!Nonnegative(throttle)||throttle>.75||!Nonnegative(bias)||!Positive(input.OriginalResponseTimeSeconds)
74 ||!Positive(input.CurrentOrdinarySpeedLimitMetresPerSecond)||input.CurrentOrdinarySpeedLimitMetresPerSecond>maximum
75 ||!Nonnegative(response)||response>1||!Nonnegative(settle)||settle>30
76 ||!Positive(physicsStep)||physicsStep>1||!Nonnegative(input.AdditionalResponseMarginSeconds)
77 ||!Nonnegative(input.ActiveAccelerationUpper)||!Positive(window)||window>60
78 ||!Positive(input.MinimumOrdinaryHandoffSpeedMetresPerSecond)
79 ||!Nonnegative(input.DistanceUncertaintyMetres)||!Nonnegative(input.SafeDropDistanceMetres)
80 ||!Nonnegative(input.RelativeDistanceMargin)||input.RelativeDistanceMargin>1
81 ||!Positive(input.RemainingDistanceMetres))return EstimateUnknown("invalid-exit-estimate-input");
82
83 var initial=v+input.ActiveAccelerationUpper*response;
84 if(!Positive(initial)||initial>=maximum)return EstimateUnknown("below-base-speed-domain-exceeded");
85 if(v<=input.CurrentOrdinarySpeedLimitMetresPerSecond)return EstimateUnknown("forward-overspeed-domain-required");
86 var tauUpper=Math.Max(Math.Max(.5,input.OriginalResponseTimeSeconds),physicsStep);
87 var sigma=(1-throttle)/tauUpper;
88 // If the native clamp's rate is too large for the bias servo, half of
89 // sigma is a mathematical lower bound above 2*bias, not a fitted brake.
90 var rate=brake/maximum;
91 if(rate>=sigma)rate=sigma/2;
92 var floor=Math.Max(input.MinimumOrdinaryHandoffSpeedMetresPerSecond,bias*sigma/(sigma-rate));
93 if(!Positive(rate)||!Positive(floor)||floor>=initial
94 ||input.CurrentOrdinarySpeedLimitMetresPerSecond<floor)return EstimateUnknown("ordinary-handoff-floor-unavailable");
95
96 var path=CreatePath(initial,rate,jerk,floor);
97 if(path is null)return EstimateUnknown("exit-budget-overflow");
98 // For any actual earlier handoff the budget is D(t)+W*v(t).
99 // In the exponential domain its derivative is v*(1-W*rate).
100 // In the jerk ramp it is v-W*J*t and has one positive root.
101 var selected=path.At(0);var selectedTime=0d;
102 void Consider(double time)
103 {
104 var point=path.At(time);
105 if(point.Distance+window*point.Speed>selected.Distance+window*selected.Speed)
106 {selected=point;selectedTime=time;}
107 }
108 Consider(path.FloorSeconds);
109 var critical=2*initial/jerk/(Math.Sqrt(window*window+2*initial/jerk)+window);
110 if(critical>0&&critical<Math.Min(path.RampSeconds,path.FloorSeconds))Consider(critical);
111
112 var responseDistance=v*response+.5*input.ActiveAccelerationUpper*response*response;
113 var noBrakeDelay=settle+physicsStep+input.AdditionalResponseMarginSeconds;
114 var transitionDistance=responseDistance+initial*noBrakeDelay;
115 var ordinaryEnvelope=window*selected.Speed;
116 var overspeedBudget=selected.Distance+ordinaryEnvelope;
117 var uncertainty=input.DistanceUncertaintyMetres+input.SafeDropDistanceMetres+input.RelativeDistanceMargin*overspeedBudget;
118 var total=Math.BitIncrement(transitionDistance+overspeedBudget+uncertainty);
119 if(!Positive(total))return EstimateUnknown("exit-budget-overflow");
120 return new(true,"estimated-no-cooling-credit-jerk-and-native-mode-2-handoff-union")
121 {
122 RemainingDistanceExceedsEstimate=input.RemainingDistanceMetres>total,
123 EstimatedStopDistanceMetres=total,NoBrakeTransitionDistanceMetres=transitionDistance,
124 OverspeedBudgetDistanceMetres=selected.Distance,OrdinaryArrivalEnvelopeMetres=ordinaryEnvelope,
125 MarginDistanceMetres=uncertainty,OffBudgetInitialSpeedMetresPerSecond=initial,
126 DecayRatePerSecond=rate,ServoDomainFloorMetresPerSecond=floor,
127 JerkRampSeconds=path.RampSeconds,DomainFloorSeconds=path.FloorSeconds,
128 BudgetHandoffSeconds=selectedTime,BudgetHandoffSpeedMetresPerSecond=selected.Speed,
129 NoBrakeDelaySeconds=response+noBrakeDelay,AssistSettleDeadlineSeconds=response+settle
130 };
131 }
132
133 /// <summary>
134 /// Guards a future bounded trial using fresh, independently verified native
135 /// observations. It sends no command. A real cap crossing ends this model;
136 /// ordinary cruise may then accelerate and remains the arrival controller's
137 /// job. A missed settle deadline or a slower brake invalidates the estimate.
138 /// </summary>
139 public static ScoExitEstimateCheck CheckExitEstimate(ScoExitEstimateInput input,ScoExitEstimate estimate,ScoExitEstimateObservation value)
140 {
141 if(!estimate.Known)return new(false,false,"estimate-unavailable");
142 if(!value.FreshBindingAndSourceGatesVerified)return new(false,false,"native-binding-or-source-gates-changed");
143 if(!Nonnegative(value.ElapsedSeconds)||!Nonnegative(value.SpeedMetresPerSecond)
144 ||!Positive(value.OrdinarySpeedLimitMetresPerSecond)||value.OrdinarySpeedLimitMetresPerSecond>input.OriginalBaseMaximumSpeedMetresPerSecond
145 ||!Positive(value.NativeStepSeconds)
146 ||value.NativeStepSeconds>input.MaximumPhysicsStepSeconds
147 ||!Nonnegative(value.FlightAssistBlend)||value.FlightAssistBlend>1
148 ||!Nonnegative(value.AllowedSpeedResidualFraction)||value.AllowedSpeedResidualFraction>1
149 ||!Nonnegative(value.AllowedSpeedResidualMetresPerSecond))
150 return new(false,false,"invalid-exit-observation");
151 if(value.ElapsedSeconds>input.StopResponseSecondsUpper&&value.ScoState is not 0 and not 3)
152 return new(false,false,"original-stop-not-observed");
153 if(value.ScoState==0&&value.FlightAssistBlend==1&&value.SpeedMetresPerSecond<=value.OrdinarySpeedLimitMetresPerSecond)
154 return new(true,true,"actual-ordinary-cap-handoff");
155 if(value.ElapsedSeconds>estimate.AssistSettleDeadlineSeconds&&(value.ScoState!=0||value.FlightAssistBlend!=1))
156 return new(false,false,"estimated-cooling-or-assist-settle-overrun");
157 if(value.SpeedMetresPerSecond<estimate.ServoDomainFloorMetresPerSecond
158 ||value.OrdinarySpeedLimitMetresPerSecond<estimate.ServoDomainFloorMetresPerSecond)
159 return new(false,false,"actual-ordinary-handoff-required-at-domain-floor");
160 var path=CreatePath(estimate.OffBudgetInitialSpeedMetresPerSecond,estimate.DecayRatePerSecond,
161 input.OriginalJerkMetresPerSecondCubed,estimate.ServoDomainFloorMetresPerSecond);
162 if(path is null)return new(false,false,"exit-budget-overflow");
163 var elapsed=Math.Max(0,value.ElapsedSeconds-estimate.NoBrakeDelaySeconds);
164 if(elapsed>path.FloorSeconds)return new(false,false,"overspeed-domain-time-exceeded");
165 var upper=path.At(elapsed).Speed;
166 upper+=upper*value.AllowedSpeedResidualFraction+value.AllowedSpeedResidualMetresPerSecond;
167 return value.SpeedMetresPerSecond>upper
168 ?new(false,false,"observed-brake-slower-than-estimate")
169 :new(true,false,"within-estimated-overspeed-domain");
170 }
171
172 /// <summary>
173 /// One scalar original-physics replay step. Caller must supply the actual
174 /// physics dt and current cap; sparse sample dt values cannot be summed to
175 /// reconstruct unobserved frames. No quarter-brake shortcut is used.
176 /// Valid for full FA, or mixed FA while forward speed stays above cap and
177 /// below base maximum (so the unassisted forward/extra-brake terms are zero).
178 /// </summary>
179 public static ScoServoStepResult StepOriginalServo(ScoServoParameters parameters,ScoServoStepInput value)
180 {
181 var speed=value.SpeedMetresPerSecond;var maximum=parameters.BaseMaximumSpeedMetresPerSecond;
182 var cap=value.OrdinaryPhysicsMaximumSpeedMetresPerSecond;var blend=value.FlightAssistBlend;
183 if(!parameters.PinnedSourceDomainVerified)return ServoUnknown("pinned-scalar-source-domain-unverified");
184 if(!Positive(maximum)||!Positive(parameters.BaseForwardAcceleration)||!Positive(parameters.BaseBrakeAcceleration)
185 ||!Positive(parameters.ResponseTimeSeconds)||!Positive(parameters.JerkMetresPerSecondCubed)
186 ||!Nonnegative(parameters.ForwardTargetBiasMetresPerSecond)||!Nonnegative(parameters.ForwardThrottle)||parameters.ForwardThrottle>.75
187 ||!Nonnegative(speed)||speed>=maximum||!Positive(cap)||cap>maximum
188 ||!Nonnegative(blend)||blend>1||!Nonnegative(value.PreviousServoAccelerationNorm)
189 ||!Nonnegative(value.CoolingDragPerSecond)||!Positive(value.NativeStepSeconds)||value.NativeStepSeconds>1
190 ||(blend<1&&speed<cap))return ServoUnknown("outside-original-scalar-domain");
191 var scale=cap/maximum;var ratio=speed/maximum;
192 var brake=parameters.BaseBrakeAcceleration*Math.Max(scale,ratio*Math.Max(1,Math.Sqrt(ratio/scale)));
193 var target=(cap-parameters.ForwardTargetBiasMetresPerSecond)*parameters.ForwardThrottle+parameters.ForwardTargetBiasMetresPerSecond;
194 var tau=Math.Max(.5,parameters.ResponseTimeSeconds*Math.Max(Math.Clamp(ratio,0,1)*4/3-1d/3,0));
195 var requested=(target-speed)/Math.Max(tau,value.NativeStepSeconds);
196 var clamped=Math.Clamp(requested,-brake,parameters.BaseForwardAcceleration*scale);
197 var norm=Math.Min(Math.Abs(clamped),value.PreviousServoAccelerationNorm+parameters.JerkMetresPerSecondCubed*value.NativeStepSeconds);
198 var acceleration=Math.CopySign(norm,clamped)*blend;
199 // The original lambda force is inside if(B>0), not multiplied by B.
200 if(value.Cooling&&blend>0)acceleration-=value.CoolingDragPerSecond*speed;
201 var next=Math.Max(0,speed+acceleration*value.NativeStepSeconds);
202 return new(true,"original-forward-servo-scalar-step")
203 {NextSpeedMetresPerSecond=next,NextServoAccelerationNorm=norm,AppliedAcceleration=acceleration,
204 NativeBrakeClampAcceleration=brake,TargetSpeedMetresPerSecond=target,EffectiveResponseTimeSeconds=tau,
205 ApproximateDistanceMetres=(speed+next)*.5*value.NativeStepSeconds};
206 }
207
208 private static ScoExitEstimate EstimateUnknown(string reason)=>new(false,reason);
209 private static ScoServoStepResult ServoUnknown(string reason)=>new(false,reason);
210 private sealed record BudgetPoint(double Speed,double Distance);
211 private sealed record BudgetPath(double Initial,double Rate,double Jerk,double Floor,double RampSeconds,double RampEndSpeed,double FloorSeconds)
212 {
213 public BudgetPoint At(double seconds)
214 {
215 if(seconds<=RampSeconds)return new(Initial-.5*Jerk*seconds*seconds,Initial*seconds-Jerk*seconds*seconds*seconds/6);
216 var speed=RampEndSpeed*Math.Exp(-Rate*(seconds-RampSeconds));
217 return new(speed,Initial*RampSeconds-Jerk*RampSeconds*RampSeconds*RampSeconds/6+(RampEndSpeed-speed)/Rate);
218 }
219 }
220 private static BudgetPath? CreatePath(double initial,double rate,double jerk,double floor)
221 {
222 // J*t = rate*(v0-.5*J*t²), evaluated without subtracting close roots.
223 var ramp=2*rate*initial/jerk/(1+Math.Sqrt(1+2*rate*rate*initial/jerk));
224 var end=initial-.5*jerk*ramp*ramp;
225 var floorTime=floor>=end?Math.Sqrt(2*(initial-floor)/jerk):ramp+Math.Log(end/floor)/rate;
226 return Positive(ramp)&&Positive(end)&&Nonnegative(floorTime)
227 ?new(initial,rate,jerk,floor,ramp,end,floorTime):null;
228 }
52 229 public static ScoBrakingForecast Predict(ScoBrakingInput value)
53 230 {
54 231 if(!value.OriginalParametersVerified)return Unknown("original-parameters-unverified");
@@ -111,3 +288,107 @@ public static class ScoBrakingPredictor
111 288 private static bool Positive(double value)=>double.IsFinite(value)&&value>0;
112 289 private static bool Nonnegative(double value)=>double.IsFinite(value)&&value>=0;
113 290 }
291
292 /// <summary>
293 /// All units SI. Verified domain means fresh pinned identity/mode 2, straight
294 /// positive local-Z, u<=.75, unit translation scale, disabled external motion
295 /// and target-velocity correction, full negative clamp, and an ordinary arrival
296 /// controller ready to accept actual v<=cap. Future Cooling/assist settlement
297 /// is deliberately an estimate with an online deadline, never a proven bound.
298 /// </summary>
299 public sealed record ScoExitEstimateInput
300 {
301 public bool OriginalParametersVerified{get;init;}
302 public bool ForwardServoDomainVerified{get;init;}
303 public bool OrdinaryArrivalControllerVerified{get;init;}
304 public int OriginalBrakeScalingMode{get;init;}=-1;
305 public double RemainingDistanceMetres{get;init;}=double.NaN;
306 public double CurrentSpeedMetresPerSecond{get;init;}=double.NaN;
307 public double CurrentOrdinarySpeedLimitMetresPerSecond{get;init;}=double.NaN;
308 public double OriginalBaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
309 public double OriginalBaseBrakeAcceleration{get;init;}=double.NaN;
310 public double OriginalResponseTimeSeconds{get;init;}=double.NaN;
311 public double OriginalJerkMetresPerSecondCubed{get;init;}=double.NaN;
312 public double OriginalForwardTargetBiasMetresPerSecond{get;init;}=double.NaN;
313 public double ForwardThrottle{get;init;}=double.NaN;
314 public double StopResponseSecondsUpper{get;init;}=double.NaN;
315 public double ActiveAccelerationUpper{get;init;}=double.NaN;
316 public double EstimatedCoolingAndAssistSettleSeconds{get;init;}=double.NaN;
317 public double MaximumPhysicsStepSeconds{get;init;}=double.NaN;
318 public double AdditionalResponseMarginSeconds{get;init;}=double.NaN;
319 public double MinimumOrdinaryHandoffSpeedMetresPerSecond{get;init;}=double.NaN;
320 public double OriginalArrivalWindowSeconds{get;init;}=double.NaN;
321 public double RelativeDistanceMargin{get;init;}=double.NaN;
322 public double DistanceUncertaintyMetres{get;init;}=double.NaN;
323 public double SafeDropDistanceMetres{get;init;}=double.NaN;
324 }
325 public sealed record ScoExitEstimate(bool Known,string Reason)
326 {
327 public bool Estimated=>true;
328 public bool QualifiedForControl=>false;
329 public bool CoolingDragUsed=>false;
330 public bool RequiresActualOrdinaryHandoff=>true;
331 public bool RemainingDistanceExceedsEstimate{get;init;}
332 public double EstimatedStopDistanceMetres{get;init;}=double.NaN;
333 public double NoBrakeTransitionDistanceMetres{get;init;}=double.NaN;
334 public double OverspeedBudgetDistanceMetres{get;init;}=double.NaN;
335 public double OrdinaryArrivalEnvelopeMetres{get;init;}=double.NaN;
336 public double MarginDistanceMetres{get;init;}=double.NaN;
337 public double OffBudgetInitialSpeedMetresPerSecond{get;init;}=double.NaN;
338 public double DecayRatePerSecond{get;init;}=double.NaN;
339 public double ServoDomainFloorMetresPerSecond{get;init;}=double.NaN;
340 public double JerkRampSeconds{get;init;}=double.NaN;
341 public double DomainFloorSeconds{get;init;}=double.NaN;
342 public double BudgetHandoffSeconds{get;init;}=double.NaN;
343 public double BudgetHandoffSpeedMetresPerSecond{get;init;}=double.NaN;
344 public double NoBrakeDelaySeconds{get;init;}=double.NaN;
345 public double AssistSettleDeadlineSeconds{get;init;}=double.NaN;
346 }
347 public sealed record ScoExitEstimateObservation
348 {
349 public bool FreshBindingAndSourceGatesVerified{get;init;}
350 public double ElapsedSeconds{get;init;}=double.NaN;
351 public double SpeedMetresPerSecond{get;init;}=double.NaN;
352 public double OrdinarySpeedLimitMetresPerSecond{get;init;}=double.NaN;
353 public double NativeStepSeconds{get;init;}=double.NaN;
354 public double FlightAssistBlend{get;init;}=double.NaN;
355 public int ScoState{get;init;}=-1;
356 public double AllowedSpeedResidualFraction{get;init;}=double.NaN;
357 public double AllowedSpeedResidualMetresPerSecond{get;init;}=double.NaN;
358 }
359 public sealed record ScoExitEstimateCheck(bool WithinEstimatedDomain,bool OrdinaryHandoffReady,string Reason)
360 {
361 public bool QualifiedForControl=>false;
362 }
363 public sealed record ScoServoParameters
364 {
365 public bool PinnedSourceDomainVerified{get;init;}
366 public double BaseMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
367 public double BaseForwardAcceleration{get;init;}=double.NaN;
368 public double BaseBrakeAcceleration{get;init;}=double.NaN;
369 public double ResponseTimeSeconds{get;init;}=double.NaN;
370 public double JerkMetresPerSecondCubed{get;init;}=double.NaN;
371 public double ForwardTargetBiasMetresPerSecond{get;init;}=double.NaN;
372 public double ForwardThrottle{get;init;}=double.NaN;
373 }
374 public sealed record ScoServoStepInput
375 {
376 public double SpeedMetresPerSecond{get;init;}=double.NaN;
377 public double OrdinaryPhysicsMaximumSpeedMetresPerSecond{get;init;}=double.NaN;
378 public double PreviousServoAccelerationNorm{get;init;}=double.NaN;
379 public double FlightAssistBlend{get;init;}=double.NaN;
380 public bool Cooling{get;init;}
381 public double CoolingDragPerSecond{get;init;}=double.NaN;
382 public double NativeStepSeconds{get;init;}=double.NaN;
383 }
384 public sealed record ScoServoStepResult(bool Known,string Reason)
385 {
386 public bool QualifiedForControl=>false;
387 public double NextSpeedMetresPerSecond{get;init;}=double.NaN;
388 public double NextServoAccelerationNorm{get;init;}=double.NaN;
389 public double AppliedAcceleration{get;init;}=double.NaN;
390 public double NativeBrakeClampAcceleration{get;init;}=double.NaN;
391 public double TargetSpeedMetresPerSecond{get;init;}=double.NaN;
392 public double EffectiveResponseTimeSeconds{get;init;}=double.NaN;
393 public double ApproximateDistanceMetres{get;init;}=double.NaN;
394 }
Added XFEEDToolBox.Test/Fixtures/sco-exit-observed.json +1 -0
@@ -0,0 +1 @@
1 {"source":"passive-sco-j-61268-2/curve.jsonl","qualifiedForControl":false,"note":"Retrospective 16.24Hz sample; future cap supplied. J/bias from later same-ship pinned read; initial servo norm unobserved.","baseMaximumSpeed":599999971328,"baseBrake":89999998976,"baseForward":44999999488,"baseResponseTime":1,"laterObservedJerk":10000000000.0,"laterObservedBias":30000.0,"throttle":0.75,"columns":["seconds","speed","physicsCap","arrivalCap","blend","state","distance","brake","sparseNativeDt"],"frames":[[0.0,22455635968,4321355264,4323640320,0.4370463490486145,3,288924784780.145,7678374912,0.004827617201954126],[0.064,21880561664,4349941248,4352165888,0.465157687664032,3,287513078247.4162,7361007616,0.004834516905248165],[0.123,21320396800,4376584192,4378291200,0.4990660548210144,3,286219852608.0167,7058567680,0.0041904267854988575],[0.187,20729446400,4403280384,4405022720,0.5475240349769592,3,284897286007.8741,6746595328,0.004668420180678368],[0.247,20140449792,4428179456,4430077440,0.6304564476013184,3,283657071030.9366,6442914816,0.004522322211414576],[0.312,19414511616,4454369792,4456219648,1,3,282363098612.9711,6079779840,0.0040684291161596775],[0.373,18671611904,4477683200,4479257088,1,3,281221983485.43475,5719226880,0.004268725868314505],[0.435,17923782656,4500343808,4501880832,1,3,280101823421.52423,5365533696,0.00513271102681756],[0.498,17199431680,4522507264,4524391424,1,3,278986766046.2631,5031214080,0.004566221032291651],[0.561,16492150784,4544377344,4545649152,1,3,277934064954.30695,4712702464,0.004867115989327431],[0.626,15820173312,4565378560,4566507008,1,3,276900185263.559,4417430528,0.004869616124778986],[0.689,15185793024,4585404928,4586568704,1,3,275906201389.99524,4145325568,0.003793535055592656],[0.75,14611865600,4603699712,4604851200,1,3,275000125601.6411,3904777472,0.0040023308247327805],[0.814,14036283392,4622206464,4623202304,1,3,274090534413.41992,3668975104,0.003512839088216424],[0.875,13513663488,4639163904,4640267264,1,3,273244000766.16565,3459641344,0.0037254360504448414],[0.935,13033279488,4654883840,4656106496,1,3,272459120478.95023,3271277824,0.00401263078674674],[0.967,12787527680,4662964224,4664246784,1,3,272055403088.95544,3176436224,0.004724117927253246],[1.032,12302241792,4679039488,4680382464,1,0,271254828050.98453,2992190976,0.005044112913310528],[1.122,12031330304,4701560832,4702543360,1,0,270155790548.13034,2886959872,0.004875115118920803],[1.157,11929190400,4710140928,4711088128,1,0,269731822054.13666,2847677184,0.004148827865719795],[1.22,11752758272,4725059584,4726054400,1,0,268989013926.30743,2780336384,0.004113729111850262],[1.279,11586598912,4739232768,4740107264,1,0,268291971941.47534,2717510400,0.0034971400164067745],[1.343,11414718464,4754015232,4754937344,1,0,267555464664.03116,2653131776,0.0040813288651406765],[1.407,11244748800,4768771072,4769657344,1,0,266824754309.7738,2590076928,0.0037458359729498625],[1.47,11087448064,4782555648,4783501824,1,0,266137574901.11017,2532262144,0.004223926924169064],[1.529,10938345472,4795715072,4796816384,1,0,265476582677.41168,2477947136,0.003910833038389683],[1.594,10780327936,4809783296,4810728960,1,0,264785848571.48657,2420898048,0.004693218972533941],[1.654,10632247296,4823083008,4823861248,1,0,264133382907.02173,2367917056,0.00366913597099483],[1.72,10481291264,4836752384,4837662720,1,0,263448080168.45663,2314390016,0.004740619100630283],[1.781,10337564672,4849881088,4850731008,1,0,262798768043.75952,2263878400,0.003945432137697935],[1.842,10200025088,4862530048,4863344128,1,0,262172321794.67197,2215960320,0.003635937115177512],[1.905,10064481280,4875106304,4875979776,1,0,261544863047.70163,2169133568,0.003989730961620808],[1.971,9923308544,4888313856,4889103360,1,0,260892391214.29675,2120784256,0.004108828958123922],[2.03,9800919040,4899857408,4900850688,1,0,260308375962.13733,2079216640,0.004492922220379114],[2.095,9664405504,4912823808,4913552896,1,0,259677092657.54126,2033238784,0.0046523199416697025],[2.159,9543952384,4924352000,4925261312,1,0,259095172174.7267,1993008256,0.006000296212732792],[2.217,9423393792,4935975936,4936884224,1,0,258517491379.05832,1953060736,0.004899416118860245],[2.282,9292787712,4948661760,4949367296,1,0,257896644470.64493,1910145280,0.003818433964625001],[2.342,9178874880,4959816704,4960509952,1,0,257342892175.24518,1873020928,0.003416640916839242],[2.408,9058304000,4971710976,4972416512,1,0,256750506243.70624,1834039424,0.003951832186430693],[2.466,8955346944,4981942784,4982827008,1,0,256232937302.99207,1801007488,0.004495623055845499],[2.529,8841152512,4993380352,4994228736,1,0,255665662255.92932,1764644608,0.003872733097523451],[2.596,8726611968,5004924416,5005701632,1,0,255095033976.44464,1728466688,0.005127110984176397],[2.658,8627794944,5014961664,5015774208,1,0,254593542045.28403,1697489792,0.0046966178342700005],[2.723,8515586560,5026432000,5027292672,1,0,254020515635.83875,1662582528,0.005177410785108805],[2.78,8417136128,5036562944,5037435392,1,0,253515923398.86777,1632189824,0.004269925877451897],[2.841,8319663104,5046656512,5047660544,1,0,253006903282.50464,1602315520,0.004858516156673431],[2.905,8215367680,5057550336,5058327040,1,0,252476450322.74463,1570585856,0.004450323060154915],[2.969,8115987456,5068000768,5068729344,1,0,251958393445.6214,1540582656,0.005341308191418648],[3.029,8027116544,5077410816,5078175232,1,0,251488395554.89703,1513942784,0.006753383204340935],[3.094,7927333376,5088050176,5088766464,1,0,250961017799.63156,1484247296,0.006109394133090973],[3.156,7837451776,5097697280,5098664448,1,0,250468351716.1596,1457694720,0.004928315058350563],[3.22,7741556736,5108060160,5108752896,1,0,249965747520.64377,1429570944,0.0045166220515966415],[3.282,7652248576,5117777408,5118434304,1,0,249483589614.16943,1403570304,0.004259427078068256],[3.344,7566320640,5127195648,5127839744,1,0,249015045396.96182,1378727552,0.0045578209683299065],[3.406,7481656320,5136528384,5137172992,1,0,248550513176.69794,1354419072,0.004256425891071558],[3.469,7397415936,5145884672,5146648576,1,0,248078454248.2289,1330397184,0.004189827013760805],[3.532,7315622912,5155025920,5155765760,1,0,247624393381.8531,1307232512,0.004574020858854055],[3.594,7234718720,5164119040,5164810240,1,0,247173295228.87222,1284474880,0.005271709058433771],[3.655,7156899840,5172923904,5173592064,1,0,246736053383.43317,1262730368,0.004532421939074993],[3.719,7073790976,5182384128,5182981120,1,0,246267974823.92587,1239666304,0.0038516330532729626],[3.779,7001231872,5190699008,5191319040,1,0,245852675787.36084,1219663488,0.004614519886672497],[3.844,6923236352,5199681024,5200336896,1,0,245403346732.84015,1198303104,0.004839316941797733],[3.906,6848700416,5208327680,5208924672,1,0,244975021385.80475,1178024576,0.0042213271372020245],[3.97,6774023168,5217046016,5217677824,1,0,244538541398.27817,1157841024,0.004203627817332745],[4.029,6706985984,5224916480,5225489408,1,0,244149286805.12762,1139836928,0.005046412814408541],[4.092,6635374592,5233382912,5234002432,1,0,243725197395.07956,1120722688,0.004948814865201712],[4.155,6565767680,5241657344,5242253824,1,0,243313819931.0225,1102263040,0.004294726066291332],[4.216,6497879040,5249764864,5250373120,1,0,242908984734.2916,1084373248,0.004622919950634241],[4.281,6428309504,5258133504,5258718208,1,0,242492671056.23788,1066155776,0.004638719838112593],[4.341,6363598848,5265965568,5266562048,1,0,242101689149.16858,1049316352,0.004387823864817619],[4.376,6327105536,5270399488,5271020032,1,0,241879355222.24152,1039865408,0.003851433051750064],[4.439,6263321600,5278195200,5278743552,1,0,241493984941.9554,1023424000,0.0038558330852538347],[4.499,6201335296,5285798912,5286382592,1,0,241112705200.86685,1007543424,0.004231327213346958],[4.562,6137883648,5293640192,5294188544,1,0,240723295840.65494,991384320,0.004026629962027073],[4.625,6076644352,5301243392,5301779456,1,0,240345014457.1967,975883840,0.004193427041172981],[4.688,6014661632,5308988928,5309489152,1,0,239960317442.815,960289472,0.003997331019490957],[4.75,5954924544,5316494336,5316983296,1,0,239586512134.2034,945350784,0.003886332968249917],[4.812,5896291328,5323905024,5324369408,1,0,239217538518.3984,930774592,0.003746235044673085],[4.876,5838574592,5331232256,5331708928,1,0,238851219531.7324,916511104,0.004532021936029196],[4.939,5781308928,5338546176,5339094528,1,0,238483204359.93515,902441472,0.00440312409773469],[5.0,5727501312,5345454592,5346027008,1,0,238136874382.85645,889296832,0.004969614092260599],[5.061,5673096192,5352481792,5353017856,1,0,237787771317.80322,876080320,0.0047747171483933926],[5.125,5617411072,5359711744,5360200192,1,0,237429794753.30115,862630592,0.005199208855628967],[5.187,5564780544,5366583296,5367107072,1,0,237084878142.96237,849991104,0.00461912015452981],[5.248,5511344128,5373597184,5374049280,1,0,236738346932.67853,837230464,0.004424723796546459],[5.312,5459283968,5380468224,5380919808,1,0,236394847947.89578,824868608,0.004338824190199375],[5.375,5408440832,5387207168,5387730432,1,0,236054935563.12912,812863360,0.005302708130329847],[5.439,5355609088,5394243584,5394683904,1,0,235708156356.82822,809136512,0.004999413155019283],[5.502,5304784896,5400934400,5401410560,1,0,235372140566.44336,810140160,0.004437922965735197],[5.563,5256564736,5407232000,5407719936,1,0,235057173967.01138,811084800,0.005591502878814936],[5.626,5205060608,5413873664,5414302208,1,0,234728066820.6733,812081088,0.005176209844648838],[5.686,5155330560,5420218368,5420670464,1,0,234410498796.09805,813032768,0.004584319889545441],[5.75,5102416896,5426895360,5427335680,1,0,234077194852.4443,814034304,0.0039804307743906975],[5.812,5052056576,5433179136,5433595904,1,0,233764899111.19882,814976896,0.004706117790192366],[5.874,5002021888,5439344128,5439760384,1,0,233456614675.58084,815901632,0.004409722983837128],[5.937,4950988800,5445579776,5446020096,1,0,233144025957.1693,816836992,0.005067511927336454],[5.998,4902398464,5451433984,5452005376,1,0,232845234009.3244,817715136,0.004988314118236303],[6.029,4875886592,5454611456,5455039488,1,0,232693167228.5618,818191744,0.005113812163472176],[6.094,4822964736,5460881920,5461286400,1,0,232381069221.24854,819132288,0.005084211938083172],[6.155,4772633600,5466770944,5467223040,1,0,232084695653.49426,820015680,0.004487921949476004],[6.217,4722064384,5472624128,5473052672,1,0,231793522828.9646,820893632,0.004864114802330732],[6.28,4669444096,5478643712,5479084544,1,0,231492156126.02094,821796544,0.004420022945851088],[6.343,4618976768,5484342272,5484806144,1,0,231206113189.0714,822651328,0.005022312980145216],[6.407,4564060672,5490480128,5490836480,1,0,230904426451.5558,823572032,0.003885831916704774],[6.466,4517274624,5495653888,5496118272,1,0,230640467592.75595,824348096,0.0045533208176493645],[6.532,4468426752,5501589504,5502040576,1,0,230344647119.301,825238400,0.0045394208282232285],[6.595,4428207616,5507154944,5507559424,1,0,230068568567.3847,826073280,0.004827215801924467],[6.655,4394090496,5512530432,5512922624,1,0,229800839331.24368,826879616,0.0043167248368263245],[6.717,4363203584,5518095360,5518428672,1,0,229525560993.48602,827714368,0.004209926817566156],[6.779,4337424384,5523411456,5523803648,1,0,229256825936.40778,828511744,0.004462022799998522],[6.843,4314525696,5528857088,5529249792,1,0,228984527327.53302,829328640,0.004563420079648495],[6.906,4294987264,5534219264,5534647296,1,0,228714442229.4919,830132928,0.004691419191658497],[6.967,4278350336,5539510272,5539985920,1,0,228447722941.84702,830926528,0.004213727079331875],[7.031,4263921664,5544872448,5545276416,1,0,228183444425.35776,831730880,0.004871715791523457],[7.092,4251857920,5550126592,5550518784,1,0,227920949833.65787,832519040,0.004214826971292496],[7.157,4241308928,5555559424,5556010496,1,0,227646342759.13055,833333888,0.004490321967750788],[7.217,4232771072,5560825344,5561181696,1,0,227387887206.85648,834123840,0.004491322208195925],[7.279,4225817600,5565972480,5566352896,1,0,227129025460.5852,834895872,0.005233309231698513],[7.345,4219569664,5571665408,5572045312,1,0,226844325047.81088,835749824,0.0039665307849645615],[7.405,4215208192,5576622080,5577049088,1,0,226593940860.50134,836493312,0.004354123957455158],[7.468,4211494400,5582005248,5582349824,1,0,226329147728.66074,837300800,0.004313425160944462],[7.533,4208719360,5587365376,5587804672,1,0,226056489739.37222,838104832,0.00480441702529788],[7.595,4206794752,5592617472,5592985600,1,0,225797256855.72858,838892608,0.005090312100946903],[7.654,4205595648,5597667840,5598036480,1,0,225544595144.51376,839650176,0.004405524116009474],[7.718,4204935936,5603145728,5603454464,1,0,225273311368.79544,840471872,0.004247225821018219],[7.75,4204830720,5605795328,5606139392,1,0,225138849091.97025,840869312,0.0038480330258607864],[7.842,4205220864,5613434368,5613838848,1,0,224753799917.2613,842015232,0.004756118170917034],[7.873,4205575680,5616108032,5616452608,1,0,224623147092.666,842416192,0.004314724821597338],[7.935,4206534656,5621228032,5621596160,1,0,224365421582.78607,843184256,0.004274224862456322]]}
Modified XFEEDToolBox.Test/ScoBrakingPredictorTests.cs +177 -0
@@ -1,11 +1,188 @@
1 1 using XFEEDToolBox.Core.Navigation;
2 2 using XFEExtension.NetCore.XUnit.Attributes;
3 using System.Text.Json;
3 4
4 5 namespace XFEEDToolBox.Test;
5 6
6 7 [TestFixture]
7 8 public sealed class ScoBrakingPredictorTests
8 9 {
10 private static ScoExitEstimateInput ExitInput=>new()
11 {
12 OriginalParametersVerified=true,ForwardServoDomainVerified=true,OrdinaryArrivalControllerVerified=true,
13 OriginalBrakeScalingMode=2,RemainingDistanceMetres=10000,CurrentSpeedMetresPerSecond=400,
14 CurrentOrdinarySpeedLimitMetresPerSecond=40,OriginalBaseMaximumSpeedMetresPerSecond=1000,
15 OriginalBaseBrakeAcceleration=150,OriginalResponseTimeSeconds=1,OriginalJerkMetresPerSecondCubed=100,
16 OriginalForwardTargetBiasMetresPerSecond=10,ForwardThrottle=.75,StopResponseSecondsUpper=0,
17 ActiveAccelerationUpper=0,EstimatedCoolingAndAssistSettleSeconds=1.5,MaximumPhysicsStepSeconds=.01,
18 AdditionalResponseMarginSeconds=.1,MinimumOrdinaryHandoffSpeedMetresPerSecond=5,
19 OriginalArrivalWindowSeconds=6,RelativeDistanceMargin=.01,DistanceUncertaintyMetres=2,SafeDropDistanceMetres=1
20 };
21 private static ScoServoParameters Servo=>new()
22 {
23 PinnedSourceDomainVerified=true,BaseMaximumSpeedMetresPerSecond=1000,BaseForwardAcceleration=75,
24 BaseBrakeAcceleration=150,ResponseTimeSeconds=1,JerkMetresPerSecondCubed=10000,
25 ForwardTargetBiasMetresPerSecond=10,ForwardThrottle=.75
26 };
27 private static ScoServoStepInput ServoStep=>new()
28 {
29 SpeedMetresPerSecond=400,OrdinaryPhysicsMaximumSpeedMetresPerSecond=40,PreviousServoAccelerationNorm=1000,
30 FlightAssistBlend=1,Cooling=false,CoolingDragPerSecond=.36,NativeStepSeconds=.01
31 };
32 [Test]public void EstimatedBudgetIsExplicitlyUnqualifiedAndGivesCoolingNoBrakeCredit()
33 {
34 var first=ScoBrakingPredictor.EstimateExit(ExitInput);
35 var longer=ScoBrakingPredictor.EstimateExit(ExitInput with{EstimatedCoolingAndAssistSettleSeconds=2});
36 Check.That(first.Known&&first.Estimated&&!first.QualifiedForControl&&!first.CoolingDragUsed&&first.RequiresActualOrdinaryHandoff);
37 Check.That(Math.Abs(longer.EstimatedStopDistanceMetres-first.EstimatedStopDistanceMetres-200)<1e-9);
38 Check.That(first.EstimatedStopDistanceMetres<400*13);
39 Check.That(!ScoBrakingPredictor.EstimateExit(ExitInput with{RemainingDistanceMetres=first.EstimatedStopDistanceMetres}).RemainingDistanceExceedsEstimate);
40 }
41 [Test]public void NativeModeTwoLowerLawDoesNotUseAnUnconditionalQuarterBrake()
42 {
43 var result=ScoBrakingPredictor.StepOriginalServo(Servo,ServoStep);
44 var clamp=150*.4*Math.Sqrt(.4/.04);
45 Check.That(result.Known&&!result.QualifiedForControl);
46 Check.That(Math.Abs(result.AppliedAcceleration+clamp)<1e-10);
47 Check.Equal(32.5d,result.TargetSpeedMetresPerSecond);
48 Check.Equal(.5d,result.EffectiveResponseTimeSeconds);
49 Check.That(Math.Abs(result.NextSpeedMetresPerSecond-(400-clamp*.01))<1e-10);
50 }
51 [Test]public void OriginalJerkRampsFromTheActualPriorNormPerPhysicsStep()
52 {
53 var parameters=Servo with{JerkMetresPerSecondCubed=100};
54 var first=ScoBrakingPredictor.StepOriginalServo(parameters,ServoStep with{PreviousServoAccelerationNorm=0,NativeStepSeconds=.1});
55 var next=ScoBrakingPredictor.StepOriginalServo(parameters,ServoStep with{PreviousServoAccelerationNorm=first.NextServoAccelerationNorm,SpeedMetresPerSecond=first.NextSpeedMetresPerSecond,NativeStepSeconds=.1});
56 Check.Equal(10d,first.NextServoAccelerationNorm);Check.Equal(399d,first.NextSpeedMetresPerSecond);
57 Check.Equal(20d,next.NextServoAccelerationNorm);Check.Equal(397d,next.NextSpeedMetresPerSecond);
58 }
59 [Test]public void CoolingLambdaIsNeitherMultipliedByBlendNorPresentWhenBlendIsZero()
60 {
61 var original=ScoBrakingPredictor.StepOriginalServo(Servo,ServoStep);
62 var mixed=ScoBrakingPredictor.StepOriginalServo(Servo,ServoStep with{FlightAssistBlend=.5,Cooling=true});
63 var zero=ScoBrakingPredictor.StepOriginalServo(Servo,ServoStep with{FlightAssistBlend=0,Cooling=true});
64 Check.That(Math.Abs(mixed.AppliedAcceleration-(original.AppliedAcceleration*.5-.36*400))<1e-10);
65 Check.Equal(0d,zero.AppliedAcceleration);Check.Equal(400d,zero.NextSpeedMetresPerSecond);
66 }
67 [Test]public void BudgetMaximisesOverHandoffsAndStopsTheOverspeedDomainAtItsFloor()
68 {
69 foreach(var window in new[]{2d,6d,10d})
70 {
71 var value=ExitInput with{OriginalArrivalWindowSeconds=window};
72 var result=ScoBrakingPredictor.EstimateExit(value);Check.That(result.Known);
73 // Independent small-step lower-law integration; no predictor roots
74 // or path primitive are reused. Evaluate every possible handoff.
75 var v=value.CurrentSpeedMetresPerSecond;var distance=0d;var t=0d;var maximum=window*v;
76 const double dt=.0001;
77 while(v>result.ServoDomainFloorMetresPerSecond)
78 {
79 var a=Math.Min(result.DecayRatePerSecond*v,value.OriginalJerkMetresPerSecondCubed*t);
80 var next=Math.Max(result.ServoDomainFloorMetresPerSecond,v-a*dt);
81 distance+=(v+next)*.5*dt;t+=dt;v=next;
82 maximum=Math.Max(maximum,distance+window*v);
83 }
84 var analytic=result.OverspeedBudgetDistanceMetres+result.OrdinaryArrivalEnvelopeMetres;
85 Check.That(Math.Abs(analytic-maximum)<.03);
86 Check.That(result.BudgetHandoffSpeedMetresPerSecond>=result.ServoDomainFloorMetresPerSecond-1e-9);
87 Check.That(result.BudgetHandoffSeconds<=result.DomainFloorSeconds);
88 }
89 }
90 [Test]public void BiasFloorAndServoLimitedRateAreDerivedRatherThanFitted()
91 {
92 var original=ScoBrakingPredictor.EstimateExit(ExitInput);Check.That(original.Known);
93 Check.That(Math.Abs(original.DecayRatePerSecond-.15)<1e-12);
94 Check.That(Math.Abs(original.ServoDomainFloorMetresPerSecond-25)<1e-12);
95 var stronger=ScoBrakingPredictor.EstimateExit(ExitInput with{OriginalBaseBrakeAcceleration=500});
96 Check.That(stronger.Known);Check.Equal(.125d,stronger.DecayRatePerSecond);Check.Equal(20d,stronger.ServoDomainFloorMetresPerSecond);
97 }
98 [Test]public void MissingExitSourceGatesAndInvalidParametersNeverAuthoriseAnEstimate()
99 {
100 foreach(var input in new[]{ExitInput with{OriginalParametersVerified=false},ExitInput with{ForwardServoDomainVerified=false},
101 ExitInput with{OrdinaryArrivalControllerVerified=false},ExitInput with{OriginalBrakeScalingMode=1},
102 ExitInput with{CurrentSpeedMetresPerSecond=1000},ExitInput with{CurrentOrdinarySpeedLimitMetresPerSecond=400},
103 ExitInput with{OriginalJerkMetresPerSecondCubed=double.NaN},
104 ExitInput with{CurrentOrdinarySpeedLimitMetresPerSecond=1},ExitInput with{ForwardThrottle=1},
105 ExitInput with{EstimatedCoolingAndAssistSettleSeconds=double.PositiveInfinity}})
106 {
107 var result=ScoBrakingPredictor.EstimateExit(input);
108 Check.That(!result.Known&&!result.QualifiedForControl&&!result.RemainingDistanceExceedsEstimate);
109 }
110 Check.That(!ScoBrakingPredictor.StepOriginalServo(Servo with{PinnedSourceDomainVerified=false},ServoStep).Known);
111 Check.That(!ScoBrakingPredictor.StepOriginalServo(Servo,ServoStep with{SpeedMetresPerSecond=20,FlightAssistBlend=.5}).Known);
112 }
113 private static ScoExitEstimateObservation Observation=>new()
114 {
115 FreshBindingAndSourceGatesVerified=true,ElapsedSeconds=2,SpeedMetresPerSecond=350,OrdinarySpeedLimitMetresPerSecond=40,
116 NativeStepSeconds=.005,FlightAssistBlend=1,ScoState=0,AllowedSpeedResidualFraction=0,AllowedSpeedResidualMetresPerSecond=0
117 };
118 [Test]public void ActualOrdinaryHandoffEndsTheModelEvenWhenTheCapHasRisen()
119 {
120 var forecast=ScoBrakingPredictor.EstimateExit(ExitInput);
121 var check=ScoBrakingPredictor.CheckExitEstimate(ExitInput,forecast,Observation with{SpeedMetresPerSecond=100,OrdinarySpeedLimitMetresPerSecond=110});
122 Check.That(check.WithinEstimatedDomain&&check.OrdinaryHandoffReady&&!check.QualifiedForControl);
123 Check.Equal("actual-ordinary-cap-handoff",check.Reason);
124 }
125 [Test]public void SettleDeadlineIdentityDomainAndBrakeResidualAreOnlineFallbackReasons()
126 {
127 var forecast=ScoBrakingPredictor.EstimateExit(ExitInput);
128 foreach(var observation in new[]{Observation with{FreshBindingAndSourceGatesVerified=false},
129 Observation with{ScoState=3},Observation with{FlightAssistBlend=.9},Observation with{NativeStepSeconds=.02},
130 Observation with{ElapsedSeconds=5,SpeedMetresPerSecond=400},
131 Observation with{OrdinarySpeedLimitMetresPerSecond=20},Observation with{OrdinarySpeedLimitMetresPerSecond=1001},
132 Observation with{ElapsedSeconds=1000}})
133 {
134 var check=ScoBrakingPredictor.CheckExitEstimate(ExitInput,forecast,observation);
135 Check.That(!check.WithinEstimatedDomain&&!check.OrdinaryHandoffReady);
136 }
137 }
138 [Test]public void RecordedCurveKeepsSparsePhysicsStepsSeparateFromElapsedTime()
139 {
140 using var doc=ReadExitFixture();var frames=doc.RootElement.GetProperty("frames").EnumerateArray().Select(x=>x.EnumerateArray().Select(v=>v.GetDouble()).ToArray()).ToArray();
141 const double c=299792458;var integral=0d;
142 for(var i=1;i<frames.Length;i++)integral+=(frames[i-1][1]+frames[i][1])*.5*(frames[i][0]-frames[i-1][0]);
143 var actual=frames[0][6]-frames[^1][6];
144 Check.Equal(130,frames.Length);Check.That(Math.Abs((frames[^1][0]-frames[0][0])-7.935)<1e-9);
145 Check.That(frames.Sum(x=>x[8])<1); // NOT 7.935 seconds of simulated history.
146 Check.That(Math.Abs(integral-actual)/actual<.002);
147 Check.That(Math.Abs(actual/c-215.34685571495913)<1e-9);
148 Check.That(!doc.RootElement.GetProperty("qualifiedForControl").GetBoolean());
149 }
150 [Test]public void OriginalScalarOffReplayAgreesWithTheIndependentObservedCurveWithinItsStatedLimits()
151 {
152 using var doc=ReadExitFixture();var root=doc.RootElement;
153 var frames=root.GetProperty("frames").EnumerateArray().Select(x=>x.EnumerateArray().Select(v=>v.GetDouble()).ToArray()).ToArray();
154 var first=Array.FindIndex(frames,x=>x[5]==0);const double c=299792458;
155 var parameters=new ScoServoParameters
156 {
157 PinnedSourceDomainVerified=true,BaseMaximumSpeedMetresPerSecond=root.GetProperty("baseMaximumSpeed").GetDouble(),
158 BaseBrakeAcceleration=root.GetProperty("baseBrake").GetDouble(),BaseForwardAcceleration=root.GetProperty("baseForward").GetDouble(),
159 ResponseTimeSeconds=root.GetProperty("baseResponseTime").GetDouble(),JerkMetresPerSecondCubed=root.GetProperty("laterObservedJerk").GetDouble(),
160 ForwardTargetBiasMetresPerSecond=root.GetProperty("laterObservedBias").GetDouble(),ForwardThrottle=root.GetProperty("throttle").GetDouble()
161 };
162 // Supplied FUTURE cap, and an initially saturated servo norm hypothesis.
163 // These are a retrospective source-equation check, not live calibration.
164 var speed=frames[first][1];var norm=frames[first][7];var distance=0d;var error=0d;
165 for(var i=first+1;i<frames.Length;i++)
166 {
167 var left=frames[i-1];var right=frames[i];var t=left[0];
168 while(t<right[0]-1e-12)
169 {
170 var dt=Math.Min(.001,right[0]-t);var fraction=(t+.5*dt-left[0])/(right[0]-left[0]);
171 var result=ScoBrakingPredictor.StepOriginalServo(parameters,new()
172 {
173 SpeedMetresPerSecond=speed,PreviousServoAccelerationNorm=norm,NativeStepSeconds=dt,FlightAssistBlend=1,
174 OrdinaryPhysicsMaximumSpeedMetresPerSecond=left[2]+(right[2]-left[2])*fraction,Cooling=false,CoolingDragPerSecond=0
175 });
176 Check.That(result.Known);speed=result.NextSpeedMetresPerSecond;norm=result.NextServoAccelerationNorm;
177 distance+=result.ApproximateDistanceMetres;t+=dt;
178 }
179 error=Math.Max(error,Math.Abs(speed-right[1]));
180 }
181 var actual=frames[first][6]-frames[^1][6];
182 Check.That(error/c<.05);Check.That(Math.Abs(distance-actual)/actual<.003);
183 Check.That(Math.Abs(speed-frames[^1][1])/c<.005);
184 }
185 private static JsonDocument ReadExitFixture()=>JsonDocument.Parse(File.ReadAllText(Path.Combine(AppContext.BaseDirectory,"Fixtures","sco-exit-observed.json")));
9 186 // Isolated mathematical fixture, not a claimed calibration of any live ship.
10 187 private static ScoBrakingInput Input=>new()
11 188 {