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;}
}
}
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;}
}
}