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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography;
using System.Text.Json;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;

namespace XFEEDToolBox.Core.Navigation;

/// <summary>Original local target/assist interfaces with expiring application flight inputs.</summary>
public sealed partial class NativeSystemFlightControlDriver(int processId,string gameHash,Func<PlayerState> player,IGameInputChannel? gameInputs=null,Func<CancellationToken,Task>? ensureFlightProtection=null,bool useNativeSupercruiseAssist=true):ISystemFlightControlDriver,IFlightDepartureDriver
{
    public event Action<JsonElement>? DepartureObserved;
    private readonly NavigationPipeClient pipe=new(processId);
    private readonly string owner=Guid.NewGuid().ToString("N");
    private readonly BankedAttitudeController attitude=new();
    private SystemNavigationTarget? selected;
    private SystemNavigationTarget? nativeSelected;
    private long? journalShip;
    private string commander="";
    private DateTimeOffset? sessionAt,entryRequestedAt,exitRequestedAt,assistRequestedAt,sceneTransitionAt;
    private ulong boundEntity,replacesEntity;
    private long generation;
    private int owningThread;
    private bool ownsCruiseAssist;
    private bool ownsDockingAssist;
    private bool ownsLaunch;
    private bool ownsController;
    private bool nativeDockingThrottle;
    private bool cooldownFlightControls;
    private bool cruisePolicyPrepared;
    private bool emergencyExited;
    public bool Active=>ownsController;
    private SystemFlightFrame? last;
    public JsonElement? LastTelemetry{get;private set;}
    public event Action<string>? DepartureProgress;
    private static bool Flag(JsonElement e,string key)=>e.TryGetProperty(key,out var value)&&value.ValueKind==JsonValueKind.True;
    private static double Number(JsonElement e,string key)=>e.TryGetProperty(key,out var value)&&value.ValueKind==JsonValueKind.Number?value.GetDouble():0;
    private static Vector3d Vector(JsonElement e,string key){var a=e.GetProperty(key);var v=new Vector3d(a[0].GetDouble(),a[1].GetDouble(),a[2].GetDouble());return v.Finite?v:throw new InvalidDataException("星系内位置无效。");}
    private static ulong Address(JsonElement e,string key)=>ulong.Parse(e.GetProperty(key).GetString()!,CultureInfo.InvariantCulture);
    public async Task<SystemNavigationCapabilities> InspectAsync(CancellationToken token)
    {
        var hello=await pipe.CallAsync(new{op="describe"},token);
        if(hello.GetProperty("schema").GetInt32()!=1||hello.GetProperty("name").GetString()!="XFEEDToolBox.Navigation"||hello.GetProperty("processId").GetInt32()!=processId
            ||hello.GetProperty("gameSha256").GetString()!=gameHash||gameHash.Length!=64||!Flag(hello,"verified"))throw new InvalidDataException("星系内驾驶接口与当前游戏构建不匹配。");
        owningThread=hello.GetProperty("owningThread").GetInt32();
        nativeDockingThrottle=Flag(hello,"nativeDockingThrottle");
        cooldownFlightControls=Flag(hello,"localCooldownControls");
        var applicationSchema=hello.TryGetProperty("localApplicationFlightSchema",out var schema)&&schema.TryGetInt32(out var schemaValue)?schemaValue:0;
        var physicalSchema=hello.TryGetProperty("physicalInputSourceGuardSchema",out var physical)&&physical.TryGetInt32(out var physicalValue)?physicalValue:0;
        var applicationFlight = !useNativeSupercruiseAssist && Flag(hello,"localApplicationFlightControl")&&applicationSchema==1&&physicalSchema==1
            && Flag(hello,"localTargets") && Flag(hello,"applicationSteeringAxes") && Flag(hello,"referenceGeometry") && Flag(hello,"adaptiveFlightControl")
            &&Flag(hello,"nativeSteering")&&Flag(hello,"independentShipHealth")&&Flag(hello,"continuousShipHealth")&&Flag(hello,"chargeAndCommitInterlock")
            && hello.TryGetProperty("localNavigationSchema",out var localSchema) && localSchema.GetInt32()==1;
        var originalDocking = dockingResetPipe is not null && requestOriginalDockingStart;
        return new(gameHash,Flag(hello,"localTargets"),Flag(hello,"localCruiseControl")||applicationFlight,
            Flag(hello,"localDockingRequests")||originalDocking,Flag(hello,"localNativeAssists")||originalDocking,
            Flag(hello,"localDockingPaths"),Flag(hello,"localPlanetaryApproach"),hello.GetProperty("evidence").GetString()??"")
        {
            ApplicationFlightControlVerified=applicationFlight,ApplicationFlightControlSchema=applicationSchema,UsesNativeSupercruiseAssist=useNativeSupercruiseAssist,
            OriginalOverchargeConfigured=originalCruiseControl is not null,OriginalDockingConfigured=originalDocking,
            TelemetryReady=Flag(hello,"inGame")&&Flag(hello,"ready")&&Flag(hello,"continuousPose")&&Flag(hello,"continuousHeat")&&owningThread>0,
            TelemetryIssue=!Flag(hello,"inGame")?"等待进入驾驶舱场景。":!Flag(hello,"ready")||owningThread<=0?"等待原生观察接口就绪。":
                !Flag(hello,"continuousPose")||!Flag(hello,"continuousHeat")?"正在恢复当前飞船姿态与热量观测。":null,
            ControlUnavailableReason=applicationFlight||Flag(hello,"localCruiseControl")?null:useNativeSupercruiseAssist?
                "当前驱动选择了未开放的游戏超巡辅助,请使用工具箱自主驾驶。":"当前插件缺少已验证的应用驾驶或物理接管契约,请更新游戏内插件。"
        };
    }
    public async Task SelectAsync(SystemNavigationTarget target,CancellationToken token)
    {
        target.Validate();if(target.NativeKey<0||target.NativeKind<=0||target.NativeIdentity.Length!=64)throw new ArgumentException("目标缺少当前游戏目录中的完整身份。");
        if(!(await InspectAsync(token)).LocalTargets)throw new NotSupportedException("当前插件尚不支持星系内目标选择。");
        var state=player();NavigationSafety.RequireLocalSelection(state,target.SystemAddress);
        await SelectNativeAsync(target,token);selected=nativeSelected=target;last=null;attitude.Reset();ownsCruiseAssist=ownsDockingAssist=false;
        journalShip=state.CurrentShipId;commander=state.CommanderId;sessionAt=state.LoadGameAt;boundEntity=replacesEntity=0;cruisePolicyPrepared=false;
        entryRequestedAt=exitRequestedAt=assistRequestedAt=sceneTransitionAt=null;
    }
    private async Task SelectNativeAsync(SystemNavigationTarget target,CancellationToken token)
    {
        var result=await pipe.CallAsync(new{op="local.select",owner,key=target.NativeKey,bodyAddress=target.BodyAddress.ToString(CultureInfo.InvariantCulture),kind=target.NativeKind,
            marketId=target.MarketId??uint.MaxValue,name=target.Name},token);
        generation=result.GetProperty("generation").GetInt64();var watch=Stopwatch.StartNew();
        ownsController=true;
        while(!result.GetProperty("selected").GetBoolean())
        {
            if(!result.GetProperty("pending").GetBoolean()||watch.Elapsed>TimeSpan.FromSeconds(2))throw new InvalidOperationException("游戏未确认所选的星系内目标。");
            await Task.Delay(25,token);result=await pipe.CallAsync(new{op="local.status",owner,generation},token);
        }
        MatchTarget(result.GetProperty("target"),target);
    }
    public async Task PrepareAsync(CancellationToken token)
    {
        if(ensureFlightProtection is not null)await ensureFlightProtection(token);
        if(selected is null)throw new InvalidOperationException("请先选择星系内目的地。");
        void RequirePreparationIdentity()
        {
            token.ThrowIfCancellationRequested();var current=player();
            if(!current.InGame||current.CommanderId!=commander||current.CurrentShipId!=journalShip||current.LoadGameAt!=sessionAt
                ||current.Navigation.CurrentSystemAddress!=selected.SystemAddress||NavigationSafety.UnhealthyHull(current))
                throw new InvalidOperationException("准备星系内驾驶时飞船、会话或安全状态改变。");
            if(NavigationSafety.HasExternalThreat(current))throw new FlightThreatPauseException();
        }
        async Task<bool> PreserveArrivalAsync(CancellationToken cancellation)
        {
            var observation=Stopwatch.StartNew();
            while(true)
            {
                cancellation.ThrowIfCancellationRequested();RequirePreparationIdentity();
                if(TravelPreparation.AtDockedDestination(player(),selected))return true;
                var observed=await ReadSystemFlightAsync(cancellation);RequirePreparationIdentity();
                if(observed.PlayerOverride)throw new InvalidOperationException("玩家已主动接管星系内驾驶。");
                if(TravelPreparation.AtDockedDestination(player(),selected)||TravelPreparation.PreserveOriginalLanding(observed,selected,DateTimeOffset.UtcNow))return true;
                var local=LastTelemetry!.Value;
                if(!NativeSystemFlightObservation.ObserveOriginalArrival(local.GetProperty("docking"),local.GetProperty("rawDockingState").GetInt32()))return false;
                // Native states 5/7 are physically docked, before Journal may
                // arrive. An active original duty without a fresh grant is also
                // observation-only; neither state permits retracting its gear.
                if(observation.Elapsed>=TimeSpan.FromSeconds(30))throw new FlightTelemetryUnavailableException("正在等待原版降落与实际停靠记录同步,不发送设备输入。");
                await Task.Delay(80,cancellation);
            }
        }
        RequirePreparationIdentity();if(TravelPreparation.AtDockedDestination(player(),selected))return;
        if(player() is {Docked:true} docked&&docked.MarketId!=selected.MarketId)
        {
            await DepartDockedStationAsync(token);
            RequirePreparationIdentity();
        }
        SystemFlightFrame frame;var watch=Stopwatch.StartNew();
        while(true)
        {
            try{frame=await ReadSystemFlightAsync(token);break;}
            catch(Exception error)when(NavigationRecovery.CanRetry(error,token)&&watch.Elapsed<TimeSpan.FromSeconds(30))
            {await Task.Delay(80,token);}
        }
        if(await PreserveArrivalAsync(token))return;
        await PhysicalInputPreparation.RequireAsync(pipe,token);
        if(gameInputs is not null)
        {
            await gameInputs.ConnectAsync(owningThread,token);
            if(!await TravelPreparation.EnsureRetractedAsync(gameInputs,player,token,frame.ShipEntity,PreserveArrivalAsync))return;
        }
        else if(TravelPreparation.HasDeployedEquipment(player()))throw new InvalidOperationException("请先收起武器挂点、货舱舱门和起落架。");
        if(await PreserveArrivalAsync(token))return;
        if(!useNativeSupercruiseAssist)
        {
            var until=DateTimeOffset.UtcNow.AddSeconds(12);
            while(true)
            {
                frame=await ReadSystemFlightAsync(token);var assist=LastTelemetry!.Value.GetProperty("supercruiseAssist");
                if(!Flag(assist,"enabled")&&!Flag(assist,"engaged")){cruisePolicyPrepared=true;break;}
                if(DateTimeOffset.UtcNow>=until)throw new TimeoutException("游戏超巡辅助尚未交还控制。");
                await ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"正在接续工具箱自主超巡。",null,null,
                    SystemNavigationAction.CancelOwnedAssists|SystemNavigationAction.ReleaseFlightAxes),frame,selected,frame.ObservedAt.AddMilliseconds(250),token);
                await Task.Delay(60,token);
            }
        }
    }
    public Task DepartAsync(CancellationToken token)
        =>FlightControlCleanup.RunAsync(()=>DepartCoreAsync(token),ReleaseOwnedDepartureControlsAsync);
    private Task ReleaseOwnedDepartureControlsAsync()=>ownsController||ownsLaunch?ReleaseSystemFlightAsync():Task.CompletedTask;
    private async Task DepartCoreAsync(CancellationToken token)
    {
        if(ensureFlightProtection is not null)await ensureFlightProtection(token);
        await DepartDockedStationAsync(token,restoreDestination:false);
    }
    private async Task DepartDockedStationAsync(CancellationToken token,bool restoreDestination=true,SystemNavigationTarget? resumeOrigin=null,
        DateTimeOffset? resumedUndockedAt=null,bool returnToOrigin=false)
    {
        var destination=selected!;var initial=player();
        if(!NavigationAssists.From(initial).AdvancedDocking)throw new NotSupportedException("当前飞船缺少已启用的高级自动起降模块,手动离场尚未完成验证。");
        using var source=new NativeCurrentSystemSource(processId,gameHash,player);var catalogue=await source.ReadLocationsAsync(token);
        var origin=resumeOrigin??StationDepartureTarget.Resolve(catalogue,initial);
        await SelectAsync(origin,token);var started=resumedUndockedAt??GameJournalClock.Now(player(),DateTimeOffset.UtcNow);
        var resumedAge=resumedUndockedAt is not null?GameJournalClock.Now(player(),DateTimeOffset.UtcNow)-started:TimeSpan.Zero;
        if(resumedAge<TimeSpan.Zero)resumedAge=TimeSpan.Zero;
        var watch=Stopwatch.StartNew();TimeSpan Elapsed()=>watch.Elapsed+resumedAge;
        if(resumeOrigin is null)await RequestLaunchAsync(token);
        TimeSpan? takeoverRequestedAt=null;string? lastMessage=null;var departurePath=new StationDeparturePathEvidence();var clearingMassLock=false;
        void Report(string message)
        {
            if(message==lastMessage)return;lastMessage=message;
            if(DepartureProgress is not {} handlers)return;
            foreach(Action<string> handler in handlers.GetInvocationList())try{handler(message);}catch{/* UI reporting cannot change departure authority. */}
        }
        var nextObservation=TimeSpan.Zero;
        void ObserveDeparture(PlayerState current,SystemFlightFrame? frame,JsonElement docking,string? error=null)
        {
            if(Elapsed()<nextObservation)return;nextObservation=Elapsed().Add(TimeSpan.FromSeconds(2));
            if(DepartureObserved is null)return;
            object? flight=frame is {} observed?new{observed.ObservedAt,observed.Mode,observed.FsdReady,observed.PlayerOverride,observed.ExternalThreat,observed.GeometryVerified,
                observed.Sequence,observed.ShipEntity,observed.ReferenceWorld,observed.TargetExclusionRadius,
                observed.Position,observed.TargetPosition,observed.Forward,observed.Velocity,observed.TargetVelocity,target=observed.SelectedTarget?.Name,
                distanceMetres=double.IsFinite(observed.TargetDistance)?(double?)observed.TargetDistance:null,
                speedMetresPerSecond=double.IsFinite(observed.Speed)?(double?)observed.Speed:null,
                throttle=double.IsFinite(observed.Throttle)?(double?)observed.Throttle:null}:null;
            var observation=JsonSerializer.SerializeToElement(new{service="StationDeparture",processId,watchEntityId=frame?.ShipEntity.ToString(),issue=error??"",
                applicationControl=new{detail=((int)Elapsed().TotalSeconds/2).ToString()},sample=new{state=frame?.Mode.ToString()},
                elapsedSeconds=Elapsed().TotalSeconds,resumingOriginalLaunch=resumeOrigin is not null,current.Docked,current.Navigation.UndockedAt,statusFlags=current.Status?.Flags,
                departurePath=new{departurePath.OriginalDutyObserved,departurePath.OriginalDutyNaturallyEnded,departurePath.PathVerified,departurePath.WithdrawalReason,clearingMassLock},
                rawDockingState=LastTelemetry is {} telemetry?(int?)Number(telemetry,"rawDockingState"):null,
                rawRequestState=LastTelemetry is {} requestTelemetry?(int?)Number(requestTelemetry,"rawRequestState"):null,
                rawPad=LastTelemetry is {} padTelemetry?(int?)Number(padTelemetry,"rawPad"):null,
                frame=flight, docking=docking.ValueKind==JsonValueKind.Object?docking:(JsonElement?)null});
            if(DepartureObserved is {} handlers)foreach(Action<JsonElement> handler in handlers.GetInvocationList())try{handler(observation);}catch{/* Optional diagnostics cannot affect departure. */}
        }
        while(true)
        {
            token.ThrowIfCancellationRequested();var state=player();
            if(state.CurrentShipId!=initial.CurrentShipId||state.LoadGameAt!=initial.LoadGameAt||state.CommanderId!=initial.CommanderId||NavigationSafety.UnhealthyHull(state))
                throw new InvalidOperationException("离场中的飞船或安全状态改变。");
            if(NavigationSafety.HasExternalThreat(state))throw new FlightThreatPauseException();
            // An uncertain cancellation is observed once, never resubmitted.
            // Recovery begins well before the station's five-minute deadline.
            if(takeoverRequestedAt is {} requested&&Elapsed()-requested>=StationDepartureTakeoverPolicy.CancellationObservationLimit)
                throw new StationDepartureRecoveryException(origin.MarketId!.Value,Elapsed());
            try
            {
                var frame=await ReadSystemFlightAsync(token);
                var dock=LastTelemetry!.Value.GetProperty("docking");
                var nativeSource=StationDepartureContinuity.NativeDutySourceVerified(dock);
                departurePath.Observe(frame,origin,state,started,DateTimeOffset.UtcNow,nativeSource,
                    Flag(dock,"nativeLaunchActive"),Flag(dock,"nativeLaunchRequested"),takeoverRequestedAt is not null);
                ObserveDeparture(state,frame,dock);
                if(resumeOrigin is not null&&returnToOrigin&&frame.Mode==LocalFlightMode.Normal&&!frame.PlayerOverride&&!frame.ExternalThreat
                    &&Flag(dock,"verified")&&StationDepartureContinuity.NativeLaunchInactive(dock))
                {Report("原版离场任务已结束,保持正常同站返航停靠入口;未确认或执行向外推进。");break;}
                var action=StationDepartureTakeoverPolicy.Decide(frame,origin,state,started,Elapsed(),DateTimeOffset.UtcNow,
                    nativeSource,Flag(dock,"nativeLaunchActive"),Flag(dock,"nativeLaunchRequested"),Flag(dock,"nativeDutyActive"),Flag(dock,"verified"),departurePath);
                if(action==StationDepartureAction.ManualTakeover)throw new InvalidOperationException("玩家已主动接管离站,自动航程已停止。");
                if(action==StationDepartureAction.RecoverSession)StationDepartureWatchdog.Check(origin.MarketId!.Value,Elapsed());
                if(action==StationDepartureAction.Completed)
                {
                    ownsLaunch=false;
                    Report(takeoverRequestedAt is null?"已确认离站与质量锁定解除,正在接续原航程。":"已确认原版离场任务退出,工具箱接管后续自动驾驶。");
                    break;
                }
                if(action==StationDepartureAction.ContinueVerifiedDeparture)
                {
                    clearingMassLock=true;
                    Report("原版已完成安全出站,正沿已验证出站航向固定节流前进,直到解除质量锁定。");
                    // No steering, radial escape vector, boost or cancellation.
                    // Renew only on the same fresh outward path and body envelope.
                    await ApplySystemFlightAsync(new(SystemNavigationPhase.Preparing,"沿原版出站路径解除质量锁定",null,StationDeparturePathEvidence.ContinuationThrottle),
                        frame,origin,frame.ObservedAt.AddMilliseconds(250),token);
                }
                else if(clearingMassLock){clearingMassLock=false;await SuspendSystemFlightAsync();}
                if(action==StationDepartureAction.CancelNativeForTakeover&&takeoverRequestedAt is null)
                {
                    Report("离站接近等待期限;已确认站体质量锁定解除,正在接管自动驾驶。");
                    takeoverRequestedAt=Elapsed();
                    await ActionAsync("cancelLaunch",frame,frame.ObservedAt.AddMilliseconds(250),token);
                }
                else if(action!=StationDepartureAction.ContinueVerifiedDeparture)Report("正在等待原版安全离场;站内路径未确认时保持原版驾驶。");
            }
            catch(Exception error)when(NavigationRecovery.CanRetry(error,token))
            {
                ObserveDeparture(state,null,default,error.ToString());
                StationDepartureWatchdog.Check(origin.MarketId!.Value,Elapsed());
                await SuspendSystemFlightAsync();
            }
            await Task.Delay(80,token);
        }
        await ReleaseSystemFlightAsync();if(restoreDestination)await SelectAsync(destination,token);
    }
    private static void MatchTarget(JsonElement actual,SystemNavigationTarget target)
    {
        if(actual.GetProperty("key").GetInt32()!=target.NativeKey||Address(actual,"bodyAddress")!=target.BodyAddress||actual.GetProperty("kind").GetInt32()!=target.NativeKind
            ||actual.GetProperty("marketId").GetUInt32()!=(target.MarketId??uint.MaxValue)||actual.GetProperty("name").GetString()!=target.Name)
            throw new InvalidOperationException("游戏选中的地点已经改变,已停止星系内驾驶。");
    }
    private async Task RebindSceneAsync(JsonElement raw,PlayerState state,ulong entity,CancellationToken token)
    {
        var target=selected!;var now=DateTimeOffset.UtcNow;var journalNow=GameJournalClock.Now(state,now);
        bool Recent(DateTimeOffset? requested,DateTimeOffset? confirmed)=>requested is {} request&&confirmed is {} actual
            &&actual>=(request+(journalNow-now)).AddSeconds(-1)&&actual<=journalNow&&journalNow-actual<TimeSpan.FromSeconds(30);
        var entered=Recent(entryRequestedAt,state.Navigation.SupercruiseEnteredAt);
        var exited=Recent(exitRequestedAt??assistRequestedAt,state.Navigation.SupercruiseExitedAt);
        if(!state.InGame||state.CurrentShipId!=journalShip||state.CommanderId!=commander||state.LoadGameAt!=sessionAt||state.Navigation.CurrentSystemAddress!=target.SystemAddress)
            throw new InvalidOperationException("切换前后的飞船或会话不一致,已停止星系内驾驶。");
        if(!entered&&!exited)
        {
            if(new[]{entryRequestedAt,exitRequestedAt,assistRequestedAt}.Any(x=>x is {} requested&&now-requested<TimeSpan.FromSeconds(35)))
                throw new FlightTelemetryUnavailableException("正在等待游戏确认超巡切换。");
            throw new InvalidOperationException("未获游戏确认的飞船实体切换,已停止星系内驾驶。");
        }
        if(!Flag(raw.GetProperty("shipHealth"),"verified"))throw new FlightTelemetryUnavailableException("正在恢复连续血量遥测。");
        var transitionFsd=raw.GetProperty("rawFsdState").GetInt32();
        if(transitionFsd!=2&&!(cooldownFlightControls&&transitionFsd==1))throw new FlightTelemetryUnavailableException("正在等待超巡切换后的飞行状态就绪。");
        using var source=new NativeCurrentSystemSource(processId,gameHash,player);var snapshot=await source.ReadLocationsAsync(token);
        bool SameDestination(SystemLocation row)=>row.Name==target.Name&&row.NativeKind==target.NativeKind
            &&(target.MarketId is {} market?row.MarketId==market:row.WorldAddress==target.BodyAddress);
        var current=snapshot.Locations.SingleOrDefault(x=>x.Selected);
        if(snapshot.EntityId!=entity||current is not null&&!SameDestination(current))throw new InvalidOperationException("切换后的当前目标与原目的地不一致。");
        var row=snapshot.Locations.SingleOrDefault(SameDestination)??throw new FlightTelemetryUnavailableException("正在恢复目的地目录。");
        var binding=target with{NativeKey=row.Key,NativeKind=row.NativeKind,NativeIdentity=row.Identity,BodyAddress=row.WorldAddress};
        await ReleaseSystemFlightAsync();
        try{await SelectNativeAsync(binding,token);}
        catch(InvalidOperationException error)when(error.Message=="Stop flight control before selecting a local destination")
        {throw new FlightTelemetryUnavailableException("正在等待旧驾驶输入释放并恢复同一目的地。");}
        nativeSelected=binding;
        replacesEntity=boundEntity;boundEntity=entity;sceneTransitionAt=entered?state.Navigation.SupercruiseEnteredAt:state.Navigation.SupercruiseExitedAt;
        if(entered)entryRequestedAt=null;if(exited)exitRequestedAt=assistRequestedAt=null;
        attitude.Reset();
        // Only this validated scene change may attach protection to the new
        // local entity. Health monitoring remains alive while old inputs bar.
        if(ensureFlightProtection is not null)await ensureFlightProtection(token);
        await PhysicalInputPreparation.RequireAsync(pipe,token);
    }
    public async Task<SystemFlightFrame> ReadSystemFlightAsync(CancellationToken token)
    {
        var target=selected??throw new InvalidOperationException("星系内目标尚未确认。");var state=player();
        var raw=await pipe.CallAsync(new{op="read"},token);var entity=Address(raw,"entityId");
        if(!Flag(raw,"inGame"))throw new FlightTelemetryUnavailableException("正在等待驾驶舱场景切换完成。");
        var ship=state.Ships.SingleOrDefault(s=>s.ShipId==state.CurrentShipId);
        if(ship?.ConfigurationAt is null||ship.ConfigurationChanged||!Flag(raw,"localPlayerShipVerified"))throw new FlightTelemetryUnavailableException("等待当前飞船的完整驾驶状态。");
        var fingerprint=NativeSystemFlightObservation.LoadoutFingerprint(ship);
        if(last is {} previous&&previous.LoadoutFingerprint!=fingerprint)throw new InvalidOperationException("飞船装配已改变,已停止星系内驾驶。");
        if(boundEntity!=0&&entity!=boundEntity)await RebindSceneAsync(raw,state,entity,token);
        else if(boundEntity==0)boundEntity=entity;
        if(!ownsLaunch&&state.Docked&&state.MarketId==target.MarketId&&target.NeedsDocking&&last is {} arrival&&arrival.ShipEntity==entity)
        {
            // Actual Docked Journal identity is independent of the navigation
            // selection, which the original game may clear after docking.
            return last=arrival with{Sequence=raw.GetProperty("sequence").GetInt64(),ObservedAt=DateTimeOffset.FromUnixTimeMilliseconds(raw.GetProperty("observedAtUnixMilliseconds").GetInt64()),
                InGame=state.InGame,LoadoutFingerprint=fingerprint,Mode=LocalFlightMode.Docked,DockedMarketId=state.MarketId,Speed=0};
        }
        JsonElement local;
        try{local=await pipe.CallAsync(new{op="local.read"},token);}
        catch(InvalidOperationException error)when(error.Message=="Local target telemetry unavailable or stale")
        {
            LocalFlightTelemetryStatus? status=null;
            try
            {
                var report=await pipe.CallAsync(new{op="local.status",owner,generation},token);
                status=LocalFlightTelemetryStatus.Parse(report,generation,nativeSelected??target);
            }
            catch(InvalidDataException statusError){throw new InvalidOperationException("当前地点诊断身份未通过核对,未重新选择目标。",statusError);}
            catch(Exception statusError)when(NavigationRecovery.CanRetry(statusError,token)){/* Retain the original sampling failure when the read-only status is also in flight. */}
            if(status?.TargetChanged==true)throw new FlightDestinationChangedException(target.Name,status.ActualTarget,error);
            Exception samplingFailure=status is null?error:new IOException($"local.status: sampleStage={status.SampleStage}, selected={status.Selected}, telemetryReady={status.TelemetryReady}",error);
            throw new FlightTelemetryUnavailableException("正在重新核对星系内目标与飞行数据。",samplingFailure);
        }
        LastTelemetry=local;MatchTarget(local.GetProperty("target"),nativeSelected??target);
        if(!TravelPreparation.StatusConfirmed(state,DateTimeOffset.UtcNow))throw new FlightTelemetryUnavailableException("正在同步当前驾驶舱状态。");
        var world=Address(local,"referenceWorldAddress");var at=DateTimeOffset.FromUnixTimeMilliseconds(local.GetProperty("observedAtUnixMilliseconds").GetInt64());var now=DateTimeOffset.UtcNow;
        if(Address(local,"entityId")!=entity||world!=Address(raw,"referenceWorldAddress")||NativeFrameProjector.SystemAddressFromBody(world)!=target.SystemAddress
            ||!Flag(local,"geometryVerified")||!Flag(local,"velocityVerified")||now-at>TimeSpan.FromMilliseconds(300)||at>now)
            throw new FlightTelemetryUnavailableException("正在同步星系内目标、参考系和飞船速度。");
        var sc=Flag(local,"inSupercruise");var rawState=local.GetProperty("rawFsdState").GetInt32();var position=Vector(local,"position");var destination=Vector(local,"targetPosition");
        var forward=Vector(local,"forward");var up=Vector(local,"up");var assist=local.GetProperty("supercruiseAssist");var dock=local.GetProperty("docking");
        var installed=NavigationAssists.From(state);var obscured=Flag(assist,"obscured");Vector3d? clear=null;
        var geometry=raw.GetProperty("geometry");
        var targetExclusionRadius=StationDepartureEnvelope.ReadRadius(geometry,world,entity,target,destination,now);
        // Only the original assist may report obstruction. Geometry is used
        // afterwards to choose a clear bypass, never to take over its controls.
        if(obscured&&Flag(geometry,"fresh")&&Flag(geometry,"referenceFrameVerified")&&Address(geometry,"worldAddress")==world)
        {
            var offset=position-Vector(geometry,"position");
            var bodies=new List<StarEnvelope>();
            StarEnvelope? blocking=null;
            foreach(var body in geometry.GetProperty("nearbyBodies").EnumerateArray())
            {
                if(Address(body,"bodyAddress")==target.PlanetAddress&&target.IsSurface)continue;
                var centre=Vector(body,"centre")+offset;var radius=body.GetProperty("exclusionRadius").GetDouble();
                if(!double.IsFinite(radius)||radius<=0)throw new FlightTelemetryUnavailableException("等待天体避障包络。");
                var envelope=new StarEnvelope(0,"",centre,radius,null,null);bodies.Add(envelope);
                if(FlightGeometry.Obscures(envelope,position,destination))blocking??=envelope;
            }
            clear=FlightGeometry.ClearPerpendicularDirection(bodies,position,destination,
                last?.ClearDirection??(blocking is {} obstacle?FlightGeometry.FastClearDirection(obstacle,position,destination,up):up));
        }
        if(obscured&&clear is null)throw new FlightTelemetryUnavailableException("正在寻找被天体遮挡目标的进近方向。");
        var pad=local.GetProperty("rawPad").GetInt32();DockingClearance? clearance=null;
        var dockingState=local.GetProperty("rawDockingState").GetInt32();
        if(target.MarketId is {} market&&Flag(dock,"verified")&&Flag(dock,"clearanceGranted")&&dockingState is 3 or 4&&pad is >=0 and <255)
            // This is a short-lived permission observation. Every renewed lease
            // requires the original game to still report a valid grant and pad.
            clearance=new(market,DockingPermission.Granted,pad+1,at,at.AddMilliseconds(300));
        var frame=new SystemFlightFrame{Sequence=local.GetProperty("sequence").GetInt64(),ObservedAt=at,GameSha256=gameHash,LoadoutFingerprint=fingerprint,ShipEntity=entity,ReplacesShipEntity=replacesEntity,SceneTransitionAt=sceneTransitionAt,
            SystemAddress=target.SystemAddress,ReferenceWorld=world,SelectedTarget=target,GeometryVerified=true,InGame=Flag(raw,"inGame")&&state.InGame,PlayerOverride=Flag(raw,"playerOverride"),
            ExternalThreat=NavigationSafety.HasExternalThreat(state),Mode=NativeSystemFlightObservation.Mode(state.Docked&&state.MarketId==target.MarketId,sc,rawState,
                Flag(dock,"verified")&&dockingState is 5 or 7,cooldownFlightControls),
            DockedMarketId=state.Docked?state.MarketId:null,
            Position=position,Velocity=Vector(local,"velocity"),Forward=forward,Up=up,AngularVelocityDegrees=Flag(local,"angularVelocityVerified")?Vector(local,"angularVelocity"):null,TargetPosition=destination,TargetVelocity=Vector(local,"targetVelocity"),TargetExclusionRadius=targetExclusionRadius,
            TargetDistance=local.GetProperty("distanceMetres").GetDouble(),Speed=local.GetProperty("speedMetresPerSecond").GetDouble(),Throttle=local.GetProperty("throttle").GetDouble(),
            EnginePips=state.Status?.Pips is {Count:3} pips?pips[1]:null,
            MaximumNormalSpeed=local.GetProperty("normalSpeedLimit").GetDouble(),MaximumCruiseSpeed=local.GetProperty("cruiseSpeedLimit").GetDouble(),
            CruiseParametersVerified=Flag(local,"cruiseParametersVerified"),CruiseApproachSeconds=Number(local,"cruiseApproachSeconds"),CruiseApproachThrottle=Number(local,"cruiseApproachThrottle"),
            SafeDisengage=Flag(local,"safeDisengage"),SafeDropDistance=Number(local,"safeDropDistance"),SafeDropSpeed=Number(local,"safeDropSpeed"),
            BrakingAcceleration=local.GetProperty("accelerationLowerBound").GetDouble(),TranslationAcceleration=local.GetProperty("accelerationLowerBound").GetDouble(),LegalApproachSpeed=100,
            AssistAlignmentDegrees=5,TargetObscured=obscured,ClearDirection=clear,FsdReady=rawState==2&&!sc&&((state.Status?.Flags??0)&(1L<<16))==0,
            LandingGearDown=((state.Status?.Flags??0)&4)!=0,ThrusterBoostAvailable=Flag(local,"normalBoostAvailable"),Assists=installed.Available(Flag(dock,"computerAvailable"),useNativeSupercruiseAssist&&Flag(assist,"available")),HeatPercent=raw.GetProperty("heatPercent").GetDouble(),Clearance=clearance,
            SupercruiseAssist=NativeSystemFlightObservation.CruiseAssist(Flag(assist,"available"),Flag(assist,"enabled"),Flag(assist,"engaged"),Flag(assist,"matchesTarget"),sc),
            DockingAssist=NativeSystemFlightObservation.DockingAssist(installed.AutoDock&&Flag(dock,"computerAvailable"),Flag(dock,"verified"),
                clearance is not null&&Flag(dock,"nativeDutyActive")&&!Flag(dock,"nativeLaunchActive")),
            DockingAssistEnabled=installed.AutoDock&&Flag(dock,"computerAvailable")&&nativeDockingThrottle&&NativeSystemFlightObservation.DockingInputZeroConfirmed(local)};
        if(cruisePolicyPrepared&&!useNativeSupercruiseAssist&&(Flag(assist,"enabled")||Flag(assist,"engaged")))
            throw new InvalidOperationException("游戏超巡辅助重新取得控制,已停止工具箱驾驶以核对输入来源。");
        if(originalCruiseControl is not null&&!useNativeSupercruiseAssist&&sc)
        {
            var thermalExpiry=await originalCruiseControl.ReadThermalControlExpiryAsync(frame,token);
            frame=frame with{OriginalScoThermalControlVerified=thermalExpiry is not null,OriginalScoThermalControlExpiresAt=thermalExpiry};
        }
        last=frame;return frame;
    }
    public async Task ApplySystemFlightAsync(SystemFlightDecision decision,SystemFlightFrame reference,SystemNavigationTarget target,DateTimeOffset expiresAt,CancellationToken token)
    {
        if(ensureFlightProtection is not null)await ensureFlightProtection(token);
        if(selected is null||!selected.SameIdentity(target)||last?.Sequence!=reference.Sequence)throw new InvalidOperationException("驾驶指令与目标或飞行帧不一致。");
        if(expiresAt<=DateTimeOffset.UtcNow)throw new FlightTelemetryUnavailableException("正在刷新星系内飞行数据。");
        await UpdateOriginalCruiseAsync(decision,reference,nativeSelected??target,expiresAt,token);
        if(decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff))
        {
            emergencyExited=await Session.NativeFlightTestProtection.HandleFailedHandoffAsync(processId);
            if(emergencyExited)ownsController=false;return;
        }
        var actions=decision.Actions;
        if((actions&SystemNavigationAction.CancelOwnedAssists)!=0)
        {
            // The selected target and controller ownership have already been
            // validated above. Even the first frame can contain a surviving
            // original duty whose module became unavailable before selection.
            if(LastTelemetry is {} observed)
            {
                ownsCruiseAssist|=NativeSystemFlightObservation.CruiseNeedsCancellation(observed.GetProperty("supercruiseAssist"));
                ownsDockingAssist|=NativeSystemFlightObservation.DockingNeedsCancellation(observed.GetProperty("docking"));
            }
            if(ownsCruiseAssist)
            {
                if(LastTelemetry is {} cruise&&NativeSystemFlightObservation.CruiseNeedsCancellation(cruise.GetProperty("supercruiseAssist")))
                {
                    await pipe.CallAsync(new{op="local.axes.release",owner,generation},token);
                    await ActionAsync("cancelSupercruiseAssist",reference,expiresAt,token);
                    // A request acknowledgement is not an inactive-duty
                    // observation. Re-read before issuing competing inputs.
                    return;
                }
                ownsCruiseAssist=false;
            }
            if(ownsDockingAssist)
            {
                if(LastTelemetry is {} docking&&NativeSystemFlightObservation.DockingNeedsCancellation(docking.GetProperty("docking")))
                {
                    await pipe.CallAsync(new{op="local.axes.release",owner,generation},token);
                    await ActionAsync("cancelDockingAssist",reference,expiresAt,token);
                    return;
                }
                ownsDockingAssist=false;
            }
        }
        if(decision.Translation is {Length:>0})throw new NotSupportedException("空间站手动进场路径尚未通过验收。");
        if((decision.Actions&SystemNavigationAction.ReleaseFlightAxes)!=0)
        {
            // Adopt an already engaged original duty for our selected target.
            // A later module failure must still relinquish that duty before
            // application control can resume.
            ownsCruiseAssist|=reference.Assists.Supercruise&&reference.SupercruiseAssist==NativeAssistState.Engaged;
            ownsDockingAssist|=reference.Assists.AutoDock&&reference.DockingAssist==NativeAssistState.Engaged;
            attitude.Reset();
            await pipe.CallAsync(new{op="local.axes.release",owner,generation},token);
        }
        else
        {
            var aim=decision.Direction is {} direction?attitude.Update(new(){Sequence=reference.Sequence,ObservedAt=reference.ObservedAt,SystemAddress=reference.SystemAddress,
                LoadoutFingerprint=reference.LoadoutFingerprint,InSupercruise=reference.Mode==LocalFlightMode.Supercruise,
                EnginePips=reference.EnginePips,SpeedFraction=reference.Speed/Math.Max(1,reference.Mode==LocalFlightMode.Supercruise?reference.MaximumCruiseSpeed:reference.MaximumNormalSpeed),
                Forward=reference.Forward,Up=reference.Up},direction,decision.UpDirection??reference.Up,reference.AngularVelocityDegrees,decision.UpDirection is not null):new AttitudeInput();
            try
            {
                await pipe.CallAsync(new{op="local.apply",owner,generation,sequence=reference.Sequence,expiresAtUnixMilliseconds=expiresAt.ToUnixTimeMilliseconds(),
                    axes=new[]{0.0,0,0,aim.Yaw,aim.Pitch,aim.Roll},throttle=decision.Throttle??-1,
                    yieldOnDockingGrant=reference.Assists.AutoDock&&target.NeedsDocking&&decision.Phase is SystemNavigationPhase.NativeLanding or SystemNavigationPhase.RequestingDocking or SystemNavigationPhase.AwaitingClearance},token);
            }
            catch(InvalidOperationException error)when(error.Message==PhysicalInputPreparation.NativeDisabledContextPending)
            {
                await pipe.CallAsync(new{op="local.axes.release",owner,generation},token);
                if(originalCruiseControl is not null)await originalCruiseControl.ReleaseAsync(token);
                await PhysicalInputPreparation.RequireAsync(pipe,token);
                throw new FlightTelemetryUnavailableException("原始物理输入图已确认,正在重新读取当前飞行帧。",error);
            }
            if(decision.Direction is not null)attitude.ConfirmApplied(DateTimeOffset.UtcNow,aim);
        }
        if((actions&SystemNavigationAction.EnterSupercruise)!=0){await ActionAsync("enterSupercruise",reference,expiresAt,token);entryRequestedAt=DateTimeOffset.UtcNow;}
        if((actions&SystemNavigationAction.BoostThrusters)!=0&&reference.Mode==LocalFlightMode.Normal&&reference.ThrusterBoostAvailable
            &&reference.TargetDistance>SystemFlightNavigator.DockingContactDistance&&decision.Throttle==1)
            await ActionAsync("boostThrusters",reference,expiresAt,token);
        if((actions&SystemNavigationAction.RequestDocking)!=0)await ActionAsync("requestDocking",reference,expiresAt,token);
        if((actions&SystemNavigationAction.EnableSupercruiseAssist)!=0){ownsCruiseAssist=true;await ActionAsync("enableSupercruiseAssist",reference,expiresAt,token);assistRequestedAt=DateTimeOffset.UtcNow;}
        if((actions&SystemNavigationAction.EnableDockingAssist)!=0)
        {
            if(!nativeDockingThrottle)throw new NotSupportedException("当前插件尚未接入已验证的停靠节流阀归零接口,请更新插件后重启游戏。");
            ownsDockingAssist=true;
            await ActionAsync("zeroThrottleForDocking",reference,expiresAt,token);
        }
        if((actions&SystemNavigationAction.SafeDisengage)!=0)
        {
            if(!reference.SafeDisengage||reference.TargetDistance>=reference.SafeDropDistance||reference.Speed>=reference.SafeDropSpeed)throw new InvalidOperationException("尚未满足游戏原生安全脱离条件。");
            await ActionAsync("safeDisengage",reference,expiresAt,token);exitRequestedAt=DateTimeOffset.UtcNow;
        }
        if((actions&SystemNavigationAction.DeployLandingGear)!=0)
        {
            if(gameInputs is null)throw new NotSupportedException("起落架输入通道尚未连接。");
            var inputs=await gameInputs.ReadAsync(token);if(inputs.EntityId!=reference.ShipEntity)throw new InvalidDataException("起落架输入不是当前飞船。");await gameInputs.SetAsync(inputs,"landingGear",true,token);
        }
    }
    private async Task ActionAsync(string action,SystemFlightFrame frame,DateTimeOffset deadline,CancellationToken token)
    {
        if(deadline<=DateTimeOffset.UtcNow)throw new FlightTelemetryUnavailableException("正在刷新星系内操作数据。");
        if(action=="zeroThrottleForDocking"&&dockingResetPipe is not null){await OriginalDockingResetAsync(frame,deadline,token);return;}
        var queued=await pipe.CallAsync(new{op="local.action",owner,generation,sequence=frame.Sequence,expiresAtUnixMilliseconds=deadline.ToUnixTimeMilliseconds(),action},token);
        var actionGeneration=queued.GetProperty("actionGeneration").GetInt64();
        while(true)
        {
            var status=await pipe.CallAsync(new{op="local.action.status",owner,generation,actionGeneration},token);
            if(Flag(status,"applied"))return;
            if(!Flag(status,"pending"))
            {
                if(action=="requestDocking")throw new DockingRequestNotAppliedException();
                throw new FlightTelemetryUnavailableException("游戏条件尚未就绪,正在重新读取并重试。");
            }
            await Task.Delay(15,token);
        }
    }
    /// <summary>Send the original player speed-zero input without approach thrust.</summary>
    public async Task SetNormalThrottleZeroAsync(SystemFlightFrame frame,CancellationToken token)
    {
        if(frame.Mode!=LocalFlightMode.Normal||frame.PlayerOverride||frame.SupercruiseAssist==NativeAssistState.Engaged)
            throw new InvalidOperationException("普通飞行节流阀归零需要当前正常飞行状态。");
        if(ensureFlightProtection is not null)await ensureFlightProtection(token);
        await pipe.CallAsync(new{op="local.axes.release",owner,generation},token);
        await ActionAsync("setNormalThrottleZero",frame,frame.ObservedAt.AddMilliseconds(250),token);
    }
    /// <summary>Ordinary low-speed exit for cancellation/recovery; does not attest arrival at a station.</summary>
    public async Task LeaveSupercruiseSlowlyAsync(SystemFlightFrame frame,CancellationToken token)
    {
        if(frame.Mode!=LocalFlightMode.Supercruise||frame.Speed>100000||frame.SupercruiseAssist==NativeAssistState.Engaged)
            throw new InvalidOperationException("等待超巡减速后安全退出。");
        var target=selected??throw new InvalidOperationException("星系内目标尚未确认。");
        var deadline=frame.ObservedAt.AddMilliseconds(250);
        await ApplySystemFlightAsync(new(SystemNavigationPhase.Waiting,"超巡减速退出",null,0),frame,target,deadline,token);
        await ActionAsync("leaveSupercruiseSlowly",frame,deadline,token);
        exitRequestedAt=DateTimeOffset.UtcNow;
    }
    public async Task RequestLaunchAsync(CancellationToken token)
    {
        if(ensureFlightProtection is not null)await ensureFlightProtection(token);
        var target=selected??throw new InvalidOperationException("请先选择当前停靠站点。");var state=player();
        if(!state.Docked||state.MarketId!=target.MarketId||!NavigationAssists.From(state).AdvancedDocking)throw new InvalidOperationException("自动起飞需要当前停靠站点和已启用的高级自动起降模块。");
        var requested=DateTimeOffset.UtcNow;var requestedJournalAt=GameJournalClock.Now(state,requested);
        while(true)
        {
            token.ThrowIfCancellationRequested();
            var current=player();
            if(current.CurrentShipId!=state.CurrentShipId||current.LoadGameAt!=state.LoadGameAt||current.CommanderId!=state.CommanderId||NavigationSafety.UnhealthyHull(current))
                throw new InvalidOperationException("起飞前的飞船或安全状态改变。");
            if(NavigationSafety.HasExternalThreat(current))throw new FlightThreatPauseException();
            if(!current.Docked&&current.Navigation.UndockedAt>=requestedJournalAt.AddSeconds(-1)
                &&current.Navigation.UndockedAt<=GameJournalClock.Now(current,DateTimeOffset.UtcNow).AddSeconds(5))return;
            try
            {
                var frame=await ReadSystemFlightAsync(token);
                // Cancellation may arrive after the original launch request has
                // been queued but before its acknowledgement reaches this task.
                ownsLaunch=true;
                await ActionAsync("launch",frame,frame.ObservedAt.AddMilliseconds(250),token);return;
            }
            catch(Exception error)when(NavigationRecovery.CanRetry(error,token)&&DateTimeOffset.UtcNow-requested<TimeSpan.FromSeconds(15))
            {await Task.Delay(100,token);}
        }
    }
    public async Task SuspendSystemFlightAsync()
        =>await FlightControlCleanup.RunAllAsync(
            ()=>originalCruiseControl?.ReleaseAsync(cause:"telemetry-suspend")??Task.CompletedTask,
            async()=>{attitude.Reset();await pipe.CallAsync(new{op="local.suspend",owner});});
    public async Task ReleaseSystemFlightAsync()
        =>await FlightControlCleanup.RunAllAsync(
            ()=>originalCruiseControl?.ReleaseAsync()??Task.CompletedTask,
            CancelOriginalDockingResetAsync,
            ReleaseOriginalAssistsAsync,
            ReleasePrimaryFlightInputsAsync);
    private async Task ReleaseOriginalAssistsAsync()
    {
        if(emergencyExited)return;
            if(ownsLaunch)
            {
                var watch=Stopwatch.StartNew();
                DateTimeOffset? clearSince=null;
                while(true)
                {
                    try
                    {
                        // Do not send a cancellation against a cached, expired
                        // frame or leave a just-queued launch request alive.
                        var launch=await ReadSystemFlightAsync(CancellationToken.None);
                        if(launch.PlayerOverride)break;
                        if(LastTelemetry is {} local&&StationDepartureContinuity.NativeLaunchInactive(local.GetProperty("docking")))
                        {
                            // The launch acknowledgement can precede the next
                            // observation of its one-shot request. One empty
                            // pre-request frame must not finish cancellation.
                            clearSince??=DateTimeOffset.UtcNow;
                            if(watch.Elapsed>=TimeSpan.FromMilliseconds(500)&&DateTimeOffset.UtcNow-clearSince>=TimeSpan.FromMilliseconds(250))break;
                        }
                        else if(LastTelemetry is {} pending&&StationDepartureContinuity.NativeDutySourceVerified(pending.GetProperty("docking"))
                            &&NativeSystemFlightObservation.LaunchNeedsCancellation(pending.GetProperty("docking")))
                        {
                            clearSince=null;
                            await ActionAsync("cancelLaunch",launch,launch.ObservedAt.AddMilliseconds(250),CancellationToken.None);
                        }
                        else clearSince=null;
                        if(watch.Elapsed>TimeSpan.FromSeconds(3))throw new InvalidOperationException("游戏尚未确认自动起飞已取消。");
                    }
                    catch(Exception error)when(NavigationRecovery.CanRetry(error,CancellationToken.None)&&watch.Elapsed<TimeSpan.FromSeconds(3)){}
                    await Task.Delay(50);
                }
            }
            if(ownsCruiseAssist&&last is {PlayerOverride:false,Mode:LocalFlightMode.Supercruise} frame&&DateTimeOffset.UtcNow-frame.ObservedAt<TimeSpan.FromMilliseconds(150)
                &&LastTelemetry is {} telemetry&&Flag(telemetry.GetProperty("supercruiseAssist"),"enabled"))
                try{await ActionAsync("cancelSupercruiseAssist",frame,frame.ObservedAt.AddMilliseconds(250),CancellationToken.None);}catch(Exception error)when(NavigationRecovery.CanRetry(error,CancellationToken.None)){}
    }
    private async Task ReleasePrimaryFlightInputsAsync()
    {
        try
        {
            if(!emergencyExited)
            {
                var watch=Stopwatch.StartNew();
                while(true){var result=await pipe.CallAsync(new{op="release",owner});if(Flag(result,"released")){ownsController=false;break;}if(watch.Elapsed>TimeSpan.FromSeconds(3))throw new InvalidOperationException("等待游戏释放自有驾驶输入。");await Task.Delay(25);}
            }
        }
        finally{attitude.Reset();ownsCruiseAssist=ownsDockingAssist=false;ownsLaunch=false;}
    }
}

/// <summary>Original duty engagement can outlive its enable flag during a scene drop.</summary>
public static class NativeSystemFlightObservation
{
    public static bool ObserveOriginalArrival(JsonElement docking,int dockingState)=>Flag(docking,"verified")
        &&!LaunchNeedsCancellation(docking)&&(dockingState is 5 or 7||Flag(docking,"nativeDutyActive"));
    private static bool Flag(JsonElement value,string name)=>value.TryGetProperty(name,out var flag)&&flag.ValueKind==JsonValueKind.True;
    public static bool DockingInputZeroConfirmed(JsonElement local)
    {
        if(!local.TryGetProperty("throttle",out var throttle)||throttle.ValueKind!=JsonValueKind.Number
            ||!double.IsFinite(throttle.GetDouble())||Math.Abs(throttle.GetDouble())>.001
            ||!local.TryGetProperty("docking",out var docking))return false;
        static bool True(JsonElement value,string key)=>value.TryGetProperty(key,out var flag)&&flag.ValueKind==JsonValueKind.True;
        // A controller receipt is insufficient if the actual desired input
        // still contains the SCA's previous 75% throttle.
        return True(docking,"throttleZero")&&True(docking,"zeroThrottleInputSent");
    }
    public static string LoadoutFingerprint(ShipRecord ship)
    {
        static bool Assist(FittedModule module)=>module.Item.Equals("int_supercruiseassist",StringComparison.OrdinalIgnoreCase)
            ||module.Item.Equals("int_dockingcomputer_standard",StringComparison.OrdinalIgnoreCase)
            ||module.Item.Equals("int_dockingcomputer_advanced",StringComparison.OrdinalIgnoreCase);
        // Operational assist changes belong to the fallback state machine.
        // They must not masquerade as a physical loadout change and abort it.
        return Convert.ToHexStringLower(SHA256.HashData(JsonSerializer.SerializeToUtf8Bytes(new{ship.ShipId,ship.Type,ship.UnladenMass,
            Modules=ship.Modules.Select(m=>new{m.Slot,m.Item,Enabled=Assist(m)?(bool?)null:m.Enabled,Priority=Assist(m)?(int?)null:m.Priority,m.Engineering})},ToolboxSettings.JsonOptions)));
    }
    public static bool CruiseNeedsCancellation(JsonElement assist)=>
        assist.TryGetProperty("matchesTarget",out var matches)&&matches.ValueKind==JsonValueKind.True
        &&(assist.TryGetProperty("enabled",out var enabled)&&enabled.ValueKind==JsonValueKind.True
            ||assist.TryGetProperty("engaged",out var engaged)&&engaged.ValueKind==JsonValueKind.True);
    public static bool DockingNeedsCancellation(JsonElement docking)=>
        docking.TryGetProperty("nativeDutyActive",out var active)&&active.ValueKind==JsonValueKind.True
        &&!(docking.TryGetProperty("nativeLaunchActive",out var launch)&&launch.ValueKind==JsonValueKind.True);
    public static bool LaunchNeedsCancellation(JsonElement docking)=>
        docking.TryGetProperty("nativeLaunchActive",out var active)&&active.ValueKind==JsonValueKind.True
        ||docking.TryGetProperty("nativeLaunchRequested",out var requested)&&requested.ValueKind==JsonValueKind.True;
    public static LocalFlightMode Mode(bool docked,bool supercruise,int fsdState,bool nativeDocked=false,bool cooldownControls=false)=>docked?LocalFlightMode.Docked
        :nativeDocked||!(fsdState is 2 or 3||cooldownControls&&fsdState==1)?LocalFlightMode.Transition:supercruise?LocalFlightMode.Supercruise:LocalFlightMode.Normal;
    public static NativeAssistState CruiseAssist(bool available,bool enabled,bool engaged,bool matchesTarget,bool supercruise)=>
        !available?NativeAssistState.Unavailable:engaged&&matchesTarget&&supercruise?NativeAssistState.Engaged
        :enabled&&matchesTarget?NativeAssistState.Requested:NativeAssistState.Ready;
    public static NativeAssistState DockingAssist(bool available,bool verified,bool engaged)=>!available?NativeAssistState.Unavailable
        :verified&&engaged?NativeAssistState.Engaged:NativeAssistState.Ready;
}