using XFEEDToolBox.Core.Models; namespace XFEEDToolBox.Core.Navigation; public interface ISystemFlightControlDriver { Task InspectAsync(CancellationToken token); Task SelectAsync(SystemNavigationTarget target,CancellationToken token); Task PrepareAsync(CancellationToken token); Task ReadSystemFlightAsync(CancellationToken token); Task ApplySystemFlightAsync(SystemFlightDecision decision,SystemFlightFrame reference,SystemNavigationTarget target,DateTimeOffset expiresAt,CancellationToken token); Task ReleaseSystemFlightAsync(); Task SuspendSystemFlightAsync()=>ReleaseSystemFlightAsync(); } public sealed record SystemNavigationProgress(NavigationRunState State,SystemNavigationTarget Target,SystemNavigationPhase Phase,string Message) {public string? Diagnostic{get;init;}} public sealed class DockingHandoffException(string message,long marketId,Exception? inner=null):InvalidOperationException(message,inner) {public long MarketId{get;}=marketId;} public sealed class DockingRequestNotAppliedException():IOException("游戏尚未确认空管请求已执行。"); public sealed class DockingResetObservationPendingException(string requestId,long marketId,DateTimeOffset requestedAt,Exception inner) :Exception("归零尚未确认,正在只读核对游戏自动降落接管,不重发请求。",inner) {public string RequestId{get;}=requestId;public long MarketId{get;}=marketId;public DateTimeOffset RequestedAt{get;}=requestedAt;} /// One local leg, with expiring inputs, actual target acknowledgements and bounded telemetry recovery. public sealed class SystemNavigationRunner(Func player,Func? clock=null) { private readonly Func now=clock??(()=>DateTimeOffset.UtcNow); private readonly object gate=new(); private CancellationTokenSource? cancellation; public NavigationRunState State{get;private set;} public bool HandoffFailed{get;private set;} public Exception? Failure{get;private set;} public string LastMessage{get;private set;}=""; public event Action? Progress; public void Stop(){lock(gate)cancellation?.Cancel();} public async Task RunAsync(SystemNavigationTarget target,ISystemFlightControlDriver driver,CancellationToken token=default) { target.Validate();CancellationTokenSource stop; lock(gate) { if(cancellation is not null)throw new InvalidOperationException("星系内导航已在运行。"); stop=CancellationTokenSource.CreateLinkedTokenSource(token);cancellation=stop; } HandoffFailed=false;Failure=null;var phase=SystemNavigationPhase.Preparing;var state=NavigationRunState.Running;var terminal="星系内导航已停止。";string? reportedMessage=null; void Report(SystemNavigationPhase next,string message,string? diagnostic=null) { if(State==NavigationRunState.Running&&phase==next&&reportedMessage==message)return; phase=next;reportedMessage=message;State=NavigationRunState.Running;Progress?.Invoke(new(State,target,next,message){Diagnostic=diagnostic}); } try { stop.Token.ThrowIfCancellationRequested(); var initial=player();var initialShip=initial.CurrentShipId;var initialCommander=initial.CommanderId;var initialSession=initial.LoadGameAt; if(!initial.InGame||initial.CurrentShipId is null||initial.Navigation.CurrentSystemAddress!=target.SystemAddress||NavigationSafety.UnhealthyHull(initial)) throw new InvalidOperationException("请先进入健康、安全的飞船驾驶状态。"); if(NavigationSafety.HasExternalThreat(initial))throw new FlightThreatPauseException(); stop.Token.ThrowIfCancellationRequested(); if(TravelPreparation.AtDockedDestination(initial,target)){state=NavigationRunState.Completed;terminal=$"已停靠 {target.Name}";return;} var capabilities=await driver.InspectAsync(stop.Token); if(!capabilities.Supports(target,NavigationAssists.From(initial)))throw new NotSupportedException("当前游戏接口尚未完成此类星系内驾驶的验证。"); await driver.SelectAsync(target,stop.Token); if(!TravelPreparation.AtDockedDestination(player(),target))await driver.PrepareAsync(stop.Token); var policy=new SystemFlightNavigator();long previous=0;ulong entity=0;string? loadout=null;DateTimeOffset? missingSince=null; DockingResetObservationPendingException? pendingReset=null;var landingObserved=false;var requestFailures=0; Report(phase,"正在核对星系内目标和驾驶状态。"); while(true) { stop.Token.ThrowIfCancellationRequested();var current=player(); if(!current.InGame||current.CurrentShipId!=initialShip||current.CommanderId!=initialCommander||current.LoadGameAt!=initialSession ||NavigationSafety.UnhealthyHull(current))throw new InvalidOperationException("飞船、会话或安全状态改变,已停止星系内导航。"); if(NavigationSafety.HasExternalThreat(current))throw new FlightThreatPauseException(); if(TravelPreparation.AtDockedDestination(current,target)){state=NavigationRunState.Completed;terminal=$"已停靠 {target.Name}";break;} SystemFlightFrame frame; try { frame=await driver.ReadSystemFlightAsync(stop.Token); if(frame.Sequencenow()||now()-frame.ObservedAt>TimeSpan.FromMilliseconds(300)||!frame.GeometryVerified) throw new FlightTelemetryUnavailableException("等待新飞行数据。"); if(frame.GameSha256!=capabilities.GameSha256||string.IsNullOrWhiteSpace(frame.LoadoutFingerprint))throw new InvalidOperationException("飞行数据构建或装配身份不匹配。"); var journalNow=GameJournalClock.Now(current,now()); var confirmedTransition=frame.ReplacesShipEntity==entity&&frame.SceneTransitionAt is {} transition&&transition<=journalNow&&journalNow-transition=initialSession&&(transition==current.Navigation.SupercruiseEnteredAt||transition==current.Navigation.SupercruiseExitedAt); if(entity!=0&&frame.ShipEntity!=entity&&!confirmedTransition||loadout is not null&&frame.LoadoutFingerprint!=loadout)throw new InvalidOperationException("受控飞船或装配改变,已停止导航。"); if(frame.Sequence==previous){await Task.Delay(40,stop.Token);continue;} entity=frame.ShipEntity;loadout=frame.LoadoutFingerprint;previous=frame.Sequence; var decision=policy.Decide(frame,target,now()); if(pendingReset is not null) { landingObserved|=frame.DockingAssist==NativeAssistState.Engaged; if(!landingObserved&&decision.Phase!=SystemNavigationPhase.Stopped&&!decision.Complete &&DockingHandoffPolicy.RecoveryRequired(frame.TargetDistance,pendingReset.RequestedAt,now())) decision=new(SystemNavigationPhase.Stopped,DockingHandoffPolicy.RecoveryMessage(frame.TargetDistance),null,null,SystemNavigationAction.ExitForFailedHandoff); else if(!landingObserved&&!decision.Complete&&now()-pendingReset.RequestedAt>TimeSpan.FromSeconds(30)) throw new InvalidOperationException("自动降落回执和接管状态仍未确认,原请求未重发。",pendingReset); if(decision.Phase!=SystemNavigationPhase.Stopped&&!decision.Complete) decision=decision with{Phase=SystemNavigationPhase.NativeLanding,Message=landingObserved?"自动降落已接管,正在核对实际停靠。":"正在核对归零回执与自动降落状态,不重发请求。"}; } Report(decision.Phase,decision.Message); if(decision.Phase==SystemNavigationPhase.Stopped) { if(decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff)) { HandoffFailed=true;Failure=pendingReset; await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token); } state=NavigationRunState.Stopped;terminal=decision.Message;break; } // A lost acknowledgement is not permission for a second // arm or another axis lease. Observe the actual AI/dock; // the same distance/ten-second handoff guard remains above. if(pendingReset is null) { await driver.ApplySystemFlightAsync(decision,frame,target,frame.ObservedAt.AddMilliseconds(250),stop.Token); if(decision.Actions.HasFlag(SystemNavigationAction.RequestDocking))requestFailures=0; } missingSince=null; if(decision.Complete){state=NavigationRunState.Completed;terminal=decision.Message;break;} } catch(DockingRequestNotAppliedException) { requestFailures++; try{await driver.SuspendSystemFlightAsync();}catch(Exception release)when(NavigationRecovery.CanRetry(release,stop.Token)){} if(requestFailures>=3) { HandoffFailed=true; throw new DockingHandoffException("连续 3 次空管请求未被游戏执行,正在恢复原会话后重试。",target.MarketId!.Value); } Report(SystemNavigationPhase.AwaitingClearance,$"游戏尚未执行空管请求,正在核对后重试({requestFailures}/3)。"); } catch(DockingResetObservationPendingException error) { if(error.MarketId!=target.MarketId)throw new InvalidDataException("未确认的归零请求属于其他市场。",error); pendingReset=error;Report(SystemNavigationPhase.NativeLanding,error.Message); } catch(Exception error)when(NavigationRecovery.CanRetry(error,stop.Token)) { missingSince??=now(); if(pendingReset is null) try{await driver.SuspendSystemFlightAsync();}catch(Exception release)when(NavigationRecovery.CanRetry(release,stop.Token)){} if(now()-missingSince>TimeSpan.FromSeconds(30))throw new InvalidOperationException("星系内飞行数据恢复未成功,已停止并交还控制。",error); Report(SystemNavigationPhase.Waiting,"正在重新连接飞行数据并自动重试。",error.ToString()); } await Task.Delay(80,stop.Token); } } catch(OperationCanceledException)when(stop.IsCancellationRequested){state=NavigationRunState.Stopped;phase=SystemNavigationPhase.Stopped;} catch(Exception error){Failure=error;state=NavigationRunState.Failed;phase=SystemNavigationPhase.Stopped;terminal=error.Message;} finally { try{await driver.ReleaseSystemFlightAsync();} catch(Exception error){Failure=Failure is null?error:new AggregateException(Failure,error);state=NavigationRunState.Failed;terminal="已停止导航,输入释放未获确认:"+error.Message;} lock(gate){cancellation=null;stop.Dispose();State=state;} LastMessage=terminal;Progress?.Invoke(new(state,target,phase,terminal)); } } }