using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class ScoHeatSampleHistoryTests { private static void Near(double expected,double actual)=>Check.That(Math.Abs(expected-actual)<1e-7); [Test]public void FastCallbacksAccumulateEnoughRealTimeForAHeatSlope() { var history=new ScoHeatSampleHistory(); Check.That(!history.Observe(1000,40)&&double.IsNaN(history.Slope)); Check.That(!history.Observe(1004,40.04)&&double.IsNaN(history.Slope)); Check.That(!history.Observe(1008,40.08)&&double.IsNaN(history.Slope)); Check.That(history.Observe(1012,40.12));Near(10,history.Slope); Check.That(!history.Observe(1016,40.16));Near(10,history.Slope); Check.That(!history.Observe(1020,40.20)); Check.That(history.Observe(1024,40.24));Near(10,history.Slope); } [Test]public void DuplicateTimestampDoesNotReplaceTheDerivativeBaseline() { var history=new ScoHeatSampleHistory();history.Observe(1000,40); Check.That(!history.Observe(1000,41)); Check.That(history.Observe(1010,40.2));Near(20,history.Slope); Check.That(!history.Observe(1010,50));Near(20,history.Slope); Check.That(history.Observe(1030,40.4));Near(10,history.Slope); } [Test]public void ClockRollbackRequiresANewValidInterval() { var history=new ScoHeatSampleHistory();history.Observe(1000,40);history.Observe(1020,40.2); Check.That(!history.Observe(1019,41)&&double.IsNaN(history.Slope)); Check.That(!history.Observe(1024,41.05)&&double.IsNaN(history.Slope)); Check.That(history.Observe(1029,41.1));Near(10,history.Slope); } [Test]public void LongGapCannotReuseThePreviousSlope() { var history=new ScoHeatSampleHistory();history.Observe(1000,40);history.Observe(1020,40.2); Check.That(!history.Observe(2021,50)&&double.IsNaN(history.Slope)); Check.That(history.Observe(2031,50.1));Near(10,history.Slope); Check.That(history.Observe(3031,49.1));Near(-1,history.Slope); } [Test]public void InvalidHeatAndExplicitResetRequireFreshSamples() { foreach(var heat in new[]{double.NaN,double.PositiveInfinity,-1d}) { var history=new ScoHeatSampleHistory();history.Observe(1000,40);history.Observe(1020,40.2); Check.That(!history.Observe(1021,heat)&&double.IsNaN(history.Slope)); Check.That(!history.Observe(1030,40.3)&&double.IsNaN(history.Slope)); Check.That(history.Observe(1040,40.4));Near(10,history.Slope); history.Reset();Check.That(double.IsNaN(history.Slope)); Check.That(!history.Observe(1050,40.5)); } } [Test]public void DerivativePreservesRapidHeatingAndCoolingForTheGuard() { var history=new ScoHeatSampleHistory();history.Observe(1000,40); Check.That(history.Observe(1010,42));Near(200,history.Slope); Check.That(history.Observe(1030,41));Near(-50,history.Slope); } [Test]public void LowerPositiveSlopesCannotKeepAnOldPeakAliveBeyondItsWindow() { var history=new ScoHeatSampleHistory();history.Observe(1000,40);history.Observe(1010,40.2); Near(20,history.ConservativeSlope(1010));Check.Equal(1260ul,history.RecentPeakExpiresAt); history.Observe(1030,40.4);history.Observe(1240,42.5); Near(10,history.Slope);Near(20,history.ConservativeSlope(1240));Check.Equal(1260ul,history.RecentPeakExpiresAt); history.Observe(1261,42.71);Near(10,history.ConservativeSlope(1261));Check.Equal(1511ul,history.RecentPeakExpiresAt); } [Test]public void SamePhaseFrameIntervalsMeasureTheWholeSimulationHeatStep() { // A 0.63 point update across 63 ms is 10 points/s. A second read only // 10 ms away from an update must not be mixed into this phase's history. var history=new ScoHeatSampleHistory();history.Observe(1000,40); history.Observe(1063,40.63);history.Observe(1126,41.26); Near(10,history.Slope);Check.Equal(1063ul,history.SlopeStartAt);Check.Equal(1126ul,history.SlopeEndAt); Near(40.63,history.SlopeStartHeat);Near(41.26,history.SlopeEndHeat); Near(64.26,41.26+Math.Max(0,history.ConservativeSlope(1126))*2+3); } [Test]public void ExpiringHighestPeakPreservesTheNextPeakStillInsideTheWindow() { var history=new ScoHeatSampleHistory();history.Observe(1000,40);history.Observe(1010,40.2); history.Observe(1110,41.7);history.Observe(1210,42.7);history.Observe(1270,43.3); Near(15,history.ConservativeSlope(1270));Check.Equal(1360ul,history.RecentPeakExpiresAt); Near(10,history.ConservativeSlope(1361)); } [Test]public void ResetAndClockDiscontinuityDiscardThePeakAndItsEvidence() { var history=new ScoHeatSampleHistory();history.Observe(1000,40);history.Observe(1010,42); Near(200,history.ConservativeSlope(1010)); history.Observe(1009,42); Check.That(double.IsNaN(history.ConservativeSlope(1009))&&double.IsNaN(history.SlopeStartHeat)); Check.Equal(0ul,history.RecentPeakExpiresAt);Check.Equal(0ul,history.SlopeEndAt); } }