using System.Diagnostics; using System.IO.Pipes; using System.Text; using System.Text.Json; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Navigation; using XFEEDToolBox.Core.Plugins; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class NativeNavigationTests { private static async Task Serve(string name,object response,CancellationToken token,Action? request=null) { await using var server=new NamedPipeServerStream(name,PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous); await server.WaitForConnectionAsync(token); var buffer=new byte[4096];var count=await server.ReadAsync(buffer,token); request?.Invoke(Encoding.UTF8.GetString(buffer,0,count)); await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(response,ToolboxSettings.JsonOptions),token); await server.WriteAsync(new byte[]{(byte)'\n'},token); server.WaitForPipeDrain(); } private static async Task Rejects(Func operation) where T:Exception {try{await operation();}catch(T){return;}throw new InvalidOperationException("Expected "+typeof(T).Name);} [Test] public async Task StatusCannotActivateNativeHooksAndObservationRequiresSeparateCommand() { var id=Random.Shared.Next(20000000,29000000);using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var commands=new List(); var server=Task.Run(async()=> { await Serve($"XFEEDToolBox.Plugin.{id}",new{schema=1,name=GamePluginManager.PluginId,processId=id,loaded=true,active=true,observationStarted=false},timeout.Token,commands.Add); await Serve($"XFEEDToolBox.Plugin.{id}",new{schema=1,name=GamePluginManager.PluginId,processId=id,loaded=true,active=true,observationStarted=true},timeout.Token,commands.Add); }); Check.That(!(await GamePluginManager.ProbeAsync(id,token:timeout.Token)).ObservationStarted); Check.That((await GamePluginManager.StartNavigationObservationAsync(id,timeout.Token)).ObservationStarted); await server;Check.Equal("status\n",commands[0]);Check.Equal("navigation.observe.start\n",commands[1]); } [Test] public async Task IncompleteNativeCapabilitiesCannotReadOrWriteFlightControls() { var id=Random.Shared.Next(30000000,39000000);var hash=new string('a',64); using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Serve($"XFEEDToolBox.Navigation.{id}",new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=false,continuousPose=true,continuousHeat=true,evidence="Observation only"}},timeout.Token); var driver=new NativeFlightControlDriver(id,hash); Check.That(!(await driver.PreflightAsync(timeout.Token)).Ready);await server; await Rejects(async()=>{await driver.ReadAsync(timeout.Token);}); await driver.ReleaseAsync(); } [Test] public async Task BuildMismatchIsRejectedBeforeAnyControlOperation() { var id=Random.Shared.Next(40000000,49000000); using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Serve($"XFEEDToolBox.Navigation.{id}",new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=new string('b',64)}},timeout.Token); var driver=new NativeFlightControlDriver(id,new string('a',64)); await Rejects(async()=>{await driver.PreflightAsync(timeout.Token);});await server; await Rejects(()=>NavigationRecovery.PreflightWithRecoveryAsync(driver,null,timeout.Token)); await Rejects(async()=>{await driver.ReadAsync(timeout.Token);}); Check.That(!NavigationRecovery.CanRetry(new InvalidOperationException("原生导航进程、构建或协议不匹配。"),default)); } [Test] public async Task HungNativeReplyTimesOutAndReleasesClientForNextConnection() { var id=Random.Shared.Next(50000000,59000000);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(async()=> { await using(var blocked=new NamedPipeServerStream(name,PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous)) {await blocked.WaitForConnectionAsync(timeout.Token);await blocked.ReadAsync(new byte[256],timeout.Token);await Task.Delay(750,timeout.Token);} await Serve(name,new{ok=true,result=new{recovered=true}},timeout.Token); }); var client=new NavigationPipeClient(id);var clock=Stopwatch.StartNew(); await Rejects(async()=>{await client.CallAsync(new{op="describe"},timeout.Token);}); Check.That(clock.Elapsed(); void Remember(string text){using var document=JsonDocument.Parse(text);requests.Add(document.RootElement.Clone());} var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=true,evidence="Isolated IPC test"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{accepted=true}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=false,cancellationPending=true}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=true,cancellationPending=false}},timeout.Token,Remember); }); var driver=new NativeFlightControlDriver(id,hash); Check.That((await driver.PreflightAsync(timeout.Token)).Ready); await driver.ApplyAsync(new("Test",null,0,false,true,false,null,"Cancellation"),1, new(123,"Test",new(0,0,0),"G"),DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token); await driver.ReleaseAsync();await server; await driver.ReleaseAsync(); Check.Equal(4,requests.Count); Check.Equal("release",requests[2].GetProperty("op").GetString()!); Check.Equal("release",requests[3].GetProperty("op").GetString()!); Check.Equal(requests[1].GetProperty("owner").GetString()!,requests[2].GetProperty("owner").GetString()!); Check.Equal(requests[2].GetProperty("owner").GetString()!,requests[3].GetProperty("owner").GetString()!); } [Test] public async Task RoutePlanningUsesOriginalGameRequestAndPreserves64BitAddressesWithoutFlightInput() { var id=Random.Shared.Next(80000000,89000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; const long target=9007199254740995;using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var requests=new List();void Remember(string text){using var document=JsonDocument.Parse(text);requests.Add(document.RootElement.Clone());} var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,gameRoutePlanning=true,evidence="Isolated route IPC fixture"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=71,applied=false,pending=true}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=71,applied=true,pending=false,failed=false,destinationAddress="9007199254740995",targetAddress="123",targetFresh=true,topologyFresh=true,waypointCount=2,routeFingerprint="18446744073709551615"}},timeout.Token,Remember); }); var driver=new NativeFlightControlDriver(id,hash);await driver.PreflightAsync(timeout.Token); var generation=await driver.RequestGameRouteAsync(new(target,"Destination",new(1,0,0),"G"),timeout.Token); var status=await driver.ReadGameRouteStatusAsync(generation,target,timeout.Token); await driver.ReleaseAsync();await server; Check.Equal(3,requests.Count);Check.Equal("route.plan",requests[1].GetProperty("op").GetString()!); Check.Equal(target,requests[1].GetProperty("destinationSystemAddress").GetInt64());Check.Equal(71L,generation); Check.Equal("route.status",requests[2].GetProperty("op").GetString()!);Check.Equal(target,status.DestinationAddress); Check.Equal(ulong.MaxValue,status.RouteFingerprint); Check.Equal(requests[1].GetProperty("owner").GetString()!,requests[2].GetProperty("owner").GetString()!); } [Test] public async Task BankedAxesUseTheExistingGuardedFlightLeaseAndRequireAnExplicitCapability() { foreach(var supported in new[]{true,false}) { var id=Random.Shared.Next(100000000,190000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));var requests=new List(); void Remember(string text){using var doc=JsonDocument.Parse(text);requests.Add(doc.RootElement.Clone());} var frame=new FlightFrame{Sequence=1,GameSha256=hash,ObservedAt=DateTimeOffset.UtcNow,GeometryVerified=true,AdaptiveFlightControl=true, SystemAddress=1,TargetSystemAddress=2,Forward=new(0,0,1),Up=new(0,1,0)}; var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=supported,evidence="Isolated three-axis IPC fixture"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{frame}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=1,selected=true,targetAddress="2"}},timeout.Token,Remember); if(supported)await Serve(name,new{ok=true,result=new{accepted=true,steering=new[]{0d,0,-.125},throttleCommand=0}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,Remember); }); var driver=new NativeFlightControlDriver(id,hash);await driver.PreflightAsync(timeout.Token);await driver.ReadAsync(timeout.Token); var next=new NavWaypoint(2,"Next",new(1,0,0),"K");await driver.SelectTargetAsync(next,timeout.Token); var orbit=new FlightDecision("Orbit",frame.Forward,0,false,false,false,null,"Banked orbit"){UpDirection=new(1,0,0)}; if(supported) { await driver.ApplyAsync(orbit,1,next,DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token); Check.Equal(-.125,driver.LastAppliedControl!.Roll!.Value); var input=requests.Single(r=>r.GetProperty("op").GetString()=="apply");var axes=input.GetProperty("steeringAxes"); Check.Equal(3,axes.GetArrayLength());Check.That(axes[2].GetDouble()<0&&Math.Abs(axes[2].GetDouble())<=.1,"An unmeasured ship starts with conservative roll authority."); } else await Rejects(()=>driver.ApplyAsync(orbit,1,next,DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token)); await driver.ReleaseAsync();await server; Check.That(requests.All(r=>!r.GetProperty("op").GetString()!.StartsWith("calibrate."))); } } [Test] public async Task RoutePlanningWaitsThroughArrivalCooldownWithoutFlightInputsButRejectsActiveCharging() { foreach(var (state,owned,fresh,retry) in new[]{(1,false,true,true),(4,false,true,true),(3,false,true,false),(1,true,true,false),(1,false,false,false)}) { var id=Random.Shared.Next(100000000,190000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));var requests=new List(); void Remember(string text){using var doc=JsonDocument.Parse(text);requests.Add(doc.RootElement.Clone());} var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,gameRoutePlanning=true,evidence="Isolated cooldown route fixture"}},timeout.Token,Remember); await Serve(name,new{ok=false,error="Game route planning requires a fresh idle FSD"},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{fresh,ownedCharging=owned,state}},timeout.Token,Remember); if(retry)await Serve(name,new{ok=true,result=new{generation=72,applied=false,pending=true}},timeout.Token,Remember); }); var driver=new NativeFlightControlDriver(id,hash,()=>new(){InGame=true,Navigation=new(){LastJumpAt=DateTimeOffset.UtcNow.AddSeconds(2)}}); await driver.PreflightAsync(timeout.Token);var destination=new NavWaypoint(2,"Destination",new(1,0,0),"G"); if(retry)Check.Equal(72L,await driver.RequestGameRouteAsync(destination,timeout.Token)); else await Rejects(()=>driver.RequestGameRouteAsync(destination,timeout.Token)); await driver.ReleaseAsync();await server; Check.Equal(retry?4:3,requests.Count); Check.That(requests.All(r=>r.GetProperty("op").GetString() is "describe" or "route.plan" or "fsd.describe")); if(retry)Check.Equal(requests[1].GetProperty("owner").GetString()!,requests[3].GetProperty("owner").GetString()!); } } [Test] public async Task DestinationIsAcknowledgedBeforeJumpAndFullThrottleIsRequired() { var id=Random.Shared.Next(70000000,79000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; const long target=9007199254740995;using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(6)); var requests=new List(); void Remember(string text){using var document=JsonDocument.Parse(text);requests.Add(document.RootElement.Clone());} var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=true,evidence="Isolated IPC fixture"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=1,selected=false,pending=true,targetAddress="0"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=1,selected=true,pending=false,targetAddress="9007199254740995"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{frame=new FlightFrame{Sequence=1,ObservedAt=DateTimeOffset.UtcNow,GameSha256=hash,Forward=new(0,0,1),Up=new(0,1,0)}}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{accepted=true}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,Remember); }); var driver=new NativeFlightControlDriver(id,hash);await driver.PreflightAsync(timeout.Token); var next=new NavWaypoint(target,"Next",new(1,0,0),"G");var jump=new FlightDecision("Jump",new(1,0,0),1,false,false,true,60,"Ready"); await Rejects(()=>driver.ApplyAsync(jump,1,next,DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token)); await driver.SelectTargetAsync(next,timeout.Token); await driver.ReadAsync(timeout.Token); await Rejects(()=>driver.ApplyAsync(jump with{Throttle=.75},1,next,DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token)); await driver.ApplyAsync(jump,1,next,DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token); await driver.ReleaseAsync();await server; Check.Equal("target.select",requests[1].GetProperty("op").GetString()!);Check.Equal(target,requests[1].GetProperty("targetSystemAddress").GetInt64()); Check.Equal("target.status",requests[2].GetProperty("op").GetString()!);Check.Equal("read",requests[3].GetProperty("op").GetString()!);Check.Equal("apply",requests[4].GetProperty("op").GetString()!); Check.Equal(1d,requests[4].GetProperty("decision").GetProperty("throttle").GetDouble()); } private static PlayerState TargetArrivalPlayer(DateTimeOffset now)=>new(){InGame=true,CommanderId="F1",CurrentShipId=8,GameMode="Solo",LoadGameAt=now.AddHours(-1), Navigation=new(){CurrentSystemAddress=1,TargetSystemAddress=2,CurrentSystemAt=now.AddSeconds(-1),LastJumpAt=now.AddSeconds(-1),Route=[new(1,"Here",new(0,0,0),"G"),new(2,"Next",new(1,0,0),"G")]}}; private static object TargetArrivalRaw(int state,bool manual=false,bool emergency=false) { var at=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); return new{sequence=7,observedAtUnixMilliseconds=at,entityId="42",referenceWorldAddress="1",localPlayerShipVerified=true,inGame=true,playerOverride=manual,rawFsdState=state, shipHealth=new{verified=true,sequence=7,observedAtUnixMilliseconds=at,entityId="42",hullFraction=1,emergency,damageObserved=false}}; } [Test]public async Task JournalArrivalBeforeNativeJumpTailWaitsWithoutAnyFlightInput() { var id=Random.Shared.Next(100000000,190000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(6));var requests=new List(); void Remember(string text){using var doc=JsonDocument.Parse(text);requests.Add(doc.RootElement.Clone());} var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=true,evidence="Isolated arrival-tail fixture"}},timeout.Token,Remember); await Serve(name,new{ok=false,error="Select the destination before starting FSD charging"},timeout.Token,Remember); foreach(var (phase,owned) in new[]{(4,false),(3,true),(2,false)}) { await Serve(name,new{ok=true,result=new{fresh=true,entityId="42",ownedCharging=owned,state=phase,targetValid=true,targetAddress="2"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=TargetArrivalRaw(phase)},timeout.Token,Remember); } await Serve(name,new{ok=true,result=new{generation=7,selected=false,pending=true,targetAddress="2"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=7,selected=true,pending=false,targetAddress="2"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,Remember); }); var player=TargetArrivalPlayer(DateTimeOffset.UtcNow);var driver=new NativeFlightControlDriver(id,hash,()=>player); var observations=new List();driver.TargetSelectionObserved+=observations.Add; await driver.PreflightAsync(timeout.Token);await driver.SelectTargetAsync(new(2,"Next",new(1,0,0),"G"),timeout.Token);await driver.ReleaseAsync();await server; Check.That(requests.All(r=>r.GetProperty("op").GetString() is "describe" or "target.select" or "target.status" or "fsd.describe" or "read" or "release")); Check.Equal(2,requests.Count(r=>r.GetProperty("op").GetString()=="target.select")); Check.Equal("acknowledged",observations[^1].GetProperty("phase").GetString()!); Check.That(observations.Any(o=>o.GetProperty("diagnostic").TryGetProperty("ownedCharging",out var owned)&&owned.ValueKind==JsonValueKind.True)); } [Test]public async Task ArrivalSelectionManualOrHealthRejectionCannotIssueAnotherTargetOrFlightRequest() { foreach(var manual in new[]{true,false}) { var id=Random.Shared.Next(100000000,190000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));var operations=new List(); void Remember(string text){using var doc=JsonDocument.Parse(text);operations.Add(doc.RootElement.GetProperty("op").GetString()!);} var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true,continuousPose=true,continuousHeat=true, starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,evidence="Isolated arrival safety fixture"}},timeout.Token,Remember); await Serve(name,new{ok=false,error="Select the destination before starting FSD charging"},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{fresh=true,entityId="42",ownedCharging=false,state=2,targetValid=true,targetAddress="2"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=TargetArrivalRaw(2,manual,!manual)},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,Remember); }); var player=TargetArrivalPlayer(DateTimeOffset.UtcNow);var driver=new NativeFlightControlDriver(id,hash,()=>player);await driver.PreflightAsync(timeout.Token); await Rejects(()=>driver.SelectTargetAsync(new(2,"Next",new(1,0,0),"G"),timeout.Token));await driver.ReleaseAsync();await server; Check.Equal(1,operations.Count(o=>o=="target.select"));Check.That(!operations.Any(o=>o is "apply" or "calibrate.fsd" or "target.status")); } } [Test] public async Task ClearNormalSpaceDepartureDoesNotEnterSupercruiseOrWaitForSpeed() { var id=Random.Shared.Next(80000000,89000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(6));var requests=new List(); var frame=new FlightFrame{Sequence=1,GameSha256=hash,ObservedAt=DateTimeOffset.UtcNow, Position=new(1e6,0,0),TargetPosition=new(0,1e7,0),Forward=new(0,1,0),Up=new(0,0,1),CurrentStar=new(1,"G",Vector3d.Zero,100,null,null), InSupercruise=false,FuelMain=30,FuelNeededToNextRefuel=10,NativeAllowsCharging=true,SpeedMetresPerSecond=0,SpeedFraction=0,OutwardDepartureRequired=false}; var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=true,evidence="Isolated reference fixture"}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{frame}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{ready=false,issue="original-flight-contexts-disabled-unconfirmed"}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{ready=true,magnitude=0}},timeout.Token,requests.Add); }); var driver=new NativeFlightControlDriver(id,hash,()=>new(){InGame=true});await driver.PreflightAsync(timeout.Token); await driver.PrepareForNavigationAsync(timeout.Token);await server; Check.Equal(4,requests.Count);Check.That(requests.All(r=>!r.Contains("apply")&&!r.Contains("target.select"))); } [Test] public async Task UnknownPhysicalSourceAndHeldRealInputRefuseLegacyPreparationBeforeAnyFlightCommand() { foreach(var missing in new[]{true,false}) { var id=Random.Shared.Next(120000000,129000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));var requests=new List(); var frame=new FlightFrame{Sequence=1,GameSha256=hash,ObservedAt=DateTimeOffset.UtcNow,Position=new(1e6,0,0), TargetPosition=new(0,1e7,0),Forward=new(0,1,0),Up=new(0,0,1),CurrentStar=new(1,"G",Vector3d.Zero,100,null,null), FuelMain=30,FuelNeededToNextRefuel=10,NativeAllowsCharging=true}; var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,evidence="Isolated physical-input fixture"}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{frame}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{ready=!missing,magnitude=missing?0:1,issue=missing?"missing-or-invalid-button:YawLeftButton":null}},timeout.Token,requests.Add); }); var driver=new NativeFlightControlDriver(id,hash,()=>new(){InGame=true});await driver.PreflightAsync(timeout.Token); await Rejects(()=>driver.PrepareForNavigationAsync(timeout.Token));await server; Check.Equal(3,requests.Count);Check.That(requests.All(request=>Check.Json(request).GetProperty("op").GetString() is "describe" or "read" or "input.physical.read")); } } [Test] public async Task ANewPhysicalInputSceneWaitsForItsProofThenRequiresANewControlFrame() { var id=Random.Shared.Next(130000000,139000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));var requests=new List(); var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,evidence="Isolated scene fixture"}},timeout.Token,requests.Add); await Serve(name,new{ok=false,error="Original physical input sources unavailable: original-flight-contexts-disabled-unconfirmed"},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{ready=false,issue="original-flight-contexts-disabled-unconfirmed"}},timeout.Token,requests.Add); await Serve(name,new{ok=true,result=new{ready=true,magnitude=0}},timeout.Token,requests.Add); }); var driver=new NativeFlightControlDriver(id,hash);await driver.PreflightAsync(timeout.Token); await Rejects(()=>driver.ApplyAsync(new("Align",null,0,false,false,false,null,"Scene proof"),1, new(2,"Next",new(0,0,1),"G"),DateTimeOffset.UtcNow.AddMilliseconds(250),timeout.Token)); await server; Check.Equal(1,requests.Count(request=>Check.Json(request).GetProperty("op").GetString()=="apply")); Check.Equal("release",Check.Json(requests[2]).GetProperty("op").GetString()!); foreach(var unknown in new[]{"Original physical input sources unavailable: missing-analog:YawAxisRaw", "Original physical input sources unavailable: unobserved-original-relative-mouse-source"}) Check.That(!NavigationRecovery.CanRetry(new InvalidOperationException(unknown),default)); } [Test] public async Task LoadedLegacyOutwardPolicyUsesExistingInputOnlyAfterSoftwareSafetyChecks() { foreach(var mode in new[]{0,1,2}) { var safe=mode!=1;var transition=mode==2; var id=Random.Shared.Next(110000000,119000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));var requests=new List(); void Remember(string text){using var document=JsonDocument.Parse(text);requests.Add(document.RootElement.Clone());} var at=DateTimeOffset.UtcNow; var frame=new FlightFrame{Sequence=1,ObservedAt=at,GameSha256=hash,GeometryVerified=true,AdaptiveFlightControl=true,JumpCommitInterlockVerified=true,NativeAllowsCharging=true, NativeFullChargePeak=safe?80:101,Fsd=FsdPhase.Idle,SystemAddress=1,TargetSystemAddress=2,HeatPercent=40,FuelMain=20,FuelRequiredForJump=5,FuelNeededToNextRefuel=6, Position=new(150,0,0),Forward=new Vector3d(-1,2,0).Unit(),Up=new(0,0,1),TargetPosition=new(-10000,20000,0),CurrentStar=new(1,"K",Vector3d.Zero,100,null,null)}; var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true,continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=true,evidence="Isolated reference fixture"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=1,selected=true,targetAddress="2"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{frame}},timeout.Token,Remember); await Serve(name,new{ok=false,error="Native departure must be outward before FSD charging"},timeout.Token,Remember); if(safe) { await Serve(name,new{ok=true,result=new{accepted=true,predictedPeak=80,throttleCommand=0}},timeout.Token,Remember); if(transition) { await Serve(name,new{ok=false,error="Hyperspace calibration requires a matching locked system target and idle FSD"},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{fresh=true,ownedCharging=false,state=2,targetValid=true,targetAddress="2"}},timeout.Token,Remember); } else await Serve(name,new{ok=true,result=new{accepted=true,generation=1}},timeout.Token,Remember); } await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,Remember); }); var next=new NavWaypoint(2,"Next",new(1,2,0),"K");var driver=new NativeFlightControlDriver(id,hash); await driver.PreflightAsync(timeout.Token);await driver.SelectTargetAsync(next,timeout.Token);await driver.ReadAsync(timeout.Token); var decision=new FlightDecision("Align and charge",frame.Forward,1,true,false,false,80,"Independent fixture"); if(transition)await Rejects(()=>driver.ApplyAsync(decision,1,next,at.AddMilliseconds(300),timeout.Token)); else if(safe)await driver.ApplyAsync(decision,1,next,at.AddMilliseconds(300),timeout.Token); else await Rejects(()=>driver.ApplyAsync(decision,1,next,at.AddMilliseconds(300),timeout.Token)); await driver.ReleaseAsync();await server; var primitive=requests.Where(r=>r.GetProperty("op").GetString()=="calibrate.fsd").ToArray(); Check.Equal(safe?1:0,primitive.Length); if(safe) { Check.Equal("beginHyperspace",primitive[0].GetProperty("desired").GetString()); Check.Equal(90,primitive[0].GetProperty("heatCeilingPercent").GetInt32()); Check.That(!requests.Where(r=>r.GetProperty("op").GetString()=="apply").Last().GetProperty("decision").GetProperty("beginCharging").GetBoolean()); } } } [Test] public async Task ObscuringPlanetPreparesSupercruiseAwayFromPlanetInsteadOfDistantStar() { var id=Random.Shared.Next(90000000,99000000);var hash=new string('a',64);var name=$"XFEEDToolBox.Navigation.{id}"; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(8));var requests=new List(); void Remember(string text){using var document=JsonDocument.Parse(text);requests.Add(document.RootElement.Clone());} var frame=new FlightFrame{Sequence=1,GameSha256=hash,ObservedAt=DateTimeOffset.UtcNow,LoadoutFingerprint="fit",SystemAddress=1, Position=new(100000,0,0),TargetPosition=new(1e8,0,0),Forward=new(-1,0,0),Up=new(0,0,1),CurrentStar=new(1,"G",Vector3d.Zero,100,null,null), Obstacles=[new(11,"Unknown",new(110000,0,0),5000,null,null)], InSupercruise=false,FuelMain=30,FuelNeededToNextRefuel=10,NativeAllowsCharging=true,SpeedFraction=1,HeatPercent=30,Fsd=FsdPhase.Idle,OutwardDepartureRequired=false}; var thermal=new ShipThermalCalibration{GameSha256=hash,LoadoutFingerprint="fit",StarBodyId=1,Verified=true,ExpiresAt=DateTimeOffset.UtcNow.AddMinutes(2), MaximumHeat=150,ChargeDurationUpperSeconds=15,ChargingGainUpperPerSecond=.1,ChargedHoldGainUpperPerSecond=.1,DistanceBands=[new(99900,3e12,1,1)],Evidence="Isolated thermal fixture"}; var next=new NavWaypoint(2,"Next",new(1,0,0),"G"); var server=Task.Run(async()=> { await Serve(name,new{ok=true,result=new{schema=1,name="XFEEDToolBox.Navigation",processId=id,gameSha256=hash,verified=true, continuousPose=true,continuousHeat=true,starEnvelopes=true,nativeSteering=true,chargeAndCommitInterlock=true,applicationSteeringAxes=true,evidence="Isolated reference fixture"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{generation=1,selected=true,targetAddress="2"}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{frame=frame with{ObservedAt=DateTimeOffset.UtcNow},thermal}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{ready=true,magnitude=0}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{accepted=true}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{frame=frame with{InSupercruise=true,Sequence=2,ObservedAt=DateTimeOffset.UtcNow},thermal}},timeout.Token,Remember); await Serve(name,new{ok=true,result=new{released=true}},timeout.Token,Remember); }); var driver=new NativeFlightControlDriver(id,hash,()=>new(){InGame=true,Navigation=new(){CurrentSystemAddress=1}}); await driver.PreflightAsync(timeout.Token);await driver.SelectTargetAsync(next,timeout.Token);await driver.PrepareForNavigationAsync(timeout.Token);await server; var decision=requests.Single(r=>r.GetProperty("op").GetString()=="apply").GetProperty("decision"); Check.That(decision.GetProperty("enterSupercruise").GetBoolean());Check.That(decision.GetProperty("beginCharging").GetBoolean()); Check.Equal(-1d,decision.GetProperty("direction").GetProperty("x").GetDouble()); } }