using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
internal static class LocalNavigationAcceptance
{
public static async Task RunAsync(int pid,string exactName,string reportPath,bool requireCruise=true,bool useNativeCruise=true,bool allowObscuration=false,CancellationToken externalToken=default,bool manualVisual=false,bool keepHandoffScene=false)
{
if(File.Exists(reportPath)||File.Exists(reportPath+".jsonl"))throw new IOException("Use a new report path.");
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;var pipe=new NavigationPipeClient(pid);
var hash=(await pipe.CallAsync(new{op="describe"})).GetProperty("gameSha256").GetString()!;
using var source=new NativeCurrentSystemSource(pid,hash,()=>journal.State);
var catalogue=await NavigationRecovery.ReadWithRecoveryAsync(source.ReadLocationsAsync,null,externalToken);var row=catalogue.Locations.Single(x=>x.Name==exactName);
var target=new SystemNavigationTarget(catalogue.SystemAddress,row.WorldAddress,row.Name,SystemTargetKind.Station,row.MarketId){NativeKey=row.Key,NativeKind=row.NativeKind,NativeIdentity=row.Identity};
if(!NavigationAssists.From(initial).AutoDock||useNativeCruise&&!NavigationAssists.From(initial).Supercruise||initial.Docked)throw new InvalidOperationException("Navigation fixture requires an undocked ship and its chosen fitted assists.");
var start=DateTimeOffset.UtcNow;FlightTestProtectionScope? protection=null;
void Session()
{
var state=journal.State;
if(!state.InGame||state.CommanderId!=initial.CommanderId||state.CurrentShipId!=initial.CurrentShipId||state.LoadGameAt!=initial.LoadGameAt
||state.Navigation.CurrentSystemAddress!=catalogue.SystemAddress||state.CargoCount!=initial.CargoCount||state.Inventory.Any(x=>x.Count!=initial.Inventory.Where(y=>y.Key==x.Key).Sum(y=>y.Count)))
throw new InvalidOperationException("Navigation acceptance session, ship, system or reserved cargo changed.");
}
async Task EnsureProtection(CancellationToken token)
{
if(protection?.Stopped!=true)return;Session();var nav=journal.State.Navigation;
if(!(nav.SupercruiseEnteredAt>=start.AddSeconds(-1)||nav.SupercruiseExitedAt>=start.AddSeconds(-1)))throw new InvalidOperationException("Unexpected entity change during native-assist navigation.");
await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid,!manualVisual,!keepHandoffScene);
}
var driver=new NativeSystemFlightControlDriver(pid,hash,()=>journal.State,new NativeGameInputChannel(pid,hash,Path.GetFullPath("XFEEDToolBox/PluginPackage")),EnsureProtection);
var policy=new SystemFlightNavigator();var arrived=false;var released=false;var cruiseEngaged=false;var dockingGranted=false;var dockingEngaged=false;string? failure=null;
var inputs=0;double maxHeat=0;double? closest=null;DateTimeOffset? unavailableSince=null;SystemNavigationPhase? lastPhase=null;var cruiseObserved=false;var safeDropRequested=false;
var fullThrottleApproachSamples=0;double peakNormalSpeedFraction=0;double? firstAtcSpeed=null,firstNativeLandingSpeed=null;
var obscuredSamples=0;var bypassFullThrottleSamples=0;var nativeObscuredSamples=0;var clearThrottleZeroSamples=0;var nativeCruiseAfterBypass=false;var bypassCleared=false;var previouslyObscured=false;
var nativeCruiseEnabledSamples=0;var cruiseActivationRequests=0;Task? dockingDiagnostics=null;
await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
async Task Record(object value){await log.WriteLineAsync(JsonSerializer.Serialize(value));await log.FlushAsync();}
try
{
protection=await FlightTestProtectionScope.ArmAsync(pid,!manualVisual,!keepHandoffScene);using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(10));
await NavigationRecovery.ReadWithRecoveryAsync(async token=>{await driver.SelectAsync(target,token);return true;},null,stop.Token);
await driver.PrepareAsync(stop.Token);
if(!useNativeCruise)
{
var cancelUntil=DateTimeOffset.UtcNow.AddSeconds(12);
while(true)
{
var frame=await driver.ReadSystemFlightAsync(stop.Token);var assist=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(!assist.GetProperty("enabled").GetBoolean()&&!assist.GetProperty("engaged").GetBoolean())break;
if(DateTimeOffset.UtcNow>=cancelUntil)throw new TimeoutException("Original cruise assist did not release control.");
await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"正在交还游戏超巡辅助并接续工具箱驾驶。",null,null,
SystemNavigationAction.CancelOwnedAssists|SystemNavigationAction.ReleaseFlightAxes),frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
await Task.Delay(60,stop.Token);
}
}
while(true)
{
if(File.Exists(reportPath+".stop"))stop.Cancel();
Session();stop.Token.ThrowIfCancellationRequested();
try
{
var frame=await driver.ReadSystemFlightAsync(stop.Token);
if(!useNativeCruise)
{
// Exercise the real software control and safe-drop path
// while leaving the user's fitted equipment unchanged.
// The original assist must stay disabled throughout.
var assist=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(assist.GetProperty("enabled").GetBoolean()||assist.GetProperty("engaged").GetBoolean())throw new InvalidOperationException("Original cruise assist interfered with software-cruise acceptance.");
frame=frame with{Assists=frame.Assists with{Supercruise=false}};
}
maxHeat=Math.Max(maxHeat,frame.HeatPercent??100);closest=Math.Min(closest??frame.TargetDistance,frame.TargetDistance);
if(frame.PlayerOverride||frame.ExternalThreat||maxHeat>=75||!allowObscuration&&frame.TargetObscured)
{await Record(new{at=DateTimeOffset.UtcNow,safetyStop=true,frame,driver.LastTelemetry});throw new InvalidOperationException("Native navigation acceptance safety bound exceeded.");}
cruiseObserved|=frame.Mode==LocalFlightMode.Supercruise;cruiseEngaged|=frame.SupercruiseAssist==NativeAssistState.Engaged;dockingGranted|=frame.Clearance?.Permission==DockingPermission.Granted;dockingEngaged|=frame.DockingAssist==NativeAssistState.Engaged;
var decision=policy.Decide(frame,target,DateTimeOffset.UtcNow);
var nativeCruise=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(frame.TargetObscured!=nativeCruise.GetProperty("obscured").GetBoolean())
throw new InvalidOperationException("Only the original assist may trigger obstruction takeover.");
if(useNativeCruise&&frame.Mode==LocalFlightMode.Supercruise&&!frame.TargetObscured&&nativeCruise.GetProperty("enabled").GetBoolean()&&nativeCruise.GetProperty("matchesTarget").GetBoolean())
{
nativeCruiseEnabledSamples++;
if(decision.Throttle is not null||decision.Direction is not null||!decision.Actions.HasFlag(SystemNavigationAction.ReleaseFlightAxes))
throw new InvalidOperationException("The enabled native assist must retain throttle and steering ownership.");
}
if(decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist))
{
cruiseActivationRequests++;
if(Vector3d.AngleDegrees(frame.Forward,(frame.TargetPosition-frame.Position).Unit())>frame.AssistAlignmentDegrees||decision.Throttle is not null||!decision.Actions.HasFlag(SystemNavigationAction.ReleaseFlightAxes))
throw new InvalidOperationException("Native cruise activation must follow alignment and immediately release flight controls.");
}
if(frame.TargetObscured)
{
obscuredSamples++;previouslyObscured=true;
if(driver.LastTelemetry!.Value.GetProperty("supercruiseAssist").GetProperty("obscured").GetBoolean())nativeObscuredSamples++;
if(decision.Throttle==1&&frame.Throttle>=.999&&decision.Direction is {} side&&Math.Abs(Vector3d.Dot(side.Unit(),(frame.TargetPosition-frame.Position).Unit()))<.02)bypassFullThrottleSamples++;
}
else if(previouslyObscured)
{
bypassCleared=true;
if(decision.Throttle==0&&frame.Throttle<=.001)clearThrottleZeroSamples++;
nativeCruiseAfterBypass|=frame.SupercruiseAssist==NativeAssistState.Engaged;
}
if(decision.Phase==SystemNavigationPhase.NormalApproach&&frame.Assists.AutoDock&&frame.TargetDistance>SystemFlightNavigator.DockingContactDistance)
{
if(decision.Throttle!=1)throw new InvalidOperationException("Fitted docking approach must hold full throttle outside ATC range.");
fullThrottleApproachSamples++;peakNormalSpeedFraction=Math.Max(peakNormalSpeedFraction,frame.Speed/frame.MaximumNormalSpeed);
}
if(decision.Actions.HasFlag(SystemNavigationAction.RequestDocking))firstAtcSpeed??=frame.Speed;
if(frame.DockingAssist==NativeAssistState.Engaged)firstNativeLandingSpeed??=frame.Speed;
if(decision.Phase==SystemNavigationPhase.Stopped)
{
if(decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff))
{
await Record(new{at=DateTimeOffset.UtcNow,protectiveExit=!(manualVisual||keepHandoffScene),manualVisualHandoffStop=manualVisual,keepHandoffScene,frame,decision});
await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
}
throw new InvalidOperationException((manualVisual||keepHandoffScene)&&decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff)
?"交接未完成,已停止自动输入,游戏保持运行。":decision.Message);
}
if(useNativeCruise&&frame.Mode==LocalFlightMode.Supercruise&&decision.Actions.HasFlag(SystemNavigationAction.CancelOwnedAssists)&&!(allowObscuration&&frame.TargetObscured))throw new InvalidOperationException("Native assist did not engage; software fallback is a separate acceptance case.");
if(lastPhase!=decision.Phase){Console.WriteLine(JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,phase=decision.Phase.ToString(),decision.Message,frame.TargetDistance,frame.Speed}));lastPhase=decision.Phase;}
await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);inputs++;unavailableSince=null;
if(decision.Phase==SystemNavigationPhase.NativeLanding&&dockingDiagnostics is null)
{
// Read provenance independently; disk/image inspection
// must never delay the two-second flight handoff guard.
var diagnosticToken=stop.Token;
dockingDiagnostics=Task.Run(async()=>
{
object provenance;
try{provenance=await NativeDockingInputDiagnostics.ReadAsync(pid,diagnosticToken);}
catch(Exception error){provenance=new{failure=error.ToString(),processAccess="QUERY_INFORMATION | VM_READ",mutationsSent=0};}
await File.WriteAllTextAsync(reportPath+".input-provenance.json",JsonSerializer.Serialize(provenance,ToolboxSettings.JsonOptions));
});
}
safeDropRequested|=decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage);
await Record(new{at=DateTimeOffset.UtcNow,inputs,cruiseEngaged,dockingGranted,dockingEngaged,frame,decision,driver.LastTelemetry});
if(decision.Complete){arrived=true;break;}
}
catch(Exception error)when(NavigationRecovery.CanRetry(error,stop.Token))
{
unavailableSince??=DateTimeOffset.UtcNow;await driver.SuspendSystemFlightAsync();await Record(new{at=DateTimeOffset.UtcNow,recovering=error.Message});
if(DateTimeOffset.UtcNow-unavailableSince>TimeSpan.FromSeconds(30))throw;
}
await Task.Delay(60,stop.Token);
}
if(requireCruise&&(useNativeCruise?!cruiseEngaged:!cruiseObserved||!safeDropRequested)||!dockingGranted||!dockingEngaged)throw new InvalidOperationException("Arrival did not prove all required cruise, safe-drop and ATC stages.");
if(obscuredSamples>0&&(!allowObscuration||bypassFullThrottleSamples==0||!bypassCleared||clearThrottleZeroSamples==0||useNativeCruise&&!nativeCruiseAfterBypass))throw new InvalidOperationException("The actual obscured leg did not prove bypass, zero throttle and its selected cruise-control mode.");
}
catch(Exception error){failure=error.ToString();}
finally
{
try{await driver.ReleaseSystemFlightAsync();released=true;}catch(Exception error){failure=(failure??"")+" Release: "+error.Message;}
if(protection is not null)await protection.DisposeAsync();
if(dockingDiagnostics is not null)try{await dockingDiagnostics.WaitAsync(TimeSpan.FromSeconds(3));}catch{}
}
var passed=arrived&&released&&failure is null;
await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new{pid,start,finished=DateTimeOffset.UtcNow,passed,arrived,released,manualVisual,keepHandoffScene,automaticExitEnabled=!manualVisual,handoffExitEnabled=!(manualVisual||keepHandoffScene),requireCruise,useNativeCruise,cruiseObserved,safeDropRequested,cruiseEngaged,dockingGranted,dockingEngaged,inputs,maxHeat,closest,
fullThrottleApproachSamples,peakNormalSpeedFraction,firstAtcSpeed,firstNativeLandingSpeed,
allowObscuration,obscuredSamples,nativeObscuredSamples,bypassFullThrottleSamples,bypassCleared,clearThrottleZeroSamples,nativeCruiseAfterBypass,nativeCruiseEnabledSamples,cruiseActivationRequests,
finalMarketId=journal.State.MarketId,finalDocked=journal.State.Docked,failure,tradeOrdersSent=0,completeNavigationAccepted=false},ToolboxSettings.JsonOptions));
Console.WriteLine(JsonSerializer.Serialize(new{passed,arrived,released,cruiseEngaged,dockingGranted,dockingEngaged,inputs,maxHeat,closest,failure}));
if(!passed)throw new InvalidOperationException("Native-assist local navigation acceptance failed.");
}
}
using System.Text.Json;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
internal static class LocalNavigationAcceptance
{
public static async Task RunAsync(int pid,string exactName,string reportPath,bool requireCruise=true,bool useNativeCruise=true,bool allowObscuration=false,CancellationToken externalToken=default,bool manualVisual=false,bool keepHandoffScene=false)
{
if(File.Exists(reportPath)||File.Exists(reportPath+".jsonl"))throw new IOException("Use a new report path.");
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;var pipe=new NavigationPipeClient(pid);
var hash=(await pipe.CallAsync(new{op="describe"})).GetProperty("gameSha256").GetString()!;
using var source=new NativeCurrentSystemSource(pid,hash,()=>journal.State);
var catalogue=await NavigationRecovery.ReadWithRecoveryAsync(source.ReadLocationsAsync,null,externalToken);var row=catalogue.Locations.Single(x=>x.Name==exactName);
var target=new SystemNavigationTarget(catalogue.SystemAddress,row.WorldAddress,row.Name,SystemTargetKind.Station,row.MarketId){NativeKey=row.Key,NativeKind=row.NativeKind,NativeIdentity=row.Identity};
if(!NavigationAssists.From(initial).AutoDock||useNativeCruise&&!NavigationAssists.From(initial).Supercruise||initial.Docked)throw new InvalidOperationException("Navigation fixture requires an undocked ship and its chosen fitted assists.");
var start=DateTimeOffset.UtcNow;FlightTestProtectionScope? protection=null;
void Session()
{
var state=journal.State;
if(!state.InGame||state.CommanderId!=initial.CommanderId||state.CurrentShipId!=initial.CurrentShipId||state.LoadGameAt!=initial.LoadGameAt
||state.Navigation.CurrentSystemAddress!=catalogue.SystemAddress||state.CargoCount!=initial.CargoCount||state.Inventory.Any(x=>x.Count!=initial.Inventory.Where(y=>y.Key==x.Key).Sum(y=>y.Count)))
throw new InvalidOperationException("Navigation acceptance session, ship, system or reserved cargo changed.");
}
async Task EnsureProtection(CancellationToken token)
{
if(protection?.Stopped!=true)return;Session();var nav=journal.State.Navigation;
if(!(nav.SupercruiseEnteredAt>=start.AddSeconds(-1)||nav.SupercruiseExitedAt>=start.AddSeconds(-1)))throw new InvalidOperationException("Unexpected entity change during native-assist navigation.");
await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid,!manualVisual,!keepHandoffScene);
}
var driver=new NativeSystemFlightControlDriver(pid,hash,()=>journal.State,new NativeGameInputChannel(pid,hash,Path.GetFullPath("XFEEDToolBox/PluginPackage")),EnsureProtection);
var policy=new SystemFlightNavigator();var arrived=false;var released=false;var cruiseEngaged=false;var dockingGranted=false;var dockingEngaged=false;string? failure=null;
var inputs=0;double maxHeat=0;double? closest=null;DateTimeOffset? unavailableSince=null;SystemNavigationPhase? lastPhase=null;var cruiseObserved=false;var safeDropRequested=false;
var fullThrottleApproachSamples=0;double peakNormalSpeedFraction=0;double? firstAtcSpeed=null,firstNativeLandingSpeed=null;
var obscuredSamples=0;var bypassFullThrottleSamples=0;var nativeObscuredSamples=0;var clearThrottleZeroSamples=0;var nativeCruiseAfterBypass=false;var bypassCleared=false;var previouslyObscured=false;
var nativeCruiseEnabledSamples=0;var cruiseActivationRequests=0;Task? dockingDiagnostics=null;
await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
async Task Record(object value){await log.WriteLineAsync(JsonSerializer.Serialize(value));await log.FlushAsync();}
try
{
protection=await FlightTestProtectionScope.ArmAsync(pid,!manualVisual,!keepHandoffScene);using var stop=CancellationTokenSource.CreateLinkedTokenSource(externalToken);stop.CancelAfter(TimeSpan.FromMinutes(10));
await NavigationRecovery.ReadWithRecoveryAsync(async token=>{await driver.SelectAsync(target,token);return true;},null,stop.Token);
await driver.PrepareAsync(stop.Token);
if(!useNativeCruise)
{
var cancelUntil=DateTimeOffset.UtcNow.AddSeconds(12);
while(true)
{
var frame=await driver.ReadSystemFlightAsync(stop.Token);var assist=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(!assist.GetProperty("enabled").GetBoolean()&&!assist.GetProperty("engaged").GetBoolean())break;
if(DateTimeOffset.UtcNow>=cancelUntil)throw new TimeoutException("Original cruise assist did not release control.");
await driver.ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"正在交还游戏超巡辅助并接续工具箱驾驶。",null,null,
SystemNavigationAction.CancelOwnedAssists|SystemNavigationAction.ReleaseFlightAxes),frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
await Task.Delay(60,stop.Token);
}
}
while(true)
{
if(File.Exists(reportPath+".stop"))stop.Cancel();
Session();stop.Token.ThrowIfCancellationRequested();
try
{
var frame=await driver.ReadSystemFlightAsync(stop.Token);
if(!useNativeCruise)
{
// Exercise the real software control and safe-drop path
// while leaving the user's fitted equipment unchanged.
// The original assist must stay disabled throughout.
var assist=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(assist.GetProperty("enabled").GetBoolean()||assist.GetProperty("engaged").GetBoolean())throw new InvalidOperationException("Original cruise assist interfered with software-cruise acceptance.");
frame=frame with{Assists=frame.Assists with{Supercruise=false}};
}
maxHeat=Math.Max(maxHeat,frame.HeatPercent??100);closest=Math.Min(closest??frame.TargetDistance,frame.TargetDistance);
if(frame.PlayerOverride||frame.ExternalThreat||maxHeat>=75||!allowObscuration&&frame.TargetObscured)
{await Record(new{at=DateTimeOffset.UtcNow,safetyStop=true,frame,driver.LastTelemetry});throw new InvalidOperationException("Native navigation acceptance safety bound exceeded.");}
cruiseObserved|=frame.Mode==LocalFlightMode.Supercruise;cruiseEngaged|=frame.SupercruiseAssist==NativeAssistState.Engaged;dockingGranted|=frame.Clearance?.Permission==DockingPermission.Granted;dockingEngaged|=frame.DockingAssist==NativeAssistState.Engaged;
var decision=policy.Decide(frame,target,DateTimeOffset.UtcNow);
var nativeCruise=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(frame.TargetObscured!=nativeCruise.GetProperty("obscured").GetBoolean())
throw new InvalidOperationException("Only the original assist may trigger obstruction takeover.");
if(useNativeCruise&&frame.Mode==LocalFlightMode.Supercruise&&!frame.TargetObscured&&nativeCruise.GetProperty("enabled").GetBoolean()&&nativeCruise.GetProperty("matchesTarget").GetBoolean())
{
nativeCruiseEnabledSamples++;
if(decision.Throttle is not null||decision.Direction is not null||!decision.Actions.HasFlag(SystemNavigationAction.ReleaseFlightAxes))
throw new InvalidOperationException("The enabled native assist must retain throttle and steering ownership.");
}
if(decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist))
{
cruiseActivationRequests++;
if(Vector3d.AngleDegrees(frame.Forward,(frame.TargetPosition-frame.Position).Unit())>frame.AssistAlignmentDegrees||decision.Throttle is not null||!decision.Actions.HasFlag(SystemNavigationAction.ReleaseFlightAxes))
throw new InvalidOperationException("Native cruise activation must follow alignment and immediately release flight controls.");
}
if(frame.TargetObscured)
{
obscuredSamples++;previouslyObscured=true;
if(driver.LastTelemetry!.Value.GetProperty("supercruiseAssist").GetProperty("obscured").GetBoolean())nativeObscuredSamples++;
if(decision.Throttle==1&&frame.Throttle>=.999&&decision.Direction is {} side&&Math.Abs(Vector3d.Dot(side.Unit(),(frame.TargetPosition-frame.Position).Unit()))<.02)bypassFullThrottleSamples++;
}
else if(previouslyObscured)
{
bypassCleared=true;
if(decision.Throttle==0&&frame.Throttle<=.001)clearThrottleZeroSamples++;
nativeCruiseAfterBypass|=frame.SupercruiseAssist==NativeAssistState.Engaged;
}
if(decision.Phase==SystemNavigationPhase.NormalApproach&&frame.Assists.AutoDock&&frame.TargetDistance>SystemFlightNavigator.DockingContactDistance)
{
if(decision.Throttle!=1)throw new InvalidOperationException("Fitted docking approach must hold full throttle outside ATC range.");
fullThrottleApproachSamples++;peakNormalSpeedFraction=Math.Max(peakNormalSpeedFraction,frame.Speed/frame.MaximumNormalSpeed);
}
if(decision.Actions.HasFlag(SystemNavigationAction.RequestDocking))firstAtcSpeed??=frame.Speed;
if(frame.DockingAssist==NativeAssistState.Engaged)firstNativeLandingSpeed??=frame.Speed;
if(decision.Phase==SystemNavigationPhase.Stopped)
{
if(decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff))
{
await Record(new{at=DateTimeOffset.UtcNow,protectiveExit=!(manualVisual||keepHandoffScene),manualVisualHandoffStop=manualVisual,keepHandoffScene,frame,decision});
await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);
}
throw new InvalidOperationException((manualVisual||keepHandoffScene)&&decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff)
?"交接未完成,已停止自动输入,游戏保持运行。":decision.Message);
}
if(useNativeCruise&&frame.Mode==LocalFlightMode.Supercruise&&decision.Actions.HasFlag(SystemNavigationAction.CancelOwnedAssists)&&!(allowObscuration&&frame.TargetObscured))throw new InvalidOperationException("Native assist did not engage; software fallback is a separate acceptance case.");
if(lastPhase!=decision.Phase){Console.WriteLine(JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,phase=decision.Phase.ToString(),decision.Message,frame.TargetDistance,frame.Speed}));lastPhase=decision.Phase;}
await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token);inputs++;unavailableSince=null;
if(decision.Phase==SystemNavigationPhase.NativeLanding&&dockingDiagnostics is null)
{
// Read provenance independently; disk/image inspection
// must never delay the two-second flight handoff guard.
var diagnosticToken=stop.Token;
dockingDiagnostics=Task.Run(async()=>
{
object provenance;
try{provenance=await NativeDockingInputDiagnostics.ReadAsync(pid,diagnosticToken);}
catch(Exception error){provenance=new{failure=error.ToString(),processAccess="QUERY_INFORMATION | VM_READ",mutationsSent=0};}
await File.WriteAllTextAsync(reportPath+".input-provenance.json",JsonSerializer.Serialize(provenance,ToolboxSettings.JsonOptions));
});
}
safeDropRequested|=decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage);
await Record(new{at=DateTimeOffset.UtcNow,inputs,cruiseEngaged,dockingGranted,dockingEngaged,frame,decision,driver.LastTelemetry});
if(decision.Complete){arrived=true;break;}
}
catch(Exception error)when(NavigationRecovery.CanRetry(error,stop.Token))
{
unavailableSince??=DateTimeOffset.UtcNow;await driver.SuspendSystemFlightAsync();await Record(new{at=DateTimeOffset.UtcNow,recovering=error.Message});
if(DateTimeOffset.UtcNow-unavailableSince>TimeSpan.FromSeconds(30))throw;
}
await Task.Delay(60,stop.Token);
}
if(requireCruise&&(useNativeCruise?!cruiseEngaged:!cruiseObserved||!safeDropRequested)||!dockingGranted||!dockingEngaged)throw new InvalidOperationException("Arrival did not prove all required cruise, safe-drop and ATC stages.");
if(obscuredSamples>0&&(!allowObscuration||bypassFullThrottleSamples==0||!bypassCleared||clearThrottleZeroSamples==0||useNativeCruise&&!nativeCruiseAfterBypass))throw new InvalidOperationException("The actual obscured leg did not prove bypass, zero throttle and its selected cruise-control mode.");
}
catch(Exception error){failure=error.ToString();}
finally
{
try{await driver.ReleaseSystemFlightAsync();released=true;}catch(Exception error){failure=(failure??"")+" Release: "+error.Message;}
if(protection is not null)await protection.DisposeAsync();
if(dockingDiagnostics is not null)try{await dockingDiagnostics.WaitAsync(TimeSpan.FromSeconds(3));}catch{}
}
var passed=arrived&&released&&failure is null;
await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new{pid,start,finished=DateTimeOffset.UtcNow,passed,arrived,released,manualVisual,keepHandoffScene,automaticExitEnabled=!manualVisual,handoffExitEnabled=!(manualVisual||keepHandoffScene),requireCruise,useNativeCruise,cruiseObserved,safeDropRequested,cruiseEngaged,dockingGranted,dockingEngaged,inputs,maxHeat,closest,
fullThrottleApproachSamples,peakNormalSpeedFraction,firstAtcSpeed,firstNativeLandingSpeed,
allowObscuration,obscuredSamples,nativeObscuredSamples,bypassFullThrottleSamples,bypassCleared,clearThrottleZeroSamples,nativeCruiseAfterBypass,nativeCruiseEnabledSamples,cruiseActivationRequests,
finalMarketId=journal.State.MarketId,finalDocked=journal.State.Docked,failure,tradeOrdersSent=0,completeNavigationAccepted=false},ToolboxSettings.JsonOptions));
Console.WriteLine(JsonSerializer.Serialize(new{passed,arrived,released,cruiseEngaged,dockingGranted,dockingEngaged,inputs,maxHeat,closest,failure}));
if(!passed)throw new InvalidOperationException("Native-assist local navigation acceptance failed.");
}
}