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.Text.Json;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Services;
using XFEEDToolBox.Views;

namespace XFEEDToolBox;

public partial class App
{
    // Explicit QA entry only. Flight, departure, safe exit and docking remain
    // owned by the normal station-journey viewmodel and its existing guards.
    private static async Task RunScoAcceptanceAsync(string[] args,string reportPath)
    {
        reportPath=Path.GetFullPath(reportPath);
        if(File.Exists(reportPath)||File.Exists(reportPath+".jsonl"))throw new IOException("Use a new SCO acceptance report path.");
        var stationPath=GetArgument(args,"--accept-sco-station")??GetArgument(args,"--station-journey")
            ??throw new ArgumentException("--accept-sco-station must name the real destination station JSON.");
        if(args.Contains("--no-session-automation"))throw new ArgumentException("SCO flight acceptance cannot run with --no-session-automation.");
        var minutes=double.Parse(GetArgument(args,"--accept-sco-minutes")??"35",CultureInfo.InvariantCulture);
        if(!double.IsFinite(minutes)||minutes is <1 or >60)throw new ArgumentException("SCO acceptance duration must be 1..60 minutes.");
        var destination=JsonSerializer.Deserialize<Station>(await File.ReadAllTextAsync(stationPath),ToolboxSettings.JsonOptions)
            ??throw new InvalidDataException("SCO acceptance station is empty.");
        if(destination.MarketId<=0||destination.IsPlanetary)throw new InvalidDataException("SCO acceptance requires a real orbital station.");
        var resume=GetArgument(args,"--resume-cg-after-sco");
        if(resume is not null&&!File.Exists(resume))throw new FileNotFoundException("Original CG configuration is missing.",resume);
        Directory.CreateDirectory(Path.GetDirectoryName(reportPath)!);
        var started=DateTimeOffset.UtcNow;var phase="preflight";string? failure=null;var passed=false;
        var baseline=false;var trialUse=false;var trialPass=false;var promoted=false;var docked=false;
        var pid=0;long processStart=0;string hash="",fingerprint="";ulong entity=0,world=0,epoch=0,armedSteps=0;
        var initial=Runtime.Player;Task? journey=null;var expectedStop=false;var supervisorStop=false;var closed=false;var windowClosing=false;
        var userStopVersion=ViewModel.NavigationUserStopVersion;
        var exits=new List<JsonElement>();var stops=new List<JsonElement>();var lastExit="";var lastStop="";
        var domainFallbacks=new List<object>();
        var lineOptions=new JsonSerializerOptions(ToolboxSettings.JsonOptions){WriteIndented=false};
        JsonElement? finalStatus=null;var events=0;var referenceRequalifications=0;var logGate=new object();DateTimeOffset lastFrame=default,lastFingerprint=default;
        using var budget=new CancellationTokenSource(TimeSpan.FromMinutes(minutes));
        var token=budget.Token;
        await using var log=new StreamWriter(new FileStream(reportPath+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
        void Log(object value)
        {lock(logGate){if(closed||events>=4096)return;events++;log.WriteLine(JsonSerializer.Serialize(value,lineOptions));log.Flush();}}
        void StopOwned()
        {
            if(journey is {IsCompleted:false}&&ViewModel.NavigationActive)
                ViewModel.StopNavigationForScoAcceptance();
        }
        void WindowClosed(object sender,WindowEventArgs e){windowClosing=true;if(!budget.IsCancellationRequested)budget.Cancel();StopOwned();}
        void Progress(FlightAutomationProgress value)
        {
            Log(new{kind="journey-progress",at=DateTimeOffset.UtcNow,phase,value});
            if(value.Stage is FlightAutomationStage.Failed||value.Stage==FlightAutomationStage.Stopped&&!expectedStop)
            {
                supervisorStop=true;
                MainWindow.DispatcherQueue.TryEnqueue(()=>{if(!budget.IsCancellationRequested)budget.Cancel();StopOwned();});
            }
        }
        void Check()
        {
            token.ThrowIfCancellationRequested();
            if(windowClosing||ViewModel.NavigationUserStopVersion!=userStopVersion)
                throw new OperationCanceledException("User Stop or window close cancelled SCO acceptance.");
            if(File.Exists(reportPath+".stop"))throw new OperationCanceledException("SCO acceptance stop file received.");
            var player=Runtime.Player;
            if(!player.InGame||player.CommanderId!=initial.CommanderId||player.CurrentShipId!=initial.CurrentShipId||player.LoadGameAt!=initial.LoadGameAt)
                throw new InvalidDataException("Commander, ship or real game session changed during SCO acceptance.");
            if(!player.CargoReliable||player.CargoCount!=0||NavigationSafety.UnhealthyHull(player)||NavigationSafety.HasExternalThreat(player))
                throw new InvalidOperationException("Real cargo, health or threat protection stopped SCO acceptance.");
            using var process=Process.GetProcessById(pid);
            if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=processStart)throw new InvalidDataException("Game process identity changed.");
            if(DateTimeOffset.UtcNow-lastFingerprint>=TimeSpan.FromSeconds(1))
            {
                lastFingerprint=DateTimeOffset.UtcNow;
                var ship=player.Ships.SingleOrDefault(s=>s.ShipId==player.CurrentShipId)??throw new InvalidDataException("Current ship is missing.");
                if(NativeSystemFlightObservation.LoadoutFingerprint(ship)!=fingerprint)throw new InvalidDataException("Ship loadout changed.");
            }
            if(supervisorStop)throw new OperationCanceledException("Owned station journey stopped or failed.");
        }
        async Task<JsonElement?> ReadAsync(string service)
        {
            using var ping=CancellationTokenSource.CreateLinkedTokenSource(token);ping.CancelAfter(TimeSpan.FromSeconds(2));
            try{return await new NavigationPipeClient(pid,service).CallAsync(new{op="read"},ping.Token);}
            catch(Exception e)when(NavigationRecovery.CanRetry(e,token)){return null;}
        }
        void Observe(JsonElement status)
        {
            if(ScoQaNumber(status,"processId")!=pid||ScoQaText(status,"gameSha256")!=hash||ScoQaText(status,"service")!=NativeCruiseControlChannel.Service)
                throw new InvalidDataException("SCO service identity differs from this acceptance process.");
            finalStatus=status.Clone();var sample=ScoQaObject(status,"sample");var policy=ScoQaObject(status,"scoExitEstimate");
            var receipt=ScoQaObject(policy,"lastExitReceipt");
            if(receipt.ValueKind==JsonValueKind.Object)
            {
                var key=receipt.GetRawText();
                if(key!=lastExit){lastExit=key;if(exits.Count>=16)throw new InvalidDataException("Unexpected repeated exit receipts.");exits.Add(receipt.Clone());Log(new{kind="complete-exit-receipt",phase,receipt});}
            }
            var stop=ScoQaObject(status,"lastOriginalStop");
            if(stop.ValueKind==JsonValueKind.Object&&ScoQaNumber(stop,"observedAtUnixMilliseconds")>=started.ToUnixTimeMilliseconds())
            {
                var key=stop.GetRawText();if(key!=lastStop){lastStop=key;if(stops.Count>=16)throw new InvalidDataException("Unexpected repeated original stops.");stops.Add(stop.Clone());Log(new{kind="original-stop-receipt",phase,stop});}
            }
            if(ScoQaFlag(sample,"playerOverride")||ScoQaFlag(sample,"healthEmergency"))throw new OperationCanceledException("Native manual takeover or health protection.");
            if(DateTimeOffset.UtcNow-lastFrame>=TimeSpan.FromSeconds(1))
            {
                lastFrame=DateTimeOffset.UtcNow;
                Log(new{kind="sco-frame",at=lastFrame,phase,sampleReady=ScoQaFlag(status,"sampleReady"),
                    state=ScoQaFinite(sample,"state"),entityId=ScoQaText(sample,"entityId"),referenceWorld=ScoQaText(sample,"referenceWorldAddress"),
                    speed=ScoQaFinite(sample,"speedMetresPerSecond"),distance=ScoQaFinite(sample,"distanceMetres"),
                    nativeEpoch=ScoQaFinite(sample,"originalUpdateClockEpoch"),nativeSteps=ScoQaFinite(sample,"originalUpdateCompletedSteps"),
                    nativeSimulationSeconds=ScoQaFinite(sample,"originalUpdateCompletedSimulationSeconds"),
                    approach=ScoQaMaybe(ScoQaObject(sample,"scoExitApproach")),issue=ScoQaText(policy,"issue"),proofAvailable=ScoQaFlag(policy,"proofAvailable")});
            }
        }
        async Task<JsonElement?> PollAsync()
        {
            Check();var native=await ReadAsync("Navigation");
            if(native is {} main&&(ScoQaFlag(main,"playerOverride")||ScoQaFlag(ScoQaObject(main,"shipHealth"),"emergency")))
                throw new OperationCanceledException("Native manual takeover or health emergency.");
            var status=await ReadAsync(NativeCruiseControlChannel.Service);if(status is {} current)Observe(current);
            Check();return status;
        }
        bool Ready(JsonElement status,bool sameBinding)
        {
            var sample=ScoQaObject(status,"sample");var at=ScoQaNumber(sample,"observedAtUnixMilliseconds");var now=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            if(!ScoQaFlag(status,"sampleReady")||!ScoQaFlag(sample,"verified")||!ScoQaFlag(sample,"inSupercruise")||ScoQaNumber(sample,"rawFsdState")!=2
                ||!ScoQaFlag(sample,"originalUpdateClockValid")
                ||!double.IsFinite(at)||at>now||now-at>200||ScoQaNumber(ScoQaObject(sample,"target"),"marketId")!=destination.MarketId)return false;
            if(sameBinding&&(ScoQaId(sample,"entityId")!=entity||ScoQaId(sample,"referenceWorldAddress")!=world))
            {
                var nextWorld=ScoQaId(sample,"referenceWorldAddress");
                if(ScoQaId(sample,"entityId")==entity&&nextWorld!=0&&nextWorld!=world
                    &&(journey is {IsCompleted:false}||expectedStop))throw new ScoQaReferenceChangedException(world,nextWorld);
                throw new InvalidDataException("SCO baseline binding changed before trial/promotion.");
            }
            return true;
        }
        Runtime.FlightAutomation.Progress+=Progress;
        MainWindow.Closed+=WindowClosed;
        using var cancellation=token.Register(()=>MainWindow.DispatcherQueue.TryEnqueue(StopOwned));
        try
        {
            if(!initial.InGame||initial.CommanderId.Length==0||initial.CurrentShipId is null||initial.LoadGameAt is null||!initial.CargoReliable||initial.CargoCount!=0)
                throw new InvalidOperationException("SCO QA requires the identified empty current ship in the real game.");
            if(Runtime.FlightAutomation.IsActive||ViewModel.RunActive||ViewModel.NavigationActive||ViewModel.SystemFlightActive)
                throw new InvalidOperationException("Another task is active; QA will not replace it.");
            var target=await new WindowsGameSessionDriver(()=>Runtime.Player,()=>Runtime.Settings.PluginGameExecutable).FindTargetAsync(token);
            pid=target.ProcessId;processStart=target.StartedUtcTicks;
            var hello=await new NavigationPipeClient(pid).CallAsync(new{op="describe"},token);
            hash=ScoQaText(hello,"gameSha256");if(hash!=XFEEDToolBox.Core.Native.NativeSystemLocationReader.GameSha256||ScoQaNumber(hello,"processId")!=pid)
                throw new InvalidDataException("Pinned game build is unverified.");
            fingerprint=NativeSystemFlightObservation.LoadoutFingerprint(initial.Ships.Single(s=>s.ShipId==initial.CurrentShipId));
            ViewModel.DemoMode=false;AppShellPage.Current!.Navigate(typeof(NavigationPage));Check();
            var pipe=new NavigationPipeClient(pid,NativeCruiseControlChannel.Service);
            var existing=await ReadAsync(NativeCruiseControlChannel.Service);
            if(existing is {} old)
            {
                Observe(old);if(ScoQaFlag(old,"owned"))throw new InvalidOperationException("An existing SCO owner is active.");
                await pipe.CallAsync(new{op="sco.exit.configure",mode="shadow"},token);
            }
            while(true)
            {
            try
            {
            Check();expectedStop=false;phase="baseline";var baselineStarted=DateTimeOffset.UtcNow;
            Log(new{kind="stage",phase,started,baselineStarted,referenceRequalifications,stationPath,destination,pid,processStart,fingerprint,minutes});
            if(journey is null||journey.IsCompleted)journey=ViewModel.RunStationJourneyAsync(destination);
            ulong originalTrialDeadline=0;
            while(true)
            {
                var status=await PollAsync();
                if(status is {} current&&Ready(current,false)&&ScoQaFlag(ScoQaObject(current,"scoExitEstimate"),"proofAvailable")
                    &&ScoQaNumber(ScoQaObject(current,"scoExitEstimate"),"provenAtUnixMilliseconds")>=baselineStarted.ToUnixTimeMilliseconds())
                {
                    var policy=ScoQaObject(current,"scoExitEstimate");var receipt=ScoQaObject(policy,"lastExitReceipt");var sample=ScoQaObject(current,"sample");
                    if(ScoQaText(policy,"trustedLoadoutFingerprint")!=fingerprint||!ScoQaFlag(receipt,"pass")||ScoQaFlag(receipt,"trial")||ScoQaFlag(receipt,"production")
                        ||ScoQaNumber(receipt,"endCompletedSteps")<=ScoQaNumber(receipt,"startCompletedSteps")||ScoQaNumber(receipt,"integratedPathDistanceMetres")<=0)
                        throw new InvalidDataException("Baseline lacks a complete same-session native exit receipt.");
                    entity=ScoQaId(sample,"entityId");world=ScoQaId(sample,"referenceWorldAddress");epoch=(ulong)ScoQaNumber(sample,"originalUpdateClockEpoch");
                    if(entity==0||world==0||ScoQaId(receipt,"entityId")!=entity||ScoQaId(receipt,"referenceWorldAddress")!=world)
                        throw new InvalidDataException("Baseline proof belongs to another native binding.");
                    baseline=true;break;
                }
                if(journey.IsCompleted)throw new InvalidOperationException("Baseline journey ended before a complete SCO proof.");
                await Task.Delay(100,token);
            }
            phase="release-baseline";expectedStop=true;StopOwned();await journey.WaitAsync(TimeSpan.FromSeconds(15),token);
            while(true)
            {
                var status=await PollAsync();
                if(status is {} idle&&Ready(idle,true)&&!ScoQaFlag(idle,"owned")&&ScoQaNumber(idle,"leaseExpiresAtUnixMilliseconds")==0
                    &&ScoQaFlag(ScoQaObject(idle,"scoExitEstimate"),"currentSourceDomainVerified"))
                {
                    armedSteps=(ulong)ScoQaNumber(ScoQaObject(idle,"sample"),"originalUpdateCompletedSteps");
                    originalTrialDeadline=(ulong)DateTimeOffset.UtcNow.AddMinutes(10).ToUnixTimeMilliseconds();
                    await pipe.CallAsync(new{op="sco.exit.configure",mode="trial",entityId=entity,referenceWorldAddress=world,
                        expiresAtUnixMilliseconds=originalTrialDeadline,maximumBursts=2},token);break;
                }
                await Task.Delay(100,token);
            }
            Check();expectedStop=false;phase="trial";Log(new{kind="stage",phase,entity,world,epoch,armedSteps});
            var consumedTrialSteps=armedSteps;var continuedFallbacks=0;var attemptTrialUse=false;var attemptStartedAt=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
            journey=ViewModel.RunStationJourneyAsync(destination);
            while(true)
            {
                var status=await PollAsync();
                if(status is {} current&&Ready(current,true))
                {
                    var sample=ScoQaObject(current,"sample");var choice=ScoQaObject(sample,"scoExitApproach");
                    var currentPolicy=ScoQaObject(current,"scoExitEstimate");
                    if(ScoQaNumber(sample,"originalUpdateClockEpoch")!=epoch||ScoQaText(currentPolicy,"mode")!="bounded-trial"
                        ||ScoQaNumber(currentPolicy,"trialExpiresAtUnixMilliseconds")!=originalTrialDeadline
                        ||DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()>=(long)originalTrialDeadline
                        ||ScoQaObject(currentPolicy,"lastPolicyRevocationFailure").ValueKind!=JsonValueKind.Null)
                        throw new InvalidOperationException("Original trial clock, deadline or authorization was revoked.");
                    if(ScoQaFlag(choice,"useEstimatedStop")&&ScoQaText(choice,"mode")=="trial")trialUse=attemptTrialUse=true;
                    var stop=ScoQaObject(current,"lastOriginalStop");var gate=ScoQaObject(stop,"gate");var stopChoice=gate.ValueKind==JsonValueKind.Object?ScoExitApproachDecision.Read(gate):null;
                    var stopAt=ScoQaNumber(stop,"observedAtUnixMilliseconds");
                    var actualEstimatedStop=ScoQaFlag(stop,"nativeMethodCalled")&&!ScoQaFlag(stop,"argument")&&ScoQaText(stop,"nativeMethodRva")=="2eaacd0"
                        &&ScoQaText(stop,"reason")=="approach-or-alignment"&&ScoQaId(stop,"entityId")==entity&&ScoQaId(stop,"referenceWorldAddress")==world
                        &&ScoQaFlag(gate,"identityVerified")&&ScoQaFlag(gate,"targetVerified")&&ScoQaFlag(gate,"geometryVerified")
                        &&stopChoice is {UseEstimatedStop:true,Mode:"trial"}&&double.IsFinite(stopAt)&&stopAt>=attemptStartedAt
                        &&stopChoice.TargetKey==ScoQaNumber(ScoQaObject(sample,"target"),"key")
                        &&stopChoice.Applies(entity,world,stopChoice.TargetKey,(ulong)stopAt)
                        &&stopChoice.RequiresStop(ScoQaNumber(gate,"distanceMetres"),ScoQaNumber(gate,"speedMetresPerSecond"),true,
                            ScoQaFlag(gate,"cruiseParametersVerified"),ScoQaNumber(gate,"cruiseApproachSeconds"),entity,world,stopChoice.TargetKey,(ulong)stopAt);
                    if(actualEstimatedStop)trialUse=attemptTrialUse=true;
                    var receipt=ScoQaObject(ScoQaObject(current,"scoExitEstimate"),"lastExitReceipt");
                    if(receipt.ValueKind==JsonValueKind.Object&&ScoQaNumber(receipt,"clockEpoch")==epoch&&ScoQaNumber(receipt,"endCompletedSteps")>consumedTrialSteps)
                    {
                        if(!ScoQaFlag(receipt,"pass"))throw new InvalidOperationException("Trial exit failed: "+ScoQaText(receipt,"reason"));
                        if(ScoQaId(receipt,"entityId")!=entity||ScoQaId(receipt,"referenceWorldAddress")!=world
                            ||ScoQaNumber(receipt,"targetKey")!=ScoQaNumber(ScoQaObject(sample,"target"),"key"))
                            throw new InvalidDataException("Trial exit receipt belongs to another ship, scene or target.");
                        if(ScoQaNumber(receipt,"startCompletedSteps")<=consumedTrialSteps)
                            throw new InvalidDataException("Trial exit receipt predates the current attempt.");
                        if(!ScoQaFlag(receipt,"trial")||!attemptTrialUse||!actualEstimatedStop)
                        {
                            var continuation=new ScoTrialContinuationContext(pid,hash,fingerprint,entity,world,epoch,
                                (int)ScoQaNumber(ScoQaObject(sample,"target"),"key"),consumedTrialSteps,originalTrialDeadline,continuedFallbacks);
                            if(!ScoTrialFallbackContinuation.CanContinue(current,continuation,DateTimeOffset.UtcNow,out var why))
                                throw new InvalidOperationException("Only an actual estimated approach stop may pass; fallback cannot continue: "+why);
                            var consumedEnd=(ulong)ScoQaNumber(receipt,"endCompletedSteps");continuedFallbacks++;
                            var fallback=new{at=DateTimeOffset.UtcNow,entity,world,epoch,attempt=1,receiptStart=ScoQaNumber(receipt,"startCompletedSteps"),receiptEnd=consumedEnd,
                                attemptTrialUse,actualEstimatedStop,reason=why,originalTrialDeadline,remainingNativeBursts=1,deadlineExtended=false,rearmed=false};
                            domainFallbacks.Add(fallback);Log(new{kind="calibrated-domain-fallback",phase,fallback});
                            phase="release-domain-fallback";expectedStop=true;StopOwned();await journey.WaitAsync(TimeSpan.FromSeconds(15),token);
                            while(true)
                            {
                                Check();if((ulong)DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()>=originalTrialDeadline)
                                    throw new InvalidOperationException("Original trial authorization expired before its remaining burst.");
                                var next=await PollAsync();
                                if(next is {} idle&&Ready(idle,true))
                                {
                                    var policy=ScoQaObject(idle,"scoExitEstimate");var freshSample=ScoQaObject(idle,"sample");
                                    if(ScoQaNumber(freshSample,"originalUpdateClockEpoch")!=epoch||ScoQaText(policy,"mode")!="bounded-trial"
                                        ||ScoQaNumber(policy,"trialExpiresAtUnixMilliseconds")!=originalTrialDeadline||ScoQaNumber(policy,"maximumTrialBurstsRemaining")!=1
                                        ||ScoQaObject(policy,"lastPolicyRevocationFailure").ValueKind!=JsonValueKind.Null)
                                        throw new InvalidOperationException("Original remaining trial authorization changed during release.");
                                    if(!ScoQaFlag(idle,"owned")&&ScoQaNumber(idle,"leaseExpiresAtUnixMilliseconds")==0&&ScoQaNumber(freshSample,"state")==0
                                        &&ScoQaFlag(policy,"currentSourceDomainVerified"))
                                    {
                                        if(ScoQaNumber(ScoQaObject(policy,"lastExitReceipt"),"endCompletedSteps")!=consumedEnd)
                                            throw new InvalidDataException("Fallback receipt changed during release.");
                                        if(!ScoTrialFallbackContinuation.CanContinue(idle,continuation,DateTimeOffset.UtcNow,out why))
                                            throw new InvalidOperationException("Remaining trial burst lost its verified fallback receipt: "+why);
                                        consumedTrialSteps=Math.Max(consumedEnd,(ulong)ScoQaNumber(freshSample,"originalUpdateCompletedSteps"));break;
                                    }
                                }
                                await Task.Delay(100,token);
                            }
                            Check();expectedStop=false;phase="trial";attemptTrialUse=false;attemptStartedAt=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
                            Log(new{kind="remaining-trial-burst",phase,attempt=2,consumedTrialSteps,originalTrialDeadline,remainingNativeBursts=1,deadlineExtended=false,rearmed=false});
                            journey=ViewModel.RunStationJourneyAsync(destination);continue;
                        }
                        trialPass=true;break;
                    }
                }
                if(journey.IsCompleted)throw new InvalidOperationException("Trial journey ended without a verified estimated exit.");
                await Task.Delay(100,token);
            }
            // Promotion is a policy change on an idle channel. Release our
            // journey explicitly instead of racing its next automatic burst.
            phase="release-trial";expectedStop=true;StopOwned();await journey.WaitAsync(TimeSpan.FromSeconds(15),token);
            phase="promote";
            while(true)
            {
                var status=await PollAsync();
                if(status is {} current&&Ready(current,true)&&!ScoQaFlag(current,"owned")&&ScoQaNumber(current,"leaseExpiresAtUnixMilliseconds")==0
                    &&ScoQaNumber(ScoQaObject(current,"sample"),"state")==0
                    &&ScoQaFlag(ScoQaObject(current,"scoExitEstimate"),"currentSourceDomainVerified"))
                {
                    var result=await pipe.CallAsync(new{op="sco.exit.configure",mode="production",entityId=entity,referenceWorldAddress=world,
                        expiresAtUnixMilliseconds=DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()},token);
                    Observe(result);promoted=ScoQaFlag(ScoQaObject(result,"scoExitEstimate"),"productionAvailable");
                    if(!promoted)throw new InvalidDataException("Production promotion did not confirm its bounded configuration.");break;
                }
                await Task.Delay(100,token);
            }
            break;
            }
            catch(ScoQaReferenceChangedException change)when(referenceRequalifications<3)
            {
                Check();referenceRequalifications++;
                Log(new{kind="natural-reference-requalification",at=DateTimeOffset.UtcNow,change.PreviousWorld,change.CurrentWorld,referenceRequalifications,
                    maximumRequalifications=3,originalBudgetUnchanged=true,oldProofReused=false});
                await pipe.CallAsync(new{op="sco.exit.configure",mode="shadow"},token);
                baseline=trialUse=trialPass=promoted=false;entity=world=epoch=armedSteps=0;
                // Keep the healthy ordinary journey running. Only restart it
                // if this QA already released it; user Stop remains terminal.
                Check();
            }
            }
            Check();expectedStop=false;phase="dock";Log(new{kind="stage",phase,promoted});
            journey=ViewModel.RunStationJourneyAsync(destination);
            while(!journey.IsCompleted){await PollAsync();await Task.Delay(100,token);}
            await journey;Check();var actual=Runtime.Player;
            docked=actual.Docked&&actual.MarketId==destination.MarketId&&actual.SystemName.Equals(destination.SystemName,StringComparison.OrdinalIgnoreCase)
                &&actual.CargoReliable&&actual.CargoCount==0&&Runtime.FlightAutomation.Stage==FlightAutomationStage.Completed;
            if(!docked)throw new InvalidOperationException("Real empty-ship docking at the acceptance station was not confirmed.");
            passed=baseline&&trialUse&&trialPass&&promoted&&docked;
        }
        catch(Exception error){failure=error.ToString();passed=false;}
        finally
        {
            expectedStop=true;StopOwned();
            if(journey is {IsCompleted:false})try{await journey.WaitAsync(TimeSpan.FromSeconds(15));}catch(Exception e){failure=(failure??"")+" / release: "+e.Message;passed=false;}
            Runtime.FlightAutomation.Progress-=Progress;
            if(!passed&&pid!=0)try{await new NavigationPipeClient(pid,NativeCruiseControlChannel.Service).CallAsync(new{op="sco.exit.configure",mode="shadow"});}catch(Exception e){Log(new{kind="strategy-release-unconfirmed",message=e.Message});}
        }
        phase="report";
        await File.WriteAllTextAsync(reportPath,JsonSerializer.Serialize(new
        {
            started,completedAt=DateTimeOffset.UtcNow,passed,failure,stationPath,destination,pid,processStart,fingerprint,baseline,trialUse,trialPass,promoted,docked,
            actual=new{Runtime.Player.SystemName,Runtime.Player.MarketId,Runtime.Player.Docked,Runtime.Player.CargoCount,Runtime.Player.CargoReliable,Runtime.Player.CurrentShipId},
            totalMinutes=minutes,events,referenceRequalifications,exits,stops,domainFallbacks,lastPolicy=finalStatus is {} final?ScoQaMaybe(ScoQaObject(final,"scoExitEstimate")):null,
            resumeCgConfiguration=resume,realJournalUsed=true,normalStationJourneyUsed=true,osInputSent=false,journalWrites=0,navRouteWrites=0
        },new JsonSerializerOptions(ToolboxSettings.JsonOptions){WriteIndented=true}));
        lock(logGate){log.Flush();closed=true;}
        if(passed&&resume is not null)
        {
            // The completed flight receipt is historical evidence, not a new
            // authorization after Stop/close received while writing it.
            try{await PollAsync();Check();}
            catch(Exception error)
            {
                MainWindow.Closed-=WindowClosed;
                await File.WriteAllTextAsync(reportPath+".resume.json",JsonSerializer.Serialize(new
                {at=DateTimeOffset.UtcNow,scoAcceptancePassed=true,resumeStarted=false,reason=error.Message},lineOptions));
                if(!windowClosing)Runtime.Log("SCO实机验收","验收已完成;接续跑商前状态或运行授权改变,未启动跑商。");
                return;
            }
            MainWindow.AppWindow.Show(false);AppShellPage.Current!.Navigate(typeof(PlannerPage));
            var resumeAuthorized=false;
            try
            {
                await ViewModel.RunAutomaticTradeAsync(resume,()=>
                {
                    Check();
                    if(Runtime.FlightAutomation.IsActive||ViewModel.RunActive||ViewModel.NavigationActive||ViewModel.SystemFlightActive)
                        throw new InvalidOperationException("Another task started before the CG handoff; its control remains unchanged.");
                    MainWindow.Closed-=WindowClosed;resumeAuthorized=true;
                });
            }
            catch(Exception error)when(!resumeAuthorized)
            {
                MainWindow.Closed-=WindowClosed;
                await File.WriteAllTextAsync(reportPath+".resume.json",JsonSerializer.Serialize(new
                {at=DateTimeOffset.UtcNow,scoAcceptancePassed=true,resumeStarted=false,reason=error.Message},lineOptions));
                if(!windowClosing)Runtime.Log("SCO实机验收","配置准备期间状态或运行授权改变,未启动跑商。");
            }
        }
        else
        {
            MainWindow.Closed-=WindowClosed;
            if(!passed){ViewModel.ShowError("SCO 实机验收未通过:"+(failure??"unknown"));Runtime.Log("SCO实机验收",failure??"unknown");MainWindow.Close();}
        }
    }
    private static JsonElement ScoQaObject(JsonElement value,string name)=>value.ValueKind==JsonValueKind.Object&&value.TryGetProperty(name,out var item)?item:default;
    private static string ScoQaText(JsonElement value,string name){var item=ScoQaObject(value,name);return item.ValueKind==JsonValueKind.String?item.GetString()??"":"";}
    private static bool ScoQaFlag(JsonElement value,string name)=>ScoQaObject(value,name).ValueKind==JsonValueKind.True;
    private static double ScoQaNumber(JsonElement value,string name){var item=ScoQaObject(value,name);return item.ValueKind==JsonValueKind.Number&&item.TryGetDouble(out var result)?result:double.NaN;}
    private static double? ScoQaFinite(JsonElement value,string name){var result=ScoQaNumber(value,name);return double.IsFinite(result)?result:null;}
    private static JsonElement? ScoQaMaybe(JsonElement value)=>value.ValueKind is JsonValueKind.Undefined or JsonValueKind.Null?null:value;
    private static ulong ScoQaId(JsonElement value,string name)=>ulong.TryParse(ScoQaText(value,name),NumberStyles.None,CultureInfo.InvariantCulture,out var result)?result:0;
    private sealed class ScoQaReferenceChangedException(ulong previousWorld,ulong currentWorld):Exception("Natural reference world changed; a new same-binding baseline is required.")
    {
        public ulong PreviousWorld{get;}=previousWorld;
        public ulong CurrentWorld{get;}=currentWorld;
    }
}