XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.IO.Pipes;
using System.Text.Json;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
[NonParallel] // Exercises the process-wide flight input interlock.
public class NativeFlightTestProtectionTests
{
    private sealed class Sessions:IGameSessionDriver
    {
        public bool CanRelog=>false;
        public readonly List<string> Calls=[];
        public GameSessionTarget Target=new(12,DateTimeOffset.UtcNow.AddMinutes(-2).UtcTicks,"fixture-game","fixture-launcher",false);
        public Task<GameSessionTarget> FindTargetAsync(CancellationToken t){Calls.Add("find");return Task.FromResult(Target);}
        public Task<GameSessionTarget> FindLaunchTargetAsync(CancellationToken t)=>throw new NotSupportedException();
        public Task ValidateLaunchAsync(GameSessionTarget p,CancellationToken t){Calls.Add("validate-launch");return Task.CompletedTask;}
        public Task ValidateRestartAsync(GameSessionTarget p,CancellationToken t)=>throw new NotSupportedException();
        public Task ValidateExitAsync(GameSessionTarget p,CancellationToken t){Check.Equal(Target,p);Calls.Add("validate-exit");return Task.CompletedTask;}
        public Task TerminateAsync(GameSessionTarget p,CancellationToken t){Calls.Add("exit");return Task.CompletedTask;}
        public Task<bool> HasExitedAsync(GameSessionTarget p,CancellationToken t)=>Task.FromResult(true);
        public Task LaunchAsync(GameSessionTarget p,CancellationToken t){Calls.Add("launch");return Task.CompletedTask;}
        public Task<GameSessionTarget?> FindReplacementAsync(GameSessionTarget p,CancellationToken t)=>Task.FromResult<GameSessionTarget?>(p with{ProcessId=13,StartedUtcTicks=p.StartedUtcTicks+1});
        public Task RelogAsync(GameSessionTarget p,CancellationToken t)=>throw new NotSupportedException();
    }
    private static async Task ServeDescription(int id,object response,CancellationToken token)
    {
        await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{id}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
        await server.WaitForConnectionAsync(token);
        var buffer=new byte[2048];var count=await server.ReadAsync(buffer,token);
        using var request=JsonDocument.Parse(buffer.AsMemory(0,count));Check.Equal("describe",request.RootElement.GetProperty("op").GetString());
        var bytes=JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result=response});
        await server.WriteAsync(bytes,token);await server.WriteAsync(new byte[]{10},token);server.WaitForPipeDrain();
    }
    [Test] public async Task MissingContinuousHealthBlocksLiveTestsBeforeSessionActionsOrFlightInput()
    {
        var id=Random.Shared.Next(180000000,190000000);using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();var hash=new string('a',64);var server=ServeDescription(id,new{schema=1,name="XFEEDToolBox.Navigation",verified=true,processId=id,gameSha256=hash},timeout.Token);
        try{await using var protection=await NativeFlightTestProtection.ArmAsync(id,hash,sessions,()=>new(),()=>{},timeout.Token);throw new Exception("Test was incorrectly armed");}
        catch(NotSupportedException){}
        await server;Check.Equal(0,sessions.Calls.Count);
    }
    [Test] public async Task ClaimedHealthCannotOverrideWrongProcessOrImageIdentity()
    {
        foreach(var wrongProcess in new[]{false,true})
        {
            var id=Random.Shared.Next(190000000,200000000);using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));
            var sessions=new Sessions();var hash=new string('a',64);
            var server=ServeDescription(id,new{schema=1,name="XFEEDToolBox.Navigation",verified=true,processId=wrongProcess?id+1:id,gameSha256=wrongProcess?hash:new string('b',64),continuousShipHealth=true},timeout.Token);
            try{await using var protection=await NativeFlightTestProtection.ArmAsync(id,hash,sessions,()=>new(),()=>{},timeout.Token);throw new Exception("Test was incorrectly armed");}
            catch(NotSupportedException){}
            await server;Check.Equal(0,sessions.Calls.Count);
        }
    }
    [Test] public async Task FailedWorkerAndUiCallbacksCannotPreventDamageEmergencyExit()
    {
        var now=DateTimeOffset.UtcNow;var sessions=new Sessions();
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),StatusAt=now,
            Status=new((1L<<24)|8,0,1,1,0,null,null,null,null,[]),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        await using var monitor=new ShipProtectionMonitor(sessions,()=>player,()=>throw new InvalidOperationException("Fixture worker failure"));
        monitor.Progress+=_=>throw new InvalidOperationException("Fixture UI failure");await monitor.EnableAsync();now=DateTimeOffset.UtcNow;
        monitor.Observe(new(1,now,sessions.Target.ProcessId,sessions.Target.StartedUtcTicks,99,7,1,null,false),now);
        monitor.Observe(new(2,now,sessions.Target.ProcessId,sessions.Target.StartedUtcTicks,99,7,.49,null,true),now);
        await monitor.Completion.WaitAsync(TimeSpan.FromSeconds(3));
        Check.That(monitor.TestingBlocked);Check.Equal("find,validate-exit,exit,validate-launch,launch",string.Join(',',sessions.Calls));
        monitor.Observe(new(3,now,sessions.Target.ProcessId,sessions.Target.StartedUtcTicks,99,7,.48,null,true),now);
        Check.Equal(1,sessions.Calls.Count(x=>x=="exit"));
    }
    [Test] public async Task DuplicateFreshPhysicsFrameDoesNotDisarmContinuousProtection()
    {
        var pid=Random.Shared.Next(200000000,210000000);using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var at=DateTimeOffset.UtcNow;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        long reads=0;
        var serve=Task.Run(async()=>
        {
            try
            {
                while(!timeout.IsCancellationRequested)
                {
                    await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
                    await server.WaitForConnectionAsync(timeout.Token);var buffer=new byte[4096];var count=await server.ReadAsync(buffer,timeout.Token);
                    using var request=JsonDocument.Parse(buffer.AsMemory(0,count));object result;
                    if(request.RootElement.GetProperty("op").GetString()=="describe")result=new{schema=1,name="XFEEDToolBox.Navigation",verified=true,processId=pid,gameSha256=hash,continuousShipHealth=true};
                    else
                    {
                        Interlocked.Increment(ref reads);
                        result=new{entityId="99",localPlayerShipVerified=true,shipHealth=new{verified=true,sequence=7,observedAtUnixMilliseconds=at.ToUnixTimeMilliseconds(),entityId="99",hullFraction=1.0,shieldFraction=(double?)null,damageObserved=false}};
                    }
                    await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),timeout.Token);await server.WriteAsync(new byte[]{10},timeout.Token);server.WaitForPipeDrain();
                }
            }
            catch(OperationCanceledException)when(timeout.IsCancellationRequested){}
        });
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},timeout.Token);
            await Task.Delay(150,timeout.Token);Check.That(Interlocked.Read(ref reads)>=2);Check.That(!protection.Stopped);Check.That(protection.Fresh);
            NativeFlightTestProtection.CheckBeforeInput(pid);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{timeout.Cancel();await serve;}
    }
    private static async Task ServeHealth(int pid,string hash,Func<long,ulong> entity,CancellationToken token,Func<long,double>? hull=null,bool independent=false,Func<DateTimeOffset>? observedAt=null,Func<long,string?>? healthFailure=null,Func<long,bool>? damage=null)
    {
        long sequence=0;double? previousHull=null;
        try
        {
            while(!token.IsCancellationRequested)
            {
                await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
                try
                {
                await server.WaitForConnectionAsync(token);var bytes=new byte[4096];var count=await server.ReadAsync(bytes,token);if(count==0)continue;
                using var request=JsonDocument.Parse(bytes.AsMemory(0,count));object result;string? refusal=null;
                if(request.RootElement.GetProperty("op").GetString()=="describe")result=new{schema=1,name="XFEEDToolBox.Navigation",verified=true,processId=pid,gameSha256=hash,continuousShipHealth=true,independentShipHealth=independent};
                else
                {
                    Check.Equal(independent?"health.read":"read",request.RootElement.GetProperty("op").GetString());
                    sequence++;var id=entity(sequence).ToString(System.Globalization.CultureInfo.InvariantCulture);refusal=healthFailure?.Invoke(sequence);
                    var health=hull?.Invoke(sequence)??1.0;
                    var damageObserved=damage?.Invoke(sequence)??(previousHull is {} previous&&health<previous-.000001);previousHull=health;
                    result=new{entityId=id,localPlayerShipVerified=true,shipHealth=new{verified=true,sequence,observedAtUnixMilliseconds=(observedAt?.Invoke()??DateTimeOffset.UtcNow).ToUnixTimeMilliseconds(),entityId=id,hullFraction=health,shieldFraction=(double?)null,damageObserved}};
                }
                object response=refusal is null?new{ok=true,result}:new{ok=false,error=refusal};
                await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(response),token);await server.WriteAsync(new byte[]{10},token);server.WaitForPipeDrain();
                }
                catch(IOException)when(!token.IsCancellationRequested){/* A disposed polling client can close before this fixture writes its response. */}
            }
        }
        catch(OperationCanceledException)when(token.IsCancellationRequested){}
        catch(IOException)when(token.IsCancellationRequested){}
    }
    [Test]public async Task HistoricalLowHullCanArmAndNewDamageKeepsPollingUntilUserRetryIsSafe()
    {
        var pid=Random.Shared.Next(310000000,320000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var now=DateTimeOffset.UtcNow;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),StatusAt=now,
            Status=new(1L<<24,0,1,1,0,null,null,null,null,[]),Ships=[new(){ShipId=7,HullHealth=.4,HullHealthAt=now,ConfigurationAt=now}]};
        var damaged=0;var stops=0;
        var server=ServeHealth(pid,hash,_=>99,stop.Token,_=>Volatile.Read(ref damaged)==0?.4:.39,independent:true);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>Interlocked.Increment(ref stops),stop.Token,automaticExit:false);
            Check.That(protection.Fresh&&!protection.Stopped);NativeFlightTestProtection.CheckBeforeInput(pid);
            Interlocked.Exchange(ref damaged,1);
            while(!protection.Stopped)await Task.Delay(20,stop.Token);
            Check.That(!protection.Fresh&&Volatile.Read(ref stops)>0);
            Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));
            var pausedSequence=protection.LastHealth!.Sequence;
            while(!protection.Fresh)await Task.Delay(30,stop.Token);
            Check.That(!protection.Stopped&&protection.LastHealth!.Sequence>pausedSequence+10);
            NativeFlightTestProtection.CheckBeforeInput(pid);Check.That(!sessions.Calls.Contains("exit")&&!sessions.Calls.Contains("launch"));
            Check.Equal(.39,protection.LastHealth.HullHealth);
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task ManualVisualHandoffStopsInputsWithoutExitOrRestart()
    {
        var pid=Random.Shared.Next(280000000,290000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var now=DateTimeOffset.UtcNow;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false);
            await NativeFlightTestProtection.ExitForFailedHandoffAsync(pid);
            Check.That(protection.Stopped);Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));
            Check.That(!sessions.Calls.Contains("exit")&&!sessions.Calls.Contains("launch")&&!sessions.Calls.Contains("validate-exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task PreservedHandoffSceneStillExitsForSubsequentRealDamage()
    {
        var pid=Random.Shared.Next(300000000,310000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var now=DateTimeOffset.UtcNow;var damaged=0;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}],
            StatusAt=now,Status=new((1L<<24)|8,0,1,1,0,null,null,null,null,[])};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,_=>Volatile.Read(ref damaged)==0?1:.49,independent:true);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,exitForFailedHandoff:false);
            while(protection.LastHealth!.Sequence<3)await Task.Delay(20,stop.Token);
            await NativeFlightTestProtection.ExitForFailedHandoffAsync(pid);
            Check.That(protection.Stopped);Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));
            Check.That(!sessions.Calls.Contains("exit")&&!sessions.Calls.Contains("launch"));
            Interlocked.Exchange(ref damaged,1);
            while(!sessions.Calls.Contains("exit"))await Task.Delay(20,stop.Token);
            Check.Equal(1,sessions.Calls.Count(x=>x=="exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task ManualVisualDamageStopsInputsWithoutExitOrRestart()
    {
        var pid=Random.Shared.Next(290000000,300000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var now=DateTimeOffset.UtcNow;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        player=player with{StatusAt=now,Status=new((1L<<24)|8,0,1,1,0,null,null,null,null,[])};
        var damaged=0;var server=ServeHealth(pid,hash,_=>99,stop.Token,_=>Volatile.Read(ref damaged)==0?1:.49,independent:true);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false);
            // The millisecond native timestamp can precede EnableTarget by a
            // fraction of a millisecond. Establish an accepted healthy baseline
            // before injecting new damage, rather than racing the initial read.
            while(protection.LastHealth!.Sequence<3)await Task.Delay(20,stop.Token);
            Interlocked.Exchange(ref damaged,1);
            while(!protection.Stopped)await Task.Delay(20,stop.Token);
            Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));
            Check.That(!sessions.Calls.Contains("exit")&&!sessions.Calls.Contains("launch")&&!sessions.Calls.Contains("validate-exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task IndependentJournalProtectionSurvivesFailedNavigationCleanup()
    {
        var pid=Random.Shared.Next(310000000,320000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var now=DateTimeOffset.UtcNow;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),StatusAt=now,
            Status=new((1L<<24)|8,0,1,1,0,null,null,null,null,[]),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        await using var independent=new ShipProtectionMonitor(sessions,()=>player,()=>{});
        await independent.EnableAsync(stop.Token);var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true);
        try
        {
            await using(var navigation=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false))
            {
                await NativeFlightTestProtection.ExitForFailedHandoffAsync(pid);
                Check.That(navigation.Stopped);Check.That(!sessions.Calls.Contains("exit"));
            }
            // The failed navigation and its native monitor are both disposed.
            // The independently owned journal guard must still process damage.
            var at=DateTimeOffset.UtcNow;
            independent.Observe(new(1,at,Check.Event(new{timestamp=at,@event="HullDamage",Health=.35,PlayerPilot=true,Fighter=false})),player);
            while(!sessions.Calls.Contains("exit"))await Task.Delay(20,stop.Token);
            Check.Equal(1,sessions.Calls.Count(x=>x=="exit"));
            await independent.Completion;
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task LostIndependentAuthorityBarsInputEvenWhenLocalHealthIsFresh()
    {
        var pid=Random.Shared.Next(320000000,330000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var now=DateTimeOffset.UtcNow;var live=true;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true);
        try
        {
            await using var p=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false,
                externalProtectionCheck:()=>{if(!live)throw new InvalidOperationException("Independent protection lost.");});
            NativeFlightTestProtection.CheckBeforeInput(pid);live=false;Check.That(p.Fresh);
            Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task InitialSceneHealthRefusalWaitsForFreshOriginalHealthBeforeArming()
    {
        var pid=Random.Shared.Next(260000000,270000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var ready=0;var reads=0;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true,healthFailure:_=>{Interlocked.Increment(ref reads);return Volatile.Read(ref ready)==0?"Native main-ship health unavailable or stale":null;});
        try
        {
            var arm=NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false);
            while(Volatile.Read(ref reads)<2)await Task.Delay(10,stop.Token);
            Check.That(!arm.IsCompleted);Interlocked.Exchange(ref ready,1);
            await using var protection=await arm;
            Check.That(protection.Fresh);Check.That(!protection.Stopped);NativeFlightTestProtection.CheckBeforeInput(pid);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task WaitingForInitialHealthStillHonoursCancellation()
    {
        var pid=Random.Shared.Next(280000000,290000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var reads=0;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true,healthFailure:_=>{Interlocked.Increment(ref reads);return "Native main-ship health unavailable or stale";});
        try
        {
            var arm=NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false);
            while(Volatile.Read(ref reads)<2)await Task.Delay(10,stop.Token);
            stop.Cancel();var cancelled=false;try{await using var protection=await arm;}catch(OperationCanceledException){cancelled=true;}
            Check.That(cancelled);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task InitialDamageProtectionRefusalIsNeverRetriedAsMissingTelemetry()
    {
        var pid=Random.Shared.Next(270000000,280000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var reads=0;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true,healthFailure:_=>{Interlocked.Increment(ref reads);return "Ship damage protection has stopped flight control";});
        try
        {
            var refused=false;try{await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false);}
            catch(InvalidOperationException error)when(error.Message=="Ship damage protection has stopped flight control"){refused=true;}
            Check.That(refused);Check.Equal(1,reads);
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task BriefHealthStallBarsEveryInputThenResumesOnlyWithFreshOriginalSamples()
    {
        var pid=Random.Shared.Next(240000000,250000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var resume=0;
        var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true,observedAt:()=>Volatile.Read(ref resume)==0?at:DateTimeOffset.UtcNow);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token);
            await Task.Delay(650,stop.Token);
            Check.That(!protection.Stopped);Check.That(!protection.Fresh);
            Check.Throws<FlightTelemetryUnavailableException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));
            foreach(var operation in new[]{"commerce.trade","commerce.trade.batch","cg.join","station.services"})
            {
                var refused=false;
                try{await new NavigationPipeClient(pid).CallAsync(new{op=operation},stop.Token);}
                catch(FlightTelemetryUnavailableException){refused=true;}
                Check.That(refused); // no mutating request may reach the fixture's pipe
            }
            Interlocked.Exchange(ref resume,1);
            while(!protection.Fresh)await Task.Delay(20,stop.Token);
            NativeFlightTestProtection.CheckBeforeInput(pid);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task IndependentHealthDoesNotDependOnReadableFlightGeometry()
    {
        var pid=Random.Shared.Next(230000000,240000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var now=DateTimeOffset.UtcNow;var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token);
            await Task.Delay(120,stop.Token);Check.That(protection.Fresh);Check.That(!protection.Stopped);Check.Equal(1d,protection.LastHealth!.HullHealth);
        }
        finally{stop.Cancel();await server;}
    }
    [Test]public async Task LoadingWitnessStillBarsExpiredInputAndRecoveryRequiresAnActualFreshHealthRead()
    {
        var pid=Random.Shared.Next(380000000,390000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(6));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var resume=0;var pauses=0;
        var phases=new System.Collections.Concurrent.ConcurrentQueue<string>();
        var player=new PlayerState{CommanderId="F1",InGame=true,GameMode="Solo",CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true,observedAt:()=>Volatile.Read(ref resume)==0?at:DateTimeOffset.UtcNow);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token,automaticExit:false,
                expectedInputFreeJumpLoading:()=>true,pauseAutomation:_=>Interlocked.Increment(ref pauses),diagnostics:diagnostic=>phases.Enqueue(diagnostic.Phase));
            await Task.Delay(650,stop.Token);Check.That(!protection.Fresh&&!protection.Stopped);Check.Equal(0,pauses);
            Check.Throws<FlightTelemetryUnavailableException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));
            Interlocked.Exchange(ref resume,1);while(!protection.Fresh)await Task.Delay(20,stop.Token);
            NativeFlightTestProtection.CheckBeforeInput(pid);
            Check.Equal(1,phases.Count(phase=>phase=="health-gap"));Check.Equal(1,phases.Count(phase=>phase=="health-recovered"));
            Check.Equal(0,pauses);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test]public async Task UnknownHealthFailurePersistsItsActualCauseBeforePausingInsteadOfCallingManualStop()
    {
        var pid=Random.Shared.Next(390000000,400000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var manualStops=0;
        var evidence=new System.Collections.Concurrent.ConcurrentQueue<NativeFlightProtectionDiagnostic>();
        var paused=new TaskCompletionSource<Exception>(TaskCreationOptions.RunContinuationsAsynchronously);
        var player=new PlayerState{CommanderId="F1",InGame=true,GameMode="Solo",CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,independent:true,healthFailure:sequence=>sequence>1?"Unknown original health source":null);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>Interlocked.Increment(ref manualStops),stop.Token,automaticExit:false,
                expectedInputFreeJumpLoading:()=>true,pauseAutomation:error=>
                {Check.That(evidence.Any(item=>item.Phase=="monitoring-paused"));paused.TrySetResult(error);},diagnostics:evidence.Enqueue);
            var reason=await paused.Task.WaitAsync(stop.Token);Check.That(reason is InvalidOperationException);Check.Equal("Unknown original health source",reason.Message);
            await Task.Delay(160,stop.Token);
            var diagnostic=evidence.Single(item=>item.Phase=="monitoring-paused");
            Check.Equal(pid,diagnostic.ProcessId);Check.Equal(sessions.Target.StartedUtcTicks,diagnostic.StartedUtcTicks);Check.Equal("Solo",diagnostic.GameMode);
            Check.Equal(1L,diagnostic.LastHealthSequence!.Value);Check.Equal(99UL,diagnostic.LastHealthEntity!.Value);
            Check.Equal(reason.Message,diagnostic.ExceptionMessage!);Check.That(diagnostic.UnavailableSince is not null);
            Check.That(protection.Stopped&&!protection.Fresh);Check.Equal(0,manualStops);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test]public async Task ActualDamageUsesTheExistingSafetyStopEvenWithAReleasedJumpLoadingWitness()
    {
        var pid=Random.Shared.Next(400000000,410000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};var hash=new string('a',64);var at=DateTimeOffset.UtcNow;var safetyStops=0;var monitoringPauses=0;
        var phases=new System.Collections.Concurrent.ConcurrentQueue<string>();
        var player=new PlayerState{CommanderId="F1",InGame=true,GameMode="Solo",CurrentShipId=7,LoadGameAt=at.AddMinutes(-1),StatusAt=at,
            Status=new((1L<<24)|8,0,1,1,0,null,null,null,null,[]),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=at,ConfigurationAt=at}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token,hull:sequence=>sequence<4?1:.49,independent:true);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>
                {Check.That(phases.Contains("safety-stop"));Interlocked.Increment(ref safetyStops);},stop.Token,automaticExit:false,
                expectedInputFreeJumpLoading:()=>true,pauseAutomation:_=>Interlocked.Increment(ref monitoringPauses),diagnostics:diagnostic=>phases.Enqueue(diagnostic.Phase));
            while(Volatile.Read(ref safetyStops)==0)await Task.Delay(20,stop.Token);
            Check.That(protection.Stopped&&!protection.Fresh);Check.Equal(0,monitoringPauses);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task ProductionMonitorSharesNativeFeedAndDisposalReleasesTheInputInterlock()
    {
        var pid=Random.Shared.Next(210000000,220000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var now=DateTimeOffset.UtcNow;var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        var server=ServeHealth(pid,hash,_=>99,stop.Token);
        try
        {
            var monitor=new ShipProtectionMonitor(sessions,()=>player,()=>{});await monitor.EnableAsync(stop.Token);
            await monitor.AttachNativeAsync(hash,stop.Token);Check.That(monitor.ContinuousHealthReady);monitor.RequireHealthyShip(requireContinuous:true);
            await monitor.DisposeAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(1));
            await using var independent=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{},stop.Token);
            Check.That(independent.Fresh);NativeFlightTestProtection.CheckBeforeInput(pid);Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task ChangedNativeShipImmediatelyBarsInputWithoutAnEmergencyForHealthyNewShip()
    {
        var pid=Random.Shared.Next(220000000,230000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var now=DateTimeOffset.UtcNow;var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        var server=ServeHealth(pid,hash,sequence=>sequence==1?99UL:100UL,stop.Token);var stopped=0;
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>{Interlocked.Increment(ref stopped);throw new InvalidOperationException("Fixture stopped worker");},stop.Token);
            await Task.Delay(120,stop.Token);Check.That(protection.Stopped);Check.Equal(0,stopped);
            Check.Equal(100UL,protection.LastHealth!.EntityId);
            Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));Check.That(!sessions.Calls.Contains("exit"));
        }
        finally{stop.Cancel();await server;}
    }
    [Test] public async Task ChangedNativeShipKeepsDamageProtectionAfterOldInputsAreBarred()
    {
        var pid=Random.Shared.Next(230000000,240000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));var sessions=new Sessions();sessions.Target=sessions.Target with{ProcessId=pid};
        var hash=new string('a',64);var now=DateTimeOffset.UtcNow;var player=new PlayerState{CommanderId="F1",InGame=true,CurrentShipId=7,LoadGameAt=now.AddMinutes(-1),Ships=[new(){ShipId=7,HullHealth=1,HullHealthAt=now,ConfigurationAt=now}]};
        player=player with{StatusAt=now,Status=new((1L<<24)|8,0,1,1,0,null,null,null,null,[])};
        var server=ServeHealth(pid,hash,sequence=>sequence==1?99UL:100UL,stop.Token,sequence=>sequence<5?1:.49);
        try
        {
            await using var protection=await NativeFlightTestProtection.ArmAsync(pid,hash,sessions,()=>player,()=>throw new InvalidOperationException("Fixture stopped worker"),stop.Token);
            while(!sessions.Calls.Contains("launch"))await Task.Delay(20,stop.Token);
            Check.That(protection.Stopped);Check.That(protection.LastHealth!.Sequence>=5);Check.Equal(100UL,protection.LastHealth.EntityId);
            Check.Throws<InvalidOperationException>(()=>NativeFlightTestProtection.CheckBeforeInput(pid));Check.Equal(1,sessions.Calls.Count(x=>x=="exit"));
        }
        finally{stop.Cancel();await server;}
    }
}