using System.Text.Json; using System.Text.Json.Nodes; using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class CruiseOverchargeSampleHistoryTests { private static readonly DateTimeOffset Now=DateTimeOffset.FromUnixTimeMilliseconds(1700000000000); private static readonly string Hash=new('a',64); private static readonly SystemFlightFrame Flight=new(){ShipEntity=12,ReferenceWorld=24,GameSha256=Hash}; private static JsonElement Sample(DateTimeOffset at,double heat=40,double reserve=.4,ulong entity=12,ulong world=24,int rawFsd=2,bool bound=true,bool ready=true,int state=0,bool canEngage=true) =>JsonSerializer.SerializeToElement(new{enabled=true,sampleReady=ready,gameSha256=Hash,sample=new{verified=true,entityId=entity.ToString(),referenceWorldAddress=world.ToString(), observedAtUnixMilliseconds=at.ToUnixTimeMilliseconds(),inSupercruise=true,stableFsd=true,rawFsdState=rawFsd,state,nativeCanEngage=canEngage,heatPercent=heat, geometryVerified=true,collisionCourse=false,nativeAssistEnabled=false, fuelVerified=true,fuelMainTonnes=32,fuelCapacityTonnes=32,reserveFuelTonnes=reserve,fuelBoundVerified=bound,fuelRateUpperTonnesPerSecond=1}}); [Test]public void FirstSampleCannotInventHeatOrFuelSlopes() {var value=new CruiseOverchargeSampleHistory(Hash).Read(Sample(Now),Flight,Now);Check.That(value.OriginalInterfacesVerified&&double.IsNaN(value.HeatRisePercentPerSecond)&&double.IsNaN(value.FuelBurnTonnesPerSecond));} [Test]public void NativeCollisionAndAssistFactsSurviveProjectionInsteadOfBeingReplacedByThePrimaryDefaults() { var node=JsonNode.Parse(Sample(Now).GetRawText())!;node["sample"]!["collisionCourse"]=true;node["sample"]!["nativeAssistEnabled"]=true; var projected=new CruiseOverchargeSampleHistory(Hash).Read(JsonSerializer.SerializeToElement(node),Flight,Now); Check.That(projected.NativeFlightConstraintsVerified&&projected.NativeGeometryVerified&&projected.NativeCollisionCourse&&projected.NativeAssistEnabled); node["sample"]!.AsObject().Remove("geometryVerified"); Check.That(!new CruiseOverchargeSampleHistory(Hash).Read(JsonSerializer.SerializeToElement(node),Flight,Now).NativeFlightConstraintsVerified); } [Test]public void TwoSamplesUseBothTheMainAndReservoirFuel() { var history=new CruiseOverchargeSampleHistory(Hash);history.Read(Sample(Now),Flight,Now);var later=Now.AddMilliseconds(100); var value=history.Read(Sample(later,42,.3),Flight,later);Check.That(Math.Abs(value.HeatRisePercentPerSecond-20)<1e-6&&Math.Abs(value.FuelBurnTonnesPerSecond-1)<1e-6); } [Test]public void DuplicateBackwardStaleAndFutureFramesCannotEnable() { var history=new CruiseOverchargeSampleHistory(Hash);history.Read(Sample(Now),Flight,Now); foreach(var at in new[]{Now,Now.AddMilliseconds(-1),Now.AddMilliseconds(-251),Now.AddMilliseconds(1)}) Check.That(!history.Read(Sample(at),Flight,Now).OriginalInterfacesVerified); } [Test]public void OwnedFreshExactDuplicateKeepsItsOriginalTimeAndMeasuredSlopes() { var history=new CruiseOverchargeSampleHistory(Hash);history.Read(Sample(Now,state:1),Flight,Now); var at=Now.AddMilliseconds(100);var sample=Sample(at,42,.3,state:2); var previous=history.Read(sample,Flight,at); var repeated=history.Read(sample,Flight,at.AddMilliseconds(80),allowRepeated:true); Check.Equal(previous,repeated);Check.Equal(at,repeated.ObservedAt); Check.That(double.IsFinite(repeated.HeatRisePercentPerSecond)&&double.IsFinite(repeated.FuelBurnTonnesPerSecond)); Check.That(!history.Read(sample,Flight,at.AddMilliseconds(80)).OriginalInterfacesVerified); Check.That(!history.Read(sample,Flight,at.AddMilliseconds(251),allowRepeated:true).OriginalInterfacesVerified); } [Test]public void OwnedDuplicatePermissionCannotAcceptChangedBackwardFutureOrWrongIdentitySamples() { var history=new CruiseOverchargeSampleHistory(Hash);history.Read(Sample(Now,state:1),Flight,Now); var at=Now.AddMilliseconds(100);history.Read(Sample(at,42,.3,state:2),Flight,at); foreach(var sample in new[]{Sample(at,43,.3,state:2),Sample(at,42,.2,state:2),Sample(at,42,.3,state:1),Sample(at,42,.3,state:2,canEngage:false), Sample(at.AddMilliseconds(-1),42,.3,state:2),Sample(at.AddMilliseconds(81),42,.3,state:2),Sample(at,42,.3,entity:13,state:2),Sample(at,42,.3,world:25,state:2)}) Check.That(!history.Read(sample,Flight,at.AddMilliseconds(80),allowRepeated:true).OriginalInterfacesVerified); } [Test]public void SceneAndShipChangesRequireNewSlopes() { var history=new CruiseOverchargeSampleHistory(Hash);history.Read(Sample(Now),Flight,Now);var later=Now.AddMilliseconds(100); Check.That(!history.Read(Sample(later,entity:13),Flight,later).OriginalInterfacesVerified); var value=history.Read(Sample(later,entity:13,world:25),Flight with{ShipEntity=13,ReferenceWorld=25},later); Check.That(value.OriginalInterfacesVerified&&double.IsNaN(value.HeatRisePercentPerSecond)); } [Test]public void UnknownFuelBoundWrongFsdAndNoSampleRemainUnusable() { var history=new CruiseOverchargeSampleHistory(Hash);Check.That(double.IsNaN(history.Read(Sample(Now,bound:false),Flight,Now).FuelRateUpperTonnesPerSecond)); Check.That(!history.Read(Sample(Now,rawFsd:3),Flight,Now).OriginalInterfacesVerified); Check.That(!history.Read(Sample(Now,ready:false),Flight,Now).OriginalInterfacesVerified); } private static JsonElement WithThermal(ulong entity=12,ulong world=24,long sourceAgeMilliseconds=100,long heatAgeMilliseconds=0) { var value=JsonNode.Parse(Sample(Now).GetRawText())!; value["sample"]!["thermal"]=JsonSerializer.SerializeToNode(new { verified=true,entityId=entity.ToString(),referenceWorldAddress=world.ToString(), sourceObservedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds()-sourceAgeMilliseconds, heatObservedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds()-heatAgeMilliseconds, heatCapacity=100,heatDisplayScale=1.5,scoInputUpper=30,otherInputUpper=3, coolingPassive=5,coolingMaximum=20,coolingScale=1,coolingHeatLower=.2,coolingHeatUpper=1, responseDelaySeconds=.02 }); return JsonSerializer.SerializeToElement(value); } [Test]public void NativeSourceAgeDoesNotBecomeTheAgeOfFreshlyObservedHeat() { var observed=new CruiseOverchargeSampleHistory(Hash).Read(WithThermal(),Flight,Now); var thermal=observed.ThermalInput??throw new InvalidOperationException("Native source model was lost."); Check.That(thermal.Verified);Check.Equal(.1,thermal.SourceAgeSeconds);Check.Equal(0d,thermal.HeatAgeSeconds); var forecast=ScoThermalPredictor.Predict(thermal); Check.That(forecast.Known&&forecast.ScoOnNetRisePercentPerSecond>0&&forecast.ScoOffNetRisePercentPerSecond<0); } [Test]public void ThermalSourcesFromAnotherShipOrReferenceWorldCannotAuthorizeSCO() { foreach(var sample in new[]{WithThermal(entity:13),WithThermal(world:25)}) { var thermal=new CruiseOverchargeSampleHistory(Hash).Read(sample,Flight,Now).ThermalInput!; Check.That(!thermal.Verified&&!ScoThermalPredictor.Predict(thermal).Known); } } [Test]public void FutureOrExpiredNativeSourcesCannotAuthorizeSCOFromAFreshFlightFrame() { foreach(var age in new long[]{-1,201}) { var thermal=new CruiseOverchargeSampleHistory(Hash).Read(WithThermal(sourceAgeMilliseconds:age),Flight,Now).ThermalInput!; Check.That(!ScoThermalPredictor.Predict(thermal).Known); } } [Test]public void IndependentHeatTimestampSurvivesTheNewerControlSampleTimestamp() { var thermal=new CruiseOverchargeSampleHistory(Hash).Read(WithThermal(heatAgeMilliseconds:1),Flight,Now).ThermalInput!; Check.Equal(.001,thermal.HeatAgeSeconds);Check.Equal(.1,thermal.SourceAgeSeconds); Check.That(ScoThermalPredictor.Predict(thermal).Known); } [Test]public void UnknownFutureOrExpiredHeatCannotBeRenewedByAFreshControlSample() { foreach(var age in new long[]{-1,201}) { var thermal=new CruiseOverchargeSampleHistory(Hash).Read(WithThermal(heatAgeMilliseconds:age),Flight,Now).ThermalInput!; Check.That(!ScoThermalPredictor.Predict(thermal).Known); } foreach(var unknown in new JsonNode?[]{null,JsonValue.Create("unknown"),JsonValue.Create(0L)}) { var node=JsonNode.Parse(WithThermal().GetRawText())!;node["sample"]!["thermal"]!["heatObservedAtUnixMilliseconds"]=unknown; var thermal=new CruiseOverchargeSampleHistory(Hash).Read(JsonSerializer.SerializeToElement(node),Flight,Now).ThermalInput!; Check.That(!ScoThermalPredictor.Predict(thermal).Known); } var missing=JsonNode.Parse(WithThermal().GetRawText())!;missing["sample"]!["thermal"]!.AsObject().Remove("heatObservedAtUnixMilliseconds"); Check.That(!ScoThermalPredictor.Predict(new CruiseOverchargeSampleHistory(Hash).Read(JsonSerializer.SerializeToElement(missing),Flight,Now).ThermalInput!).Known); } }