using System.IO.Pipes; using System.Text.Json; using XFEEDToolBox.Core.Navigation; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class NativeCruiseStartRefusalTests { [Test]public Task ARefusedOptionalStartReturnsToPrimaryControlAndBacksOffInsteadOfSuspendingIt()=>Verify(false); [Test]public Task AnAcknowledgedWarmupThatTheGameStopsKeepsPrimaryControlAndRetriesOnlyAfterCooldown()=>Verify(true); [Test]public Task Live08NaturalSoiStopReleasesTheOldBindingBeforeWatchingTheNewWorldWithoutRestartingSco()=>Verify(true,true); private static async Task Verify(bool stoppedStart,bool soiChange=false) { var pid=Random.Shared.Next(500000,900000);var hash=new string('a',64);var calls=0;var starts=0;var watches=0;var moved=false;var watchedWorld="24";JsonElement? receipt=null; var startedAt=0L; using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(6)); var server=Task.Run(async()=> { while(!timeout.IsCancellationRequested) { try { await using var pipe=new NamedPipeServerStream($"XFEEDToolBox.{NativeCruiseControlChannel.Service}.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous); await pipe.WaitForConnectionAsync(timeout.Token);var buffer=new byte[8192];var count=await pipe.ReadAsync(buffer,timeout.Token); using var parsed=JsonDocument.Parse(buffer.AsMemory(0,count));var request=parsed.RootElement;var op=request.GetProperty("op").GetString();calls++; if(op=="watch"){watches++;watchedWorld=moved?"48":"24";} if(op=="cruise.set") { starts++;startedAt=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();receipt=JsonSerializer.SerializeToElement(new{owner=request.GetProperty("owner").GetString(),requestId=request.GetProperty("requestId").GetString(),generation=1, selectionGeneration=1,entityId="12",referenceWorldAddress="24",failed=!stoppedStart,applied=stoppedStart,nativeMethodCalled=stoppedStart, nativeMethodRva="2eaacd0",argument=true,actualStateAfterCall=1,observedAtUnixMilliseconds=startedAt,reason="geometry-or-assist",gate=new{identityVerified=true,targetVerified=true,emergency=false,damageSeen=false,assistEnabled=false,hullFraction=1}}); } else Check.That(op is "watch" or "read"||stoppedStart&&op=="cruise.release","An off SCO refusal must not send a primary input command."); var now=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var gate=new{identityVerified=true,targetVerified=true,geometryVerified=true,emergency=false,damageSeen=false,assistEnabled=false,collisionCourse=false,rawFsdState=2,evaluatedAtUnixMilliseconds=now}; var owned=stoppedStart&&op=="cruise.set"; object? lastStop=stoppedStart&&starts>0&&!owned?new{owner="controller",startRequestId=receipt!.Value.GetProperty("requestId").GetString(),selectionGeneration=1, entityId="12",referenceWorldAddress="24",argument=false,nativeMethodCalled=moved,nativeMethodRva=moved?"2eaacd0":null,stateBefore=moved?2:3,stateAfter=3, reason=moved?"geometry-or-assist":"flight-state",observedAtUnixMilliseconds=now,gate}:null; var result=new{schema=1,service=NativeCruiseControlChannel.Service,processId=pid,gameSha256=hash,enabled=true,owningThread=7,overchargeControl=true, originalFuelRangeObservations=true,nativeStopReceipts=true,componentThermalModel=true,expiringOriginalControl=true,controlTelemetrySchema=1,overchargeControlQualified=false, owned,generation=1,receipt,released=true,lastOriginalStop=lastStop,lastGate=gate,watchEntityId="12",watchReferenceWorld=watchedWorld,watchInterface="123456",sampleReady=true, sample=new{verified=true,entityId="12",referenceWorldAddress=moved?"48":"24",@interface="123456",vtableRva="533e6e0",observedAtUnixMilliseconds=now, inSupercruise=true,stableFsd=true,rawFsdState=2,state=owned?1:0,active=owned,hullFraction=1,healthEmergency=false,nativeCanEngage=true,playerOverride=false,geometryVerified=true,collisionCourse=false,nativeAssistEnabled=false, heatPercent=40,fuelVerified=true,fuelMainTonnes=32,fuelCapacityTonnes=32,reserveFuelTonnes=.4,fuelBoundVerified=true,fuelRateUpperTonnesPerSecond=1, thermal=new{schema=1,verified=true,entityId="12",referenceWorldAddress="24",sourceObservedAtUnixMilliseconds=now,heatObservedAtUnixMilliseconds=now, heatCapacity=100,heatDisplayScale=1,scoInputUpper=10,otherInputUpper=3,coolingPassive=10,coolingMaximum=10,coolingScale=1,coolingHeatLower=0,coolingHeatUpper=1,responseDelaySeconds=.02}}}; await pipe.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),timeout.Token);await pipe.WriteAsync(new byte[]{10},timeout.Token); if(OperatingSystem.IsWindows())pipe.WaitForPipeDrain(); } catch(OperationCanceledException)when(timeout.IsCancellationRequested){break;} } }); var channel=new NativeCruiseControlChannel(pid,hash);var target=new SystemNavigationTarget(1,24,"Station",SystemTargetKind.Station,42){NativeKey=1,NativeKind=9}; async Task Update() { var now=DateTimeOffset.UtcNow;var frame=new SystemFlightFrame{ObservedAt=now,Sequence=1,ShipEntity=12,ReferenceWorld=moved?48UL:24UL,GameSha256=hash,InGame=true, Mode=LocalFlightMode.Supercruise,GeometryVerified=true,SystemAddress=1,SelectedTarget=target,Forward=new(0,0,1),TargetPosition=new(0,0,1e12),TargetDistance=1e12,Speed=1e9}; await channel.UpdateAsync("controller",1,frame,new(SystemNavigationPhase.Supercruise,"ordinary flight",new(0,0,1),.75),target,8,now.AddMilliseconds(250),timeout.Token); } try { await Update();await Task.Delay(35,timeout.Token);await Update();Check.Equal(1,starts); if(soiChange) { moved=true;await Update();Check.Equal(2,watches);Check.Equal("48",watchedWorld);Check.Equal(1,starts); Check.That(channel.LastStatus!.Value.GetProperty("sample").GetProperty("referenceWorldAddress").GetString()=="48"); await channel.ReleaseAsync(timeout.Token);return; } if(stoppedStart){await Update();Check.Equal(1,starts);} var before=calls;await Update();Check.Equal(before,calls);Check.That(channel.LastDecision?.UseOvercharge==false); } finally{timeout.Cancel();await server;} } }