using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
/// <summary>One explicitly admitted empty SCO pulse, then the original Homer return. No SCA, cargo order, client creation or observer creation.</summary>
internal static class SCOControlFlightAcceptance
{
private const double LightSecond=299792458;
private const string FuelScope="Original SCO energy curve over pressure 0..1, when consuming; excludes other consumers and fuel scooping";
// Unknown local policy estimates stay explicitly unknown in evidence. The
// original samples and all finite-number acceptance predicates are unchanged.
private static readonly JsonSerializerOptions EvidenceJson=new(ToolboxSettings.JsonOptions){NumberHandling=JsonNumberHandling.AllowNamedFloatingPointLiterals};
private static async Task WriteEvidenceAsync(Channel<object> channel,TextWriter sink,Action<Exception> failed)
{
try{await foreach(var value in channel.Reader.ReadAllAsync()){await sink.WriteLineAsync(JsonSerializer.Serialize(value,EvidenceJson));await sink.FlushAsync();}}
catch(Exception error){channel.Writer.TryComplete(error);failed(error);throw;}
}
private static async Task CompleteEvidenceAsync(Channel<object> channel,Task writer,Action<Exception> failed,Func<Task> save)
{
channel.Writer.TryComplete();try{await writer;}catch(Exception error){failed(error);}finally{await save();}
}
private static bool True(JsonElement value,string key)=>value.TryGetProperty(key,out var item)&&item.ValueKind==JsonValueKind.True;
private static bool False(JsonElement value,string key)=>value.TryGetProperty(key,out var item)&&item.ValueKind==JsonValueKind.False;
private static Vector3d Vector(JsonElement value,string key){var array=value.GetProperty(key);return new(array[0].GetDouble(),array[1].GetDouble(),array[2].GetDouble());}
private static bool Finite(JsonElement value,string key,out double number){number=double.NaN;return value.TryGetProperty(key,out var item)&&item.ValueKind==JsonValueKind.Number&&item.TryGetDouble(out number)&&double.IsFinite(number);}
private static bool Stamp(JsonElement value,string key,out long at){at=0;return value.TryGetProperty(key,out var item)&&item.TryGetInt64(out at);}
private static bool ReadNextAxisFrame(Exception error,SystemNavigationAction action,bool mayIssueSco,CancellationToken token)
=>action==SystemNavigationAction.None&&!mayIssueSco&&NavigationRecovery.CanRetry(error,token)&&NavigationRecovery.Kind(error)==FlightRecovery.ReadNextFrame;
private static bool FreshSourceForInput(SystemFlightFrame frame,long lastApplied,DateTimeOffset now)
=>frame.Sequence>lastApplied&&frame.ObservedAt<=now&&now-frame.ObservedAt<=TimeSpan.FromMilliseconds(150);
private static SystemNavigationTarget Target(CurrentSystemSnapshot catalogue,SystemLocation row,SystemTargetKind kind)=>new(catalogue.SystemAddress,row.WorldAddress,row.Name,kind,row.MarketId)
{NativeKey=row.Key,NativeKind=row.NativeKind,NativeIdentity=row.Identity};
// Catalogue positions may use the current station frame. Only the matching
// original body's verified unified centre is used for this clearance check.
private static SystemLocation[] ClearFarTargets(CurrentSystemSnapshot catalogue,JsonElement raw,DateTimeOffset now)
{
if(!Stamp(raw,"observedAtUnixMilliseconds",out var at)||now.ToUnixTimeMilliseconds()-at is <0 or >250||!True(raw,"inGame")||!True(raw,"localPlayerShipVerified")
||raw.GetProperty("entityId").GetString()!=catalogue.EntityId.ToString()||now-catalogue.ObservedAt>TimeSpan.FromMilliseconds(250)||catalogue.ObservedAt>now)
return [];
var geometry=raw.GetProperty("geometry");
if(!True(geometry,"fresh")||!True(geometry,"referenceFrameVerified")||geometry.GetProperty("worldAddress").GetString()!=raw.GetProperty("referenceWorldAddress").GetString())return [];
var position=Vector(geometry,"position");if(!position.Finite)return [];
var bodies=geometry.GetProperty("nearbyBodies");if(bodies.ValueKind!=JsonValueKind.Array||bodies.GetArrayLength()==0)return [];
return catalogue.Locations.Where(row=>
{
if(row.Key<0||row.NativeKind<=0||row.WorldAddress==0||row.Identity.Length!=64||row.DistanceMetres is not {} distance
||!double.IsFinite(distance)||distance<50*LightSecond||row.Category is not(SystemLocationCategory.Star or SystemLocationCategory.Planet or SystemLocationCategory.LandablePlanet))return false;
var matching=bodies.EnumerateArray().Where(body=>body.TryGetProperty("bodyAddress",out var id)&&id.ValueKind==JsonValueKind.String&&id.GetString()==row.WorldAddress.ToString()).Take(2).ToArray();
if(matching.Length!=1)return false;
var point=Vector(matching[0],"centre");if(!point.Finite)return false;
var offset=point-position;var length=offset.Length;
if(!double.IsFinite(length)||Math.Abs(length-distance)>Math.Max(100,distance*1e-6))return false;
var direction=offset.Unit();var horizon=length-10*LightSecond;
foreach(var body in bodies.EnumerateArray())
{
if(!Finite(body,"exclusionRadius",out var radius)||radius<=0)return false;
var centre=Vector(body,"centre");if(!centre.Finite)return false;
var to=centre-position;var projection=Math.Clamp(Vector3d.Dot(to,direction),0,horizon);var closest=to-direction*projection;
if(closest.Length<=radius*1.2)return false;
}
return true;
}).OrderBy(row=>row.DistanceMetres).ThenBy(row=>row.Key).ToArray();
}
private static bool VerifiedSample(JsonElement status,ulong entity,ulong world,long now,out JsonElement sample)
{
sample=default;
if(!True(status,"sampleReady")||!status.TryGetProperty("sample",out sample)||sample.ValueKind!=JsonValueKind.Object||!True(sample,"verified")||!True(sample,"inSupercruise")||!True(sample,"stableFsd")
||sample.GetProperty("entityId").GetString()!=entity.ToString()||sample.GetProperty("referenceWorldAddress").GetString()!=world.ToString()||sample.GetProperty("rawFsdState").GetInt32()!=2
||!Stamp(sample,"observedAtUnixMilliseconds",out var at)||now-at is <0 or >200||!False(sample,"nativeAssistEnabled")||!True(sample,"fuelVerified")||!True(sample,"fuelBoundVerified")||!True(sample,"fuelRateLowerVerified")
||sample.GetProperty("fuelRateBoundsScope").GetString()!=FuelScope||!Finite(sample,"fuelRateLowerTonnesPerSecond",out var lower)||!Finite(sample,"fuelRateUpperTonnesPerSecond",out var upper)||lower<0||upper<=0||lower>upper
||!Finite(sample,"heatPercent",out var heat)||heat>=75||!Finite(sample,"fuelMainTonnes",out var fuel)||fuel<8)return false;
return true;
}
private static bool OriginalEnable(JsonElement receipt,int thread)=>True(receipt,"applied")&&True(receipt,"nativeMethodCalled")&&True(receipt,"argument")
&&receipt.GetProperty("nativeMethodRva").GetString()=="2eaacd0"&&receipt.GetProperty("owningThread").GetInt32()==thread&&receipt.GetProperty("actualStateAfterCall").GetInt32() is 1 or 2;
private static bool OriginalReleasedStop(JsonElement value,JsonElement enable,int thread,long afterActive)
=>True(value,"nativeMethodCalled")&&False(value,"argument")&&value.GetProperty("nativeMethodRva").GetString()=="2eaacd0"&&value.GetProperty("owningThread").GetInt32()==thread
&&value.GetProperty("owner").GetString()==enable.GetProperty("owner").GetString()&&value.GetProperty("startRequestId").GetString()==enable.GetProperty("requestId").GetString()
&&value.GetProperty("selectionGeneration").GetUInt64()==enable.GetProperty("selectionGeneration").GetUInt64()
&&value.GetProperty("entityId").GetString()==enable.GetProperty("entityId").GetString()&&value.GetProperty("referenceWorldAddress").GetString()==enable.GetProperty("referenceWorldAddress").GetString()
&&value.GetProperty("reason").GetString()=="released"&&value.GetProperty("releaseRequestId").ValueKind==JsonValueKind.String&&Guid.TryParseExact(value.GetProperty("releaseRequestId").GetString(),"N",out _)
&&value.GetProperty("stateBefore").GetInt32() is 1 or 2&&value.GetProperty("stateAfter").GetInt32() is 0 or 3
&&Stamp(value,"observedAtUnixMilliseconds",out var at)&&at>=afterActive;
private static bool QualifiedCycle(JsonElement status,JsonElement enable,JsonElement stopped,long lastActiveAt,out JsonElement proof)
{
proof=default;if(!True(status,"overchargeControlQualified")||!status.TryGetProperty("qualifiedCycle",out proof)||proof.ValueKind!=JsonValueKind.Object)return false;
return proof.GetProperty("owner").GetString()==enable.GetProperty("owner").GetString()&&proof.GetProperty("startRequestId").GetString()==enable.GetProperty("requestId").GetString()
&&proof.GetProperty("selectionGeneration").GetUInt64()==enable.GetProperty("selectionGeneration").GetUInt64()
&&proof.GetProperty("entityId").GetString()==enable.GetProperty("entityId").GetString()&&proof.GetProperty("referenceWorldAddress").GetString()==enable.GetProperty("referenceWorldAddress").GetString()
&&proof.GetProperty("activeSamples").GetInt32()>=5&&proof.GetProperty("lastActiveAtUnixMilliseconds").GetInt64()>=lastActiveAt
&&proof.GetProperty("releaseRequestId").GetString()==stopped.GetProperty("releaseRequestId").GetString()
&&proof.GetProperty("originalStopAtUnixMilliseconds").GetInt64()==stopped.GetProperty("observedAtUnixMilliseconds").GetInt64()
&&proof.GetProperty("offAtUnixMilliseconds").GetInt64()>stopped.GetProperty("observedAtUnixMilliseconds").GetInt64();
}
internal static async Task RunAsync(int pid,string directory,string recoveryPackage)
{
directory=Path.GetFullPath(directory);recoveryPackage=Path.GetFullPath(recoveryPackage);var root=Path.GetDirectoryName(directory)!;
if(Directory.Exists(directory))throw new IOException("Use a fresh one-shot SCO directory.");
using var exclusive=new FileStream(Path.Combine(root,"docking-fixture-wait.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);Directory.CreateDirectory(directory);
var report=Path.Combine(directory,"result.json");var explicitStop=Path.Combine(directory,"stop");var start=DateTimeOffset.UtcNow;
using var timeout=new CancellationTokenSource(TimeSpan.FromMinutes(15));var token=timeout.Token;using var sentinel=new Timer(_=>{if(File.Exists(explicitStop))timeout.Cancel();},null,100,100);
using var process=Process.GetProcessById(pid);var started=process.StartTime.ToUniversalTime().Ticks;
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
if(!initial.InGame||!initial.Docked||initial.MarketId!=4210924803||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||NavigationSafety.HasExternalThreat(initial)||NavigationSafety.UnhealthyHull(initial))
throw new InvalidOperationException("Healthy empty Homer Corsair/5 required.");
using var guardian=await PersistentProtectionProcess.StartOrReuseAsync(pid,root,token,allowStart:false);PersistentProtectionProcess.Current=guardian;
FlightTestProtectionScope? protection=null;NativeSystemFlightControlDriver? driver=null;SystemNavigationTarget? selected=null;SystemLocation? farRow=null;
var phase="docked-preflight";string? failure=null,eventFailure=null;var maintainedBefore=false;var launched=false;var entered=false;var activeFive=false;var originalOff=false;var offObserved=false;var exited=false;var docked=false;var maintainedAfter=false;var recovered=false;var flightStarted=false;
var enterRequests=0;var dropRequests=0;var rebinds=0;var enableCalls=0;long lastInputSequence=0;var activeSamples=new List<JsonElement>();var sampleTimes=new HashSet<long>();var commands=new List<JsonElement>();var stops=new List<JsonElement>();
JsonElement? firstEnable=null,originalStop=null,offSample=null,qualifiedCycle=null,finalDock=null;ulong controlEntity=0,controlWorld=0;long lastActiveAt=0;int controlThread=0;
var events=Channel.CreateUnbounded<object>(new(){SingleReader=true,SingleWriter=true});
await using var log=new StreamWriter(new FileStream(Path.Combine(directory,"frames.jsonl"),FileMode.CreateNew,FileAccess.Write,FileShare.Read));
void EvidenceFailed(Exception error){eventFailure??="Evidence writer: "+error;timeout.Cancel();}
var writer=Task.Run(()=>WriteEvidenceAsync(events,log,EvidenceFailed));
void TryRecord(object value)=>events.Writer.TryWrite(value);
void Record(object value)
{
if(eventFailure is not null||!events.Writer.TryWrite(value)){eventFailure??="SCO evidence channel stopped.";timeout.Cancel();throw new IOException(eventFailure);}
}
async Task Save()
{
var temp=report+".tmp";await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(new{passed=maintainedBefore&&launched&&entered&&activeFive&&originalOff&&offObserved&&exited&&docked&&maintainedAfter&&failure is null&&eventFailure is null,
pid,started,start,at=DateTimeOffset.UtcNow,phase,maintainedBefore,launched,entered,activeFive,originalOff,offObserved,exited,docked,maintainedAfter,recovered,enableCalls,enterRequests,dropRequests,rebinds,
farTarget=farRow,activeSamples,commands,stops,firstEnable,originalStop,offSample,qualifiedCycle,finalDock,failure,eventFailure,gameSupercruiseAssistUsed=false,tradeOrdersSent=0,crossSystemFlight=false,persistent075Acceptance=false,clientTerminated=false},ToolboxSettings.JsonOptions));File.Move(temp,report,true);
}
async Task Phase(string value){phase=value;Console.WriteLine(value);await Save();}
void Session()
{
token.ThrowIfCancellationRequested();process.Refresh();var state=journal.State;
if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=started||!state.InGame||state.CommanderId!=initial.CommanderId||state.LoadGameAt!=initial.LoadGameAt||state.CurrentShipId!=5||state.CargoCount!=0||state.CargoCapacity!=192
||state.Navigation.CurrentSystemAddress!=initial.Navigation.CurrentSystemAddress||NavigationSafety.HasExternalThreat(state)||NavigationSafety.UnhealthyHull(state))throw new InvalidOperationException("SCO test ship, session, system or health identity changed.");
guardian.RequireReady(pid);if(eventFailure is not null)throw new InvalidDataException(eventFailure);
}
async Task Protection(CancellationToken cancellation)
{
cancellation.ThrowIfCancellationRequested();Session();
if(protection?.Stopped==true)
{
var nav=journal.State.Navigation;
if(protection.LastIssue!="飞船实体已切换,已暂停驾驶输入并继续监测健康。"||!(nav.SupercruiseEnteredAt>=start.AddSeconds(-1)||entered&&nav.SupercruiseExitedAt>=start.AddSeconds(-1)))throw new InvalidOperationException("The protection stop is not a verified SC scene transition.");
await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);rebinds++;
}
NativeFlightTestProtection.CheckBeforeInput(pid);
}
async Task<SystemFlightFrame> Read()
{
var limit=DateTimeOffset.UtcNow.AddSeconds(20);
while(true)
{
try
{
Session();var frame=await driver!.ReadSystemFlightAsync(token);await Protection(token);
if(frame.PlayerOverride||frame.ExternalThreat||frame.SupercruiseAssist is NativeAssistState.Engaged or NativeAssistState.Requested or NativeAssistState.Aligning||(frame.HeatPercent??100)>=75||frame.Mode is LocalFlightMode.OrbitalCruise or LocalFlightMode.Glide)
throw new InvalidOperationException("SCO smoke safety boundary exceeded.");
var sca=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");if(!False(sca,"enabled")||!False(sca,"engaged"))throw new InvalidDataException("Game SCA is not explicitly disabled.");return frame;
}
catch(Exception error)when(NavigationRecovery.CanRetry(error,token)&&DateTimeOffset.UtcNow<limit){Record(new{at=DateTimeOffset.UtcNow,phase,recovering=error.Message});await Task.Delay(40,token);}
}
}
async Task<bool> Apply(SystemFlightFrame frame,Vector3d? aim,double throttle,SystemNavigationPhase stage=SystemNavigationPhase.NormalApproach,SystemNavigationAction actions=SystemNavigationAction.None)
{
if(!FreshSourceForInput(frame,lastInputSequence,DateTimeOffset.UtcNow)){Record(new{at=DateTimeOffset.UtcNow,phase,skippedOldSource=true,frame.Sequence,frame.ObservedAt,actionSubmitted=false});return false;}
flightStarted=true;
try{await driver!.ApplySystemFlightAsync(new(stage,phase,aim,throttle,actions),frame,selected!,frame.ObservedAt.AddMilliseconds(250),token);lastInputSequence=frame.Sequence;return true;}
catch(Exception error)when(ReadNextAxisFrame(error,actions,stage is SystemNavigationPhase.Supercruise or SystemNavigationPhase.Aligning,token))
{Record(new{at=DateTimeOffset.UtcNow,phase,skippedStaleAxisFrame=true,error=error.Message,actionReplayed=false});return false;}
}
try
{
protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);
var pipe=new NavigationPipeClient(pid);using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);
var cargo=await pipe.CallAsync(new{op="commerce.inventory"},token);
if(!True(cargo,"verified")||cargo.GetProperty("count").GetInt32()!=0||cargo.GetProperty("capacity").GetInt32()!=192||cargo.GetProperty("credits").GetInt64()<4427098+200000)
throw new InvalidDataException("Original empty cargo and rebuy/service reserve have not been verified.");
var catalogue=await source.ReadLocationsAsync(token);var raw=await pipe.CallAsync(new{op="read"},token);var candidates=ClearFarTargets(catalogue,raw,DateTimeOffset.UtcNow);
Record(new{at=DateTimeOffset.UtcNow,phase,catalogue,raw,geometry=raw.GetProperty("geometry"),candidates});
farRow=candidates.FirstOrDefault()??throw new InvalidDataException("No original sufficiently distant and clear target is available; the ship remains docked.");
var home=catalogue.Locations.Single(row=>row.MarketId==4210924803&&row.Name=="Homer Platform");selected=Target(catalogue,home,SystemTargetKind.Station);
driver=new(pid,NativeSystemLocationReader.GameSha256,()=>journal.State,new NativeGameInputChannel(pid,NativeSystemLocationReader.GameSha256,Path.Combine(AppContext.BaseDirectory,"PluginPackage")),Protection,useNativeSupercruiseAssist:false);
await driver.ConfigureOriginalCruiseControlAsync(()=>8,allowUnqualifiedEmptyTrial:true,token);var control=driver.CruiseControl!;controlThread=(await control.InspectAsync(token)).OwningThread;
Action<JsonElement> observeStatus=value=>
{
Record(new{at=DateTimeOffset.UtcNow,kind="SCO-observed",value});
try
{
if(value.GetProperty("schema").GetInt32()!=1||value.GetProperty("processId").GetInt32()!=pid||value.GetProperty("service").GetString()!=NativeCruiseControlChannel.Service||value.GetProperty("gameSha256").GetString()!=NativeSystemLocationReader.GameSha256)
throw new InvalidDataException("SCO observation belongs to another controller service or game.");
if(controlEntity==0||!VerifiedSample(value,controlEntity,controlWorld,DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),out var sample))return;
var at=sample.GetProperty("observedAtUnixMilliseconds").GetInt64();
if(firstEnable is {} enable&&sample.GetProperty("state").GetInt32()==2&&at>=enable.GetProperty("observedAtUnixMilliseconds").GetInt64()&&sampleTimes.Add(at))
{
if(!True(sample,"controlTelemetryReady")||!True(sample,"geometryVerified")||!True(sample,"targetVerified")||True(sample,"collisionCourse"))throw new InvalidDataException("Active SCO no longer has its verified clear original target.");
activeSamples.Add(sample.Clone());lastActiveAt=at;activeFive=activeSamples.Count>=5;
}
if(firstEnable is {} enabled&&originalStop is {} stopped&&sample.GetProperty("state").GetInt32() is 0 or 3&&False(value,"owned")&&at>stopped.GetProperty("observedAtUnixMilliseconds").GetInt64()
&&QualifiedCycle(value,enabled,stopped,lastActiveAt,out var cycle)&&cycle.GetProperty("offAtUnixMilliseconds").GetInt64()<=at)
{offSample=sample.Clone();qualifiedCycle=cycle.Clone();offObserved=true;}
}
catch(Exception error){eventFailure="SCO observation schema: "+error.Message;}
};
control.Observed+=observeStatus;
control.OriginalReceipt+=value=>
{
commands.Add(value.Clone());Record(new{at=DateTimeOffset.UtcNow,kind="SCO-original-receipt",value});
try{if(OriginalEnable(value,controlThread)){enableCalls++;firstEnable??=value.Clone();if(enableCalls>1)eventFailure="A second original SCO enable is not allowed in this one-shot test.";}}
catch(Exception error){eventFailure="SCO original receipt schema: "+error.Message;}
};
control.OriginalStopReceipt+=value=>
{
stops.Add(value.Clone());Record(new{at=DateTimeOffset.UtcNow,kind="SCO-original-stop",value});
try{if(firstEnable is {} enable&&activeFive&&OriginalReleasedStop(value,enable,controlThread,lastActiveAt)){originalStop=value.Clone();originalOff=true;}}
catch(Exception error){eventFailure="SCO stop receipt schema: "+error.Message;}
};
await driver.SelectAsync(selected,token);await Read();await protection.DisposeAsync();protection=null;
await Phase("docked-maintenance-before");await DockedMaintenance.RunAsync(pid,Path.Combine(directory,"maintenance-before"),token);maintainedBefore=true;
protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);await Phase("original-launch");flightStarted=true;await driver.RequestLaunchAsync(token);var launchLimit=DateTimeOffset.UtcNow.AddMinutes(4);var launchSeen=false;
while(true)
{
var frame=await Read();var launching=True(driver.LastTelemetry!.Value.GetProperty("docking"),"nativeLaunchActive");launchSeen|=launching;Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(launchSeen&&!launching&&!journal.State.Docked&&journal.State.Navigation.UndockedAt>=start.AddSeconds(-1))break;
if(DateTimeOffset.UtcNow>launchLimit)throw new TimeoutException("Original launch did not complete.");await Task.Delay(60,token);
}
driver.ConfirmDockingValidationLaunchCompleted();launched=true;await driver.PrepareAsync(token);await Phase("original-sc-entry");var entryLimit=DateTimeOffset.UtcNow.AddSeconds(90);
while(true)
{
var frame=await Read();Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(frame.Mode==LocalFlightMode.Supercruise){entered=true;await Apply(frame,null,0);break;}
if(DateTimeOffset.UtcNow>entryLimit||frame.TargetDistance>60000)throw new TimeoutException("Bounded original SC entry did not complete.");
var outward=(frame.Position-frame.TargetPosition).Unit();var aligned=Vector3d.AngleDegrees(frame.Forward,outward)<=5;
var action=enterRequests==0&&aligned&&frame.FsdReady?SystemNavigationAction.EnterSupercruise:SystemNavigationAction.None;
var sent=await Apply(frame,outward,aligned?1:.25,actions:action);if(sent&&action!=SystemNavigationAction.None)enterRequests++;await Task.Delay(40,token);
}
await driver.ReleaseSystemFlightAsync();catalogue=await source.ReadLocationsAsync(token);var liveFar=catalogue.Locations.SingleOrDefault(row=>row.WorldAddress==farRow.WorldAddress&&row.Name==farRow.Name)
??throw new InvalidDataException("The chosen original far body did not survive the verified SC scene transition.");
selected=Target(catalogue,liveFar,SystemTargetKind.Orbit);await driver.SelectAsync(selected,token);await Phase("align-real-far-target");var aimLimit=DateTimeOffset.UtcNow.AddSeconds(60);
while(true)
{
var frame=await Read();var aim=(frame.TargetPosition-frame.Position).Unit();Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(frame.TargetObscured||frame.CollisionCourse||frame.TargetDistance<30*LightSecond)throw new InvalidDataException("The actual selected far target is obscured, unsafe or too close.");
await Apply(frame,aim,0);if(Vector3d.AngleDegrees(frame.Forward,aim)<=2)break;if(DateTimeOffset.UtcNow>aimLimit)throw new TimeoutException("The real far target could not be aligned.");await Task.Delay(40,token);
}
await Phase("one-original-sco-pulse");var pulseLimit=DateTimeOffset.UtcNow.AddSeconds(8);
while(!activeFive)
{
var frame=await Read();controlEntity=frame.ShipEntity;controlWorld=frame.ReferenceWorld;Record(new{at=DateTimeOffset.UtcNow,phase,frame,decision=control.LastDecision});
if(stops.Count>0)throw new InvalidOperationException("SCO was already stopped by an interlock before five Active frames; no second pulse will be attempted.");
if(DateTimeOffset.UtcNow>pulseLimit)throw new TimeoutException("Five real SCO Active frames were not observed in the single bounded pulse.");
await Apply(frame,(frame.TargetPosition-frame.Position).Unit(),.25,SystemNavigationPhase.Supercruise);await Task.Delay(35,token);
}
await Phase("explicit-original-sco-off");var offLimit=DateTimeOffset.UtcNow.AddSeconds(2);await control.ReleaseAsync(token);Session();
while(!offObserved&&originalOff&&DateTimeOffset.UtcNow<offLimit)
{
// The original physics timestamp can precede the stop call in
// the same game tick. Read the next original sample; never repeat release.
observeStatus(await new NavigationPipeClient(pid,NativeCruiseControlChannel.Service).CallAsync(new{op="read"},token));Session();
if(!offObserved)await Task.Delay(15,token);
}
if(!originalOff||!offObserved||enableCalls!=1)throw new InvalidDataException("An independent original false-call receipt and a subsequent actual Off sample are required.");
var stoppedFrame=await Read();await Apply(stoppedFrame,null,0);await driver.ReleaseSystemFlightAsync();catalogue=await source.ReadLocationsAsync(token);
home=catalogue.Locations.Single(row=>row.MarketId==4210924803&&row.Name=="Homer Platform");selected=Target(catalogue,home,SystemTargetKind.Station);await driver.SelectAsync(selected,token);
await Phase("software-sc-return-homer");var returnLimit=DateTimeOffset.UtcNow.AddMinutes(5);var navigator=new SystemFlightNavigator();
while(true)
{
var frame=await Read();Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(frame.Mode==LocalFlightMode.Normal){exited=true;await Apply(frame,null,0);break;}
if(DateTimeOffset.UtcNow>returnLimit)throw new TimeoutException("The bounded original SC return did not complete.");
if(frame.Mode==LocalFlightMode.Transition){await Task.Delay(40,token);continue;}
var decision=navigator.Decide(frame,selected,DateTimeOffset.UtcNow);
if(decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist)||decision.Phase==SystemNavigationPhase.Stopped)throw new InvalidDataException("The return cannot use SCA or an unverified route.");
if(!FreshSourceForInput(frame,lastInputSequence,DateTimeOffset.UtcNow)){await Task.Delay(15,token);continue;}
if(decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage)&&dropRequests>0){await Task.Delay(40,token);continue;}
// Keep the existing software speed/attitude policy while forbidding
// any second SCO pulse during this short qualification return.
var returned=await Apply(frame,decision.Direction,decision.Throttle??-1,actions:decision.Actions);
if(returned&&decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage))dropRequests++;await Task.Delay(40,token);
}
await driver.ReleaseSystemFlightAsync();await protection.DisposeAsync();protection=null;await Phase("original-ai-return-homer");
await OriginalDockingResetAcceptance.RunAsync(pid,Path.Combine(directory,"return-docking.json"),recoveryPackage,requireNonzero:false,sharedStopFile:explicitStop,protectionWorkRoot:root);docked=true;PersistentProtectionProcess.Current=guardian;
await Phase("docked-maintenance-after");await DockedMaintenance.RunAsync(pid,Path.Combine(directory,"maintenance-after"),token);maintainedAfter=true;
finalDock=await pipe.CallAsync(new{op="commerce.docked.status",marketId=4210924803},token);
if(!True(finalDock.Value,"ready")||!True(finalDock.Value,"serviceReady"))throw new InvalidDataException("Final actual Homer services are not ready.");await Phase("passed-one-original-sco-pulse-and-return");
}
catch(Exception error)
{
failure=error.ToString();TryRecord(new{at=DateTimeOffset.UtcNow,phase,failure});await Save();Console.WriteLine(error.Message);
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();}catch(Exception release){TryRecord(new{releaseFailure=release.ToString()});}
if(protection is not null){await protection.DisposeAsync();protection=null;}
if(flightStarted&&!File.Exists(explicitStop)&&!process.HasExited&&!journal.State.Docked&&journal.State.CurrentShipId==5&&journal.State.CargoCount==0&&journal.State.LoadGameAt==initial.LoadGameAt)
{
try
{await Phase("same-process-original-menu-recovery");await OriginalDockingResetAcceptance.EnterPauseAsync(pid,guardian,Path.Combine(directory,"recovery-pause.json"));await SessionRecoveryAcceptance.RunAsync(pid,recoveryPackage,Path.Combine(directory,"recovery-menu.json"),true,root);await GameSessionAcceptance.StartAsync(settings.PluginGameExecutable,Path.Combine(directory,"recovery-solo.json"),pid);recovered=true;}
catch(Exception recovery){TryRecord(new{recoveryFailure=recovery.ToString(),clientTerminated=false});}
}
}
finally
{
try
{
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();}catch(Exception release){TryRecord(new{finalReleaseFailure=release.ToString()});}
if(protection is not null)await protection.DisposeAsync();
}
finally
{
PersistentProtectionProcess.Current=null;await CompleteEvidenceAsync(events,writer,EvidenceFailed,Save);
}
}
if(failure is not null||eventFailure is not null)throw new InvalidOperationException("One-shot SCO qualification failed; evidence was preserved and the pulse was not repeated.");
}
/// <summary>Completes an already failed empty attempt from its new Solo. Does not configure any SCO controller.</summary>
internal static async Task FinishReturnAsync(int pid,string directory,string recoveryPackage,string attemptName)
{
if(attemptName.Length is 0 or >48||attemptName.Any(c=>!(char.IsAsciiLetterLower(c)||char.IsAsciiDigit(c)||c=='-')))throw new ArgumentException("A fresh bounded return attempt name is required.");
directory=Path.GetFullPath(directory);recoveryPackage=Path.GetFullPath(recoveryPackage);var root=Path.GetDirectoryName(directory)!;
var report=Path.Combine(directory,attemptName+".json");var stopFile=Path.Combine(directory,"stop");
if(File.Exists(report)||File.Exists(report+".jsonl")||Directory.Exists(Path.Combine(directory,"maintenance-after")))throw new IOException("Preserve prior return and monetary evidence.");
using var original=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(directory,"result.json")));
using var process=Process.GetProcessById(pid);var ticks=process.StartTime.ToUniversalTime().Ticks;
if(original.RootElement.GetProperty("pid").GetInt32()!=pid||original.RootElement.GetProperty("started").GetInt64()!=ticks||original.RootElement.GetProperty("passed").GetBoolean())throw new InvalidDataException("One actual failed same-process SCO attempt is required.");
using var lease=new FileStream(Path.Combine(root,"docking-fixture-wait.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
using var timeout=new CancellationTokenSource(TimeSpan.FromMinutes(9));var token=timeout.Token;
using var sentinel=new Timer(_=>{if(File.Exists(stopFile))timeout.Cancel();},null,100,100);
using var guardian=await PersistentProtectionProcess.StartOrReuseAsync(pid,root,token,allowStart:false);PersistentProtectionProcess.Current=guardian;
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;var start=DateTimeOffset.UtcNow;
var originalStart=original.RootElement.GetProperty("start").GetDateTimeOffset();var soloRecovered=initial.LoadGameAt>originalStart;
ulong originalEntity;
{var first=new Utf8JsonReader(await File.ReadAllBytesAsync(Path.Combine(directory,"frames.jsonl")));using var originalFrame=JsonDocument.ParseValue(ref first);originalEntity=ulong.Parse(originalFrame.RootElement.GetProperty("raw").GetProperty("entityId").GetString()!);}
var originalEntryConfirmed=original.RootElement.GetProperty("enterRequests").GetInt32()==1&&original.RootElement.GetProperty("enableCalls").GetInt32()==0&&original.RootElement.GetProperty("firstEnable").ValueKind==JsonValueKind.Null;
if(!initial.InGame||initial.Docked||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||initial.LoadGameAt is null||!soloRecovered&&(!originalEntryConfirmed||initial.Navigation.SupercruiseEnteredAt is not {} scAt||scAt<originalStart)
||NavigationSafety.HasExternalThreat(initial)||NavigationSafety.UnhealthyHull(initial))throw new InvalidDataException("A new healthy empty same-PID Solo entry must be confirmed before return.");
FlightTestProtectionScope? protection=null;NativeSystemFlightControlDriver? driver=null;SystemNavigationTarget? target=null;
var phase="return-preflight";string? failure=null;var inputStarted=false;var entered=false;var exited=false;var docked=false;var maintained=false;var recoveredAgain=false;var enters=0;var drops=0;var rebinds=0;long lastInputSequence=0;JsonElement? finalDock=null,health=null;
await using var log=new StreamWriter(new FileStream(report+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
async Task Record(object value){await log.WriteLineAsync(JsonSerializer.Serialize(value,EvidenceJson));await log.FlushAsync();}
async Task Save()
{
var temp=report+".tmp";await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(new{passed=docked&&maintained&&failure is null,pid,ticks,start,at=DateTimeOffset.UtcNow,phase,entered,exited,docked,maintained,recoveredAgain,enters,drops,rebinds,finalDock,health,failure,
originalAttempt=Path.Combine(directory,"result.json"),originalFailurePreserved=true,originalSoloRecoveryConfirmed=soloRecovered,sameLoadGameContinuation=!soloRecovered,originalEntity,SCOControllerConfigured=false,overchargeCommandsSent=0,gameSupercruiseAssistUsed=false,tradeOrdersSent=0,clientTerminated=false},ToolboxSettings.JsonOptions));File.Move(temp,report,true);
}
async Task Phase(string value){phase=value;Console.WriteLine(value);await Save();}
void Session()
{
token.ThrowIfCancellationRequested();process.Refresh();var state=journal.State;
if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=ticks||!state.InGame||state.CommanderId!=initial.CommanderId||state.LoadGameAt!=initial.LoadGameAt||state.CurrentShipId!=5||state.CargoCount!=0||state.CargoCapacity!=192
||state.Navigation.CurrentSystemAddress!=initial.Navigation.CurrentSystemAddress||NavigationSafety.HasExternalThreat(state)||NavigationSafety.UnhealthyHull(state))throw new InvalidDataException("The same-process empty return identity or safety changed.");
guardian.RequireReady(pid);
}
async Task Protect(CancellationToken cancellation)
{
cancellation.ThrowIfCancellationRequested();Session();
if(protection?.Stopped==true)
{
if(protection.LastIssue!="飞船实体已切换,已暂停驾驶输入并继续监测健康。"||!(journal.State.Navigation.SupercruiseEnteredAt>=start.AddSeconds(-1)||entered&&journal.State.Navigation.SupercruiseExitedAt>=start.AddSeconds(-1)))throw new InvalidDataException("The return protection cannot be rebound without an original SC transition.");
await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);rebinds++;
}
NativeFlightTestProtection.CheckBeforeInput(pid);
}
async Task<SystemFlightFrame> Read()
{
var limit=DateTimeOffset.UtcNow.AddSeconds(20);
while(true)
{
try
{
Session();var frame=await driver!.ReadSystemFlightAsync(token);await Protect(token);var assist=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(frame.PlayerOverride||frame.ExternalThreat||(frame.HeatPercent??100)>=75||frame.Mode is LocalFlightMode.Glide or LocalFlightMode.OrbitalCruise||!False(assist,"enabled")||!False(assist,"engaged"))throw new InvalidDataException("The return requires healthy software navigation and explicitly disabled SCA.");
return frame;
}
catch(Exception error)when(NavigationRecovery.CanRetry(error,token)&&DateTimeOffset.UtcNow<limit){await Record(new{at=DateTimeOffset.UtcNow,phase,recovering=error.Message});await Task.Delay(40,token);}
}
}
async Task<bool> Apply(SystemFlightFrame frame,Vector3d? aim,double throttle,SystemNavigationAction action=SystemNavigationAction.None)
{
if(!FreshSourceForInput(frame,lastInputSequence,DateTimeOffset.UtcNow)){await Record(new{at=DateTimeOffset.UtcNow,phase,skippedOldSource=true,frame.Sequence,frame.ObservedAt,actionSubmitted=false});return false;}
inputStarted=true;
try{await driver!.ApplySystemFlightAsync(new(SystemNavigationPhase.NormalApproach,phase,aim,throttle,action),frame,target!,frame.ObservedAt.AddMilliseconds(250),token);lastInputSequence=frame.Sequence;return true;}
catch(Exception error)when(ReadNextAxisFrame(error,action,false,token))
{await Record(new{at=DateTimeOffset.UtcNow,phase,skippedStaleAxisFrame=true,error=error.Message,actionReplayed=false});return false;}
}
try
{
protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);var pipe=new NavigationPipeClient(pid);
var inventory=await pipe.CallAsync(new{op="commerce.inventory"},token);if(!True(inventory,"verified")||inventory.GetProperty("count").GetInt32()!=0||inventory.GetProperty("capacity").GetInt32()!=192)throw new InvalidDataException("The original empty inventory is not confirmed.");
using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);var catalogue=await source.ReadLocationsAsync(token);var home=catalogue.Locations.Single(row=>row.MarketId==4210924803&&row.Name=="Homer Platform");target=Target(catalogue,home,SystemTargetKind.Station);
driver=new(pid,NativeSystemLocationReader.GameSha256,()=>journal.State,new NativeGameInputChannel(pid,NativeSystemLocationReader.GameSha256,Path.Combine(AppContext.BaseDirectory,"PluginPackage")),Protect,useNativeSupercruiseAssist:false);
await driver.SelectAsync(target,token);await driver.PrepareAsync(token);var frame=await Read();await Record(new{at=DateTimeOffset.UtcNow,phase,frame,raw=await pipe.CallAsync(new{op="read"},token),inventory});
if(frame.TargetDistance>20000000||frame.TargetObscured||frame.CollisionCourse)throw new InvalidDataException("The original Homer return is outside the admitted clear local volume.");
if(!soloRecovered&&(frame.Mode!=LocalFlightMode.Supercruise||frame.ShipEntity==originalEntity))throw new InvalidDataException("The original requested old-to-new main-ship SC transition is not confirmed.");
if(frame.Mode==LocalFlightMode.Normal&&frame.TargetDistance<=40000)exited=true;
else
{
await Phase("return-software-sc-entry");var until=DateTimeOffset.UtcNow.AddSeconds(90);
while(frame.Mode!=LocalFlightMode.Supercruise)
{
if(DateTimeOffset.UtcNow>until)throw new TimeoutException("The single original return SC entry did not complete.");
if(frame.Mode==LocalFlightMode.Transition){await Task.Delay(40,token);frame=await Read();continue;}
if(frame.Mode!=LocalFlightMode.Normal||frame.TargetObscured||frame.CollisionCourse)throw new InvalidDataException("The original local SC return geometry changed.");
var direction=(frame.TargetPosition-frame.Position).Unit();var aligned=Vector3d.AngleDegrees(frame.Forward,direction)<=5;var action=aligned&&frame.FsdReady&&enters==0?SystemNavigationAction.EnterSupercruise:SystemNavigationAction.None;
await Record(new{at=DateTimeOffset.UtcNow,phase,frame});var sent=await Apply(frame,direction,aligned?1:0,action);if(sent&&action!=SystemNavigationAction.None)enters++;await Task.Delay(40,token);frame=await Read();
}
entered=true;await Phase("return-software-sc-approach");var approachUntil=DateTimeOffset.UtcNow.AddMinutes(3);DateTimeOffset? exitUntil=null;var navigator=new SystemFlightNavigator();
while(true)
{
await Record(new{at=DateTimeOffset.UtcNow,phase,frame});if(frame.Mode==LocalFlightMode.Normal){exited=true;await Apply(frame,null,0);break;}
if(DateTimeOffset.UtcNow>(exitUntil??approachUntil)||frame.TargetDistance>20000000)throw new TimeoutException("The original one-shot safe return exit did not complete.");
if(frame.Mode==LocalFlightMode.Transition){await Task.Delay(40,token);frame=await Read();continue;}
if(frame.Mode!=LocalFlightMode.Supercruise)throw new InvalidDataException("Unexpected original mode during safe return.");
if(drops>0){await Task.Delay(40,token);frame=await Read();continue;}
var decision=navigator.Decide(frame,target,DateTimeOffset.UtcNow);
if(decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist)||decision.Phase==SystemNavigationPhase.Stopped)throw new InvalidDataException("The original software return cannot use SCA or unverified geometry.");
var sent=await Apply(frame,decision.Direction,decision.Throttle??-1,decision.Actions);
if(sent&&decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage)){drops++;exitUntil=DateTimeOffset.UtcNow.AddSeconds(45);await Phase("return-original-safe-exit");}
await Task.Delay(40,token);frame=await Read();
}
}
await driver.ReleaseSystemFlightAsync();await protection.DisposeAsync();protection=null;await Phase("return-original-g-docking");
await OriginalDockingResetAcceptance.RunAsync(pid,Path.Combine(directory,attemptName+"-docking.json"),recoveryPackage,requireNonzero:false,sharedStopFile:stopFile,protectionWorkRoot:root);docked=true;PersistentProtectionProcess.Current=guardian;
await Phase("return-original-maintenance");await DockedMaintenance.RunAsync(pid,Path.Combine(directory,"maintenance-after"),token);maintained=true;
finalDock=await pipe.CallAsync(new{op="commerce.docked.status",marketId=4210924803},token);health=await pipe.CallAsync(new{op="health.read"},token);guardian.RequireReady(pid);
if(!True(finalDock.Value,"ready")||!True(finalDock.Value,"serviceReady")||!True(health.Value.GetProperty("shipHealth"),"verified"))throw new InvalidDataException("The returned original Homer services and health are not ready.");await Phase("returned-and-maintained");
}
catch(Exception error)
{
failure=error.ToString();Console.WriteLine(error.Message);await Save();
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();}catch{}
if(protection is not null){await protection.DisposeAsync();protection=null;}
if(inputStarted&&!File.Exists(stopFile)&&!process.HasExited&&!journal.State.Docked&&journal.State.LoadGameAt==initial.LoadGameAt)
{
try{await OriginalDockingResetAcceptance.EnterPauseAsync(pid,guardian,Path.Combine(directory,attemptName+"-pause.json"));await SessionRecoveryAcceptance.RunAsync(pid,recoveryPackage,Path.Combine(directory,attemptName+"-menu.json"),true,root);await GameSessionAcceptance.StartAsync(settings.PluginGameExecutable,Path.Combine(directory,attemptName+"-solo.json"),pid);recoveredAgain=true;}
catch(Exception recovery){failure+="\nReturn recovery: "+recovery;}
}
}
finally
{
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();if(protection is not null)await protection.DisposeAsync();}
finally{PersistentProtectionProcess.Current=null;await Save();}
}
if(failure is not null)throw new InvalidOperationException("Same-attempt return incomplete; the failure is preserved and no SCO pulse was repeated.");
}
internal static void SelfTest()
{
var now=DateTimeOffset.FromUnixTimeMilliseconds(1700000000000);var hash=new string('a',64);var entity=12UL;var world=24UL;
JsonElement Raw(double radius=1,bool includeFar=true)=>JsonSerializer.SerializeToElement(new{observedAtUnixMilliseconds=now.ToUnixTimeMilliseconds(),inGame=true,localPlayerShipVerified=true,entityId="12",referenceWorldAddress="24",
geometry=new{fresh=true,referenceFrameVerified=true,worldAddress="24",position=new[]{0d,0,0},nearbyBodies=new[]{new{bodyAddress="101",centre=new[]{100*LightSecond,0d,0},exclusionRadius=1d},new{bodyAddress="999",centre=new[]{0d,1000000,0},exclusionRadius=radius}}.Skip(includeFar?0:1).ToArray()}});
var far=new SystemLocation(1,101,"real far body","",3,SystemLocationCategory.Planet,null,new(-111,13,17),100*LightSecond,false,hash);
CurrentSystemSnapshot Catalogue(params SystemLocation[] rows)=>new(1,"Fixture",entity,1,now,rows);
var checks=new List<bool>{ClearFarTargets(Catalogue(far),Raw(),now).Length==1,ClearFarTargets(Catalogue(far with{DistanceMetres=1}),Raw(),now).Length==0,
ClearFarTargets(Catalogue(far),Raw(includeFar:false),now).Length==0,ClearFarTargets(Catalogue(far),Raw(2000000),now).Length==0,
ClearFarTargets(Catalogue(far),Raw(),now.AddMilliseconds(251)).Length==0,ClearFarTargets(Catalogue(far with{DistanceMetres=200*LightSecond}),Raw(),now).Length==0,
ClearFarTargets(Catalogue(far with{Category=SystemLocationCategory.Station}),Raw(),now).Length==0,ClearFarTargets(Catalogue(far with{Identity=""}),Raw(),now).Length==0};
var enable=JsonSerializer.SerializeToElement(new{owner=new string('a',32),requestId=new string('b',32),selectionGeneration=3,entityId="12",referenceWorldAddress="24"});
JsonElement Stop(bool called=true,long after=100,string reason="released")=>JsonSerializer.SerializeToElement(new{owner=new string('a',32),startRequestId=new string('b',32),selectionGeneration=3,
entityId="12",referenceWorldAddress="24",nativeMethodRva="2eaacd0",nativeMethodCalled=called,argument=false,owningThread=88,releaseRequestId=new string('c',32),stateBefore=2,stateAfter=3,reason,observedAtUnixMilliseconds=now.AddMilliseconds(after).ToUnixTimeMilliseconds()});
var afterActive=now.AddMilliseconds(90).ToUnixTimeMilliseconds();var stops=new[]{OriginalReleasedStop(Stop(),enable,88,afterActive),!OriginalReleasedStop(Stop(false),enable,88,afterActive),
!OriginalReleasedStop(Stop(after:80),enable,88,afterActive),!OriginalReleasedStop(Stop(reason:"heat"),enable,88,afterActive)};
JsonElement Cycle(ulong selection=3,int off=101)=>JsonSerializer.SerializeToElement(new{overchargeControlQualified=true,qualifiedCycle=new{owner=new string('a',32),startRequestId=new string('b',32),selectionGeneration=selection,entityId="12",referenceWorldAddress="24",activeSamples=5,
lastActiveAtUnixMilliseconds=afterActive,releaseRequestId=new string('c',32),originalStopAtUnixMilliseconds=now.AddMilliseconds(100).ToUnixTimeMilliseconds(),offAtUnixMilliseconds=now.AddMilliseconds(off).ToUnixTimeMilliseconds()}});
var cycles=new[]{QualifiedCycle(Cycle(),enable,Stop(),afterActive,out _),!QualifiedCycle(Cycle(selection:4),enable,Stop(),afterActive,out _),!QualifiedCycle(Cycle(off:100),enable,Stop(),afterActive,out _)};
var stale=new InvalidOperationException("Fresh healthy player ship required for local navigation");var none=SystemNavigationAction.None;
using var stopped=new CancellationTokenSource();stopped.Cancel();
var sourceFrame=new SystemFlightFrame{Sequence=8,ObservedAt=now};
var transitionChecks=new[]{ReadNextAxisFrame(stale,none,false,default),!ReadNextAxisFrame(stale,SystemNavigationAction.EnterSupercruise,false,default),
!ReadNextAxisFrame(stale,SystemNavigationAction.RequestDocking,false,default),!ReadNextAxisFrame(stale,none,true,default),
!ReadNextAxisFrame(new TimeoutException(),none,false,default),!ReadNextAxisFrame(new InvalidOperationException("unknown or foreign owner"),none,false,default),
!ReadNextAxisFrame(new InvalidOperationException("Native target or primary-star envelope changed"),none,false,default),!ReadNextAxisFrame(stale,none,false,stopped.Token),
FreshSourceForInput(sourceFrame,7,now.AddMilliseconds(150)),!FreshSourceForInput(sourceFrame,8,now),!FreshSourceForInput(sourceFrame,7,now.AddMilliseconds(151))};
if(checks.Any(value=>!value)||stops.Any(value=>!value)||cycles.Any(value=>!value)||transitionChecks.Any(value=>!value))throw new InvalidDataException("Original target, independent stop, same-cycle or bounded transition fixtures failed.");
using var encoded=JsonDocument.Parse(JsonSerializer.Serialize(new CruiseOverchargeDecision(false,CruiseOverchargeReason.Unavailable),EvidenceJson));
if(encoded.RootElement.GetProperty("predictedHeatPercent").GetString()!="NaN")throw new InvalidDataException("Unavailable estimates lost their unknown evidence value.");
var channel=Channel.CreateUnbounded<object>();using var sink=new StringWriter();sink.Dispose();using var cancelled=new CancellationTokenSource();var finalSaved=false;Exception? evidenceFailure=null;
void Failed(Exception error){evidenceFailure??=error;cancelled.Cancel();}
channel.Writer.TryWrite(new{value=1});var fault=WriteEvidenceAsync(channel,sink,Failed);
CompleteEvidenceAsync(channel,fault,Failed,()=>{finalSaved=true;return Task.CompletedTask;}).GetAwaiter().GetResult();
if(evidenceFailure is null||!cancelled.IsCancellationRequested||channel.Writer.TryWrite(new{value=2})||!finalSaved)throw new InvalidDataException("Evidence I/O failure must immediately cancel, close the channel and preserve final reporting.");
var atcWaitChecks=OriginalDockingResetAcceptance.SelfTestAtcWait();
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,targetChecks=checks.Count,stopChecks=stops.Length,qualifiedCycleChecks=cycles.Length,transitionChecks=transitionChecks.Length,atcWaitChecks,evidenceChecks=3,gameCallsSent=0,inputsSent=0,entity,world},ToolboxSettings.JsonOptions));
}
internal static void ReplayPreflight(string path)
{
var reader=new Utf8JsonReader(File.ReadAllBytes(path));using var first=JsonDocument.ParseValue(ref reader);var record=first.RootElement;
var catalogue=record.GetProperty("catalogue").Deserialize<CurrentSystemSnapshot>(ToolboxSettings.JsonOptions)??throw new InvalidDataException("Saved original catalogue missing.");
var at=record.GetProperty("at").GetDateTimeOffset();var geometry=record.GetProperty("geometry");var originalRaw=record.TryGetProperty("raw",out var raw);
if(!originalRaw)
{
// Old attempt 01 saved geometry but omitted its raw header. These
// reconstructed clock/identity fields are for geometry replay only;
// they can never authorize live selection, input, or a successful flight.
raw=JsonSerializer.SerializeToElement(new{observedAtUnixMilliseconds=catalogue.ObservedAt.ToUnixTimeMilliseconds(),inGame=true,localPlayerShipVerified=true,
entityId=catalogue.EntityId.ToString(),referenceWorldAddress=geometry.GetProperty("worldAddress").GetString(),geometry});
}
var candidates=ClearFarTargets(catalogue,raw,at);
Console.WriteLine(JsonSerializer.Serialize(new{diagnosticOnly=true,service=NativeCruiseControlChannel.Service,geometryFromOriginalBodyCentres=true,controlCoordinatesFromFit=false,
originalRawHeaderSaved=originalRaw,reconstructedHeaderForOfflineReplayOnly=!originalRaw,candidateCount=candidates.Length,
targets=candidates.Select(row=>new{row.Key,row.WorldAddress,row.Name,distanceLightSeconds=row.DistanceMetres/LightSecond}),liveQualificationPassed=false,gameCallsSent=0,inputsSent=0},ToolboxSettings.JsonOptions));
}
}
using System.Diagnostics;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEEDToolBox.Core.Session;
/// <summary>One explicitly admitted empty SCO pulse, then the original Homer return. No SCA, cargo order, client creation or observer creation.</summary>
internal static class SCOControlFlightAcceptance
{
private const double LightSecond=299792458;
private const string FuelScope="Original SCO energy curve over pressure 0..1, when consuming; excludes other consumers and fuel scooping";
// Unknown local policy estimates stay explicitly unknown in evidence. The
// original samples and all finite-number acceptance predicates are unchanged.
private static readonly JsonSerializerOptions EvidenceJson=new(ToolboxSettings.JsonOptions){NumberHandling=JsonNumberHandling.AllowNamedFloatingPointLiterals};
private static async Task WriteEvidenceAsync(Channel<object> channel,TextWriter sink,Action<Exception> failed)
{
try{await foreach(var value in channel.Reader.ReadAllAsync()){await sink.WriteLineAsync(JsonSerializer.Serialize(value,EvidenceJson));await sink.FlushAsync();}}
catch(Exception error){channel.Writer.TryComplete(error);failed(error);throw;}
}
private static async Task CompleteEvidenceAsync(Channel<object> channel,Task writer,Action<Exception> failed,Func<Task> save)
{
channel.Writer.TryComplete();try{await writer;}catch(Exception error){failed(error);}finally{await save();}
}
private static bool True(JsonElement value,string key)=>value.TryGetProperty(key,out var item)&&item.ValueKind==JsonValueKind.True;
private static bool False(JsonElement value,string key)=>value.TryGetProperty(key,out var item)&&item.ValueKind==JsonValueKind.False;
private static Vector3d Vector(JsonElement value,string key){var array=value.GetProperty(key);return new(array[0].GetDouble(),array[1].GetDouble(),array[2].GetDouble());}
private static bool Finite(JsonElement value,string key,out double number){number=double.NaN;return value.TryGetProperty(key,out var item)&&item.ValueKind==JsonValueKind.Number&&item.TryGetDouble(out number)&&double.IsFinite(number);}
private static bool Stamp(JsonElement value,string key,out long at){at=0;return value.TryGetProperty(key,out var item)&&item.TryGetInt64(out at);}
private static bool ReadNextAxisFrame(Exception error,SystemNavigationAction action,bool mayIssueSco,CancellationToken token)
=>action==SystemNavigationAction.None&&!mayIssueSco&&NavigationRecovery.CanRetry(error,token)&&NavigationRecovery.Kind(error)==FlightRecovery.ReadNextFrame;
private static bool FreshSourceForInput(SystemFlightFrame frame,long lastApplied,DateTimeOffset now)
=>frame.Sequence>lastApplied&&frame.ObservedAt<=now&&now-frame.ObservedAt<=TimeSpan.FromMilliseconds(150);
private static SystemNavigationTarget Target(CurrentSystemSnapshot catalogue,SystemLocation row,SystemTargetKind kind)=>new(catalogue.SystemAddress,row.WorldAddress,row.Name,kind,row.MarketId)
{NativeKey=row.Key,NativeKind=row.NativeKind,NativeIdentity=row.Identity};
// Catalogue positions may use the current station frame. Only the matching
// original body's verified unified centre is used for this clearance check.
private static SystemLocation[] ClearFarTargets(CurrentSystemSnapshot catalogue,JsonElement raw,DateTimeOffset now)
{
if(!Stamp(raw,"observedAtUnixMilliseconds",out var at)||now.ToUnixTimeMilliseconds()-at is <0 or >250||!True(raw,"inGame")||!True(raw,"localPlayerShipVerified")
||raw.GetProperty("entityId").GetString()!=catalogue.EntityId.ToString()||now-catalogue.ObservedAt>TimeSpan.FromMilliseconds(250)||catalogue.ObservedAt>now)
return [];
var geometry=raw.GetProperty("geometry");
if(!True(geometry,"fresh")||!True(geometry,"referenceFrameVerified")||geometry.GetProperty("worldAddress").GetString()!=raw.GetProperty("referenceWorldAddress").GetString())return [];
var position=Vector(geometry,"position");if(!position.Finite)return [];
var bodies=geometry.GetProperty("nearbyBodies");if(bodies.ValueKind!=JsonValueKind.Array||bodies.GetArrayLength()==0)return [];
return catalogue.Locations.Where(row=>
{
if(row.Key<0||row.NativeKind<=0||row.WorldAddress==0||row.Identity.Length!=64||row.DistanceMetres is not {} distance
||!double.IsFinite(distance)||distance<50*LightSecond||row.Category is not(SystemLocationCategory.Star or SystemLocationCategory.Planet or SystemLocationCategory.LandablePlanet))return false;
var matching=bodies.EnumerateArray().Where(body=>body.TryGetProperty("bodyAddress",out var id)&&id.ValueKind==JsonValueKind.String&&id.GetString()==row.WorldAddress.ToString()).Take(2).ToArray();
if(matching.Length!=1)return false;
var point=Vector(matching[0],"centre");if(!point.Finite)return false;
var offset=point-position;var length=offset.Length;
if(!double.IsFinite(length)||Math.Abs(length-distance)>Math.Max(100,distance*1e-6))return false;
var direction=offset.Unit();var horizon=length-10*LightSecond;
foreach(var body in bodies.EnumerateArray())
{
if(!Finite(body,"exclusionRadius",out var radius)||radius<=0)return false;
var centre=Vector(body,"centre");if(!centre.Finite)return false;
var to=centre-position;var projection=Math.Clamp(Vector3d.Dot(to,direction),0,horizon);var closest=to-direction*projection;
if(closest.Length<=radius*1.2)return false;
}
return true;
}).OrderBy(row=>row.DistanceMetres).ThenBy(row=>row.Key).ToArray();
}
private static bool VerifiedSample(JsonElement status,ulong entity,ulong world,long now,out JsonElement sample)
{
sample=default;
if(!True(status,"sampleReady")||!status.TryGetProperty("sample",out sample)||sample.ValueKind!=JsonValueKind.Object||!True(sample,"verified")||!True(sample,"inSupercruise")||!True(sample,"stableFsd")
||sample.GetProperty("entityId").GetString()!=entity.ToString()||sample.GetProperty("referenceWorldAddress").GetString()!=world.ToString()||sample.GetProperty("rawFsdState").GetInt32()!=2
||!Stamp(sample,"observedAtUnixMilliseconds",out var at)||now-at is <0 or >200||!False(sample,"nativeAssistEnabled")||!True(sample,"fuelVerified")||!True(sample,"fuelBoundVerified")||!True(sample,"fuelRateLowerVerified")
||sample.GetProperty("fuelRateBoundsScope").GetString()!=FuelScope||!Finite(sample,"fuelRateLowerTonnesPerSecond",out var lower)||!Finite(sample,"fuelRateUpperTonnesPerSecond",out var upper)||lower<0||upper<=0||lower>upper
||!Finite(sample,"heatPercent",out var heat)||heat>=75||!Finite(sample,"fuelMainTonnes",out var fuel)||fuel<8)return false;
return true;
}
private static bool OriginalEnable(JsonElement receipt,int thread)=>True(receipt,"applied")&&True(receipt,"nativeMethodCalled")&&True(receipt,"argument")
&&receipt.GetProperty("nativeMethodRva").GetString()=="2eaacd0"&&receipt.GetProperty("owningThread").GetInt32()==thread&&receipt.GetProperty("actualStateAfterCall").GetInt32() is 1 or 2;
private static bool OriginalReleasedStop(JsonElement value,JsonElement enable,int thread,long afterActive)
=>True(value,"nativeMethodCalled")&&False(value,"argument")&&value.GetProperty("nativeMethodRva").GetString()=="2eaacd0"&&value.GetProperty("owningThread").GetInt32()==thread
&&value.GetProperty("owner").GetString()==enable.GetProperty("owner").GetString()&&value.GetProperty("startRequestId").GetString()==enable.GetProperty("requestId").GetString()
&&value.GetProperty("selectionGeneration").GetUInt64()==enable.GetProperty("selectionGeneration").GetUInt64()
&&value.GetProperty("entityId").GetString()==enable.GetProperty("entityId").GetString()&&value.GetProperty("referenceWorldAddress").GetString()==enable.GetProperty("referenceWorldAddress").GetString()
&&value.GetProperty("reason").GetString()=="released"&&value.GetProperty("releaseRequestId").ValueKind==JsonValueKind.String&&Guid.TryParseExact(value.GetProperty("releaseRequestId").GetString(),"N",out _)
&&value.GetProperty("stateBefore").GetInt32() is 1 or 2&&value.GetProperty("stateAfter").GetInt32() is 0 or 3
&&Stamp(value,"observedAtUnixMilliseconds",out var at)&&at>=afterActive;
private static bool QualifiedCycle(JsonElement status,JsonElement enable,JsonElement stopped,long lastActiveAt,out JsonElement proof)
{
proof=default;if(!True(status,"overchargeControlQualified")||!status.TryGetProperty("qualifiedCycle",out proof)||proof.ValueKind!=JsonValueKind.Object)return false;
return proof.GetProperty("owner").GetString()==enable.GetProperty("owner").GetString()&&proof.GetProperty("startRequestId").GetString()==enable.GetProperty("requestId").GetString()
&&proof.GetProperty("selectionGeneration").GetUInt64()==enable.GetProperty("selectionGeneration").GetUInt64()
&&proof.GetProperty("entityId").GetString()==enable.GetProperty("entityId").GetString()&&proof.GetProperty("referenceWorldAddress").GetString()==enable.GetProperty("referenceWorldAddress").GetString()
&&proof.GetProperty("activeSamples").GetInt32()>=5&&proof.GetProperty("lastActiveAtUnixMilliseconds").GetInt64()>=lastActiveAt
&&proof.GetProperty("releaseRequestId").GetString()==stopped.GetProperty("releaseRequestId").GetString()
&&proof.GetProperty("originalStopAtUnixMilliseconds").GetInt64()==stopped.GetProperty("observedAtUnixMilliseconds").GetInt64()
&&proof.GetProperty("offAtUnixMilliseconds").GetInt64()>stopped.GetProperty("observedAtUnixMilliseconds").GetInt64();
}
internal static async Task RunAsync(int pid,string directory,string recoveryPackage)
{
directory=Path.GetFullPath(directory);recoveryPackage=Path.GetFullPath(recoveryPackage);var root=Path.GetDirectoryName(directory)!;
if(Directory.Exists(directory))throw new IOException("Use a fresh one-shot SCO directory.");
using var exclusive=new FileStream(Path.Combine(root,"docking-fixture-wait.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);Directory.CreateDirectory(directory);
var report=Path.Combine(directory,"result.json");var explicitStop=Path.Combine(directory,"stop");var start=DateTimeOffset.UtcNow;
using var timeout=new CancellationTokenSource(TimeSpan.FromMinutes(15));var token=timeout.Token;using var sentinel=new Timer(_=>{if(File.Exists(explicitStop))timeout.Cancel();},null,100,100);
using var process=Process.GetProcessById(pid);var started=process.StartTime.ToUniversalTime().Ticks;
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;
if(!initial.InGame||!initial.Docked||initial.MarketId!=4210924803||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||NavigationSafety.HasExternalThreat(initial)||NavigationSafety.UnhealthyHull(initial))
throw new InvalidOperationException("Healthy empty Homer Corsair/5 required.");
using var guardian=await PersistentProtectionProcess.StartOrReuseAsync(pid,root,token,allowStart:false);PersistentProtectionProcess.Current=guardian;
FlightTestProtectionScope? protection=null;NativeSystemFlightControlDriver? driver=null;SystemNavigationTarget? selected=null;SystemLocation? farRow=null;
var phase="docked-preflight";string? failure=null,eventFailure=null;var maintainedBefore=false;var launched=false;var entered=false;var activeFive=false;var originalOff=false;var offObserved=false;var exited=false;var docked=false;var maintainedAfter=false;var recovered=false;var flightStarted=false;
var enterRequests=0;var dropRequests=0;var rebinds=0;var enableCalls=0;long lastInputSequence=0;var activeSamples=new List<JsonElement>();var sampleTimes=new HashSet<long>();var commands=new List<JsonElement>();var stops=new List<JsonElement>();
JsonElement? firstEnable=null,originalStop=null,offSample=null,qualifiedCycle=null,finalDock=null;ulong controlEntity=0,controlWorld=0;long lastActiveAt=0;int controlThread=0;
var events=Channel.CreateUnbounded<object>(new(){SingleReader=true,SingleWriter=true});
await using var log=new StreamWriter(new FileStream(Path.Combine(directory,"frames.jsonl"),FileMode.CreateNew,FileAccess.Write,FileShare.Read));
void EvidenceFailed(Exception error){eventFailure??="Evidence writer: "+error;timeout.Cancel();}
var writer=Task.Run(()=>WriteEvidenceAsync(events,log,EvidenceFailed));
void TryRecord(object value)=>events.Writer.TryWrite(value);
void Record(object value)
{
if(eventFailure is not null||!events.Writer.TryWrite(value)){eventFailure??="SCO evidence channel stopped.";timeout.Cancel();throw new IOException(eventFailure);}
}
async Task Save()
{
var temp=report+".tmp";await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(new{passed=maintainedBefore&&launched&&entered&&activeFive&&originalOff&&offObserved&&exited&&docked&&maintainedAfter&&failure is null&&eventFailure is null,
pid,started,start,at=DateTimeOffset.UtcNow,phase,maintainedBefore,launched,entered,activeFive,originalOff,offObserved,exited,docked,maintainedAfter,recovered,enableCalls,enterRequests,dropRequests,rebinds,
farTarget=farRow,activeSamples,commands,stops,firstEnable,originalStop,offSample,qualifiedCycle,finalDock,failure,eventFailure,gameSupercruiseAssistUsed=false,tradeOrdersSent=0,crossSystemFlight=false,persistent075Acceptance=false,clientTerminated=false},ToolboxSettings.JsonOptions));File.Move(temp,report,true);
}
async Task Phase(string value){phase=value;Console.WriteLine(value);await Save();}
void Session()
{
token.ThrowIfCancellationRequested();process.Refresh();var state=journal.State;
if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=started||!state.InGame||state.CommanderId!=initial.CommanderId||state.LoadGameAt!=initial.LoadGameAt||state.CurrentShipId!=5||state.CargoCount!=0||state.CargoCapacity!=192
||state.Navigation.CurrentSystemAddress!=initial.Navigation.CurrentSystemAddress||NavigationSafety.HasExternalThreat(state)||NavigationSafety.UnhealthyHull(state))throw new InvalidOperationException("SCO test ship, session, system or health identity changed.");
guardian.RequireReady(pid);if(eventFailure is not null)throw new InvalidDataException(eventFailure);
}
async Task Protection(CancellationToken cancellation)
{
cancellation.ThrowIfCancellationRequested();Session();
if(protection?.Stopped==true)
{
var nav=journal.State.Navigation;
if(protection.LastIssue!="飞船实体已切换,已暂停驾驶输入并继续监测健康。"||!(nav.SupercruiseEnteredAt>=start.AddSeconds(-1)||entered&&nav.SupercruiseExitedAt>=start.AddSeconds(-1)))throw new InvalidOperationException("The protection stop is not a verified SC scene transition.");
await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);rebinds++;
}
NativeFlightTestProtection.CheckBeforeInput(pid);
}
async Task<SystemFlightFrame> Read()
{
var limit=DateTimeOffset.UtcNow.AddSeconds(20);
while(true)
{
try
{
Session();var frame=await driver!.ReadSystemFlightAsync(token);await Protection(token);
if(frame.PlayerOverride||frame.ExternalThreat||frame.SupercruiseAssist is NativeAssistState.Engaged or NativeAssistState.Requested or NativeAssistState.Aligning||(frame.HeatPercent??100)>=75||frame.Mode is LocalFlightMode.OrbitalCruise or LocalFlightMode.Glide)
throw new InvalidOperationException("SCO smoke safety boundary exceeded.");
var sca=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");if(!False(sca,"enabled")||!False(sca,"engaged"))throw new InvalidDataException("Game SCA is not explicitly disabled.");return frame;
}
catch(Exception error)when(NavigationRecovery.CanRetry(error,token)&&DateTimeOffset.UtcNow<limit){Record(new{at=DateTimeOffset.UtcNow,phase,recovering=error.Message});await Task.Delay(40,token);}
}
}
async Task<bool> Apply(SystemFlightFrame frame,Vector3d? aim,double throttle,SystemNavigationPhase stage=SystemNavigationPhase.NormalApproach,SystemNavigationAction actions=SystemNavigationAction.None)
{
if(!FreshSourceForInput(frame,lastInputSequence,DateTimeOffset.UtcNow)){Record(new{at=DateTimeOffset.UtcNow,phase,skippedOldSource=true,frame.Sequence,frame.ObservedAt,actionSubmitted=false});return false;}
flightStarted=true;
try{await driver!.ApplySystemFlightAsync(new(stage,phase,aim,throttle,actions),frame,selected!,frame.ObservedAt.AddMilliseconds(250),token);lastInputSequence=frame.Sequence;return true;}
catch(Exception error)when(ReadNextAxisFrame(error,actions,stage is SystemNavigationPhase.Supercruise or SystemNavigationPhase.Aligning,token))
{Record(new{at=DateTimeOffset.UtcNow,phase,skippedStaleAxisFrame=true,error=error.Message,actionReplayed=false});return false;}
}
try
{
protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);
var pipe=new NavigationPipeClient(pid);using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);
var cargo=await pipe.CallAsync(new{op="commerce.inventory"},token);
if(!True(cargo,"verified")||cargo.GetProperty("count").GetInt32()!=0||cargo.GetProperty("capacity").GetInt32()!=192||cargo.GetProperty("credits").GetInt64()<4427098+200000)
throw new InvalidDataException("Original empty cargo and rebuy/service reserve have not been verified.");
var catalogue=await source.ReadLocationsAsync(token);var raw=await pipe.CallAsync(new{op="read"},token);var candidates=ClearFarTargets(catalogue,raw,DateTimeOffset.UtcNow);
Record(new{at=DateTimeOffset.UtcNow,phase,catalogue,raw,geometry=raw.GetProperty("geometry"),candidates});
farRow=candidates.FirstOrDefault()??throw new InvalidDataException("No original sufficiently distant and clear target is available; the ship remains docked.");
var home=catalogue.Locations.Single(row=>row.MarketId==4210924803&&row.Name=="Homer Platform");selected=Target(catalogue,home,SystemTargetKind.Station);
driver=new(pid,NativeSystemLocationReader.GameSha256,()=>journal.State,new NativeGameInputChannel(pid,NativeSystemLocationReader.GameSha256,Path.Combine(AppContext.BaseDirectory,"PluginPackage")),Protection,useNativeSupercruiseAssist:false);
await driver.ConfigureOriginalCruiseControlAsync(()=>8,allowUnqualifiedEmptyTrial:true,token);var control=driver.CruiseControl!;controlThread=(await control.InspectAsync(token)).OwningThread;
Action<JsonElement> observeStatus=value=>
{
Record(new{at=DateTimeOffset.UtcNow,kind="SCO-observed",value});
try
{
if(value.GetProperty("schema").GetInt32()!=1||value.GetProperty("processId").GetInt32()!=pid||value.GetProperty("service").GetString()!=NativeCruiseControlChannel.Service||value.GetProperty("gameSha256").GetString()!=NativeSystemLocationReader.GameSha256)
throw new InvalidDataException("SCO observation belongs to another controller service or game.");
if(controlEntity==0||!VerifiedSample(value,controlEntity,controlWorld,DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),out var sample))return;
var at=sample.GetProperty("observedAtUnixMilliseconds").GetInt64();
if(firstEnable is {} enable&&sample.GetProperty("state").GetInt32()==2&&at>=enable.GetProperty("observedAtUnixMilliseconds").GetInt64()&&sampleTimes.Add(at))
{
if(!True(sample,"controlTelemetryReady")||!True(sample,"geometryVerified")||!True(sample,"targetVerified")||True(sample,"collisionCourse"))throw new InvalidDataException("Active SCO no longer has its verified clear original target.");
activeSamples.Add(sample.Clone());lastActiveAt=at;activeFive=activeSamples.Count>=5;
}
if(firstEnable is {} enabled&&originalStop is {} stopped&&sample.GetProperty("state").GetInt32() is 0 or 3&&False(value,"owned")&&at>stopped.GetProperty("observedAtUnixMilliseconds").GetInt64()
&&QualifiedCycle(value,enabled,stopped,lastActiveAt,out var cycle)&&cycle.GetProperty("offAtUnixMilliseconds").GetInt64()<=at)
{offSample=sample.Clone();qualifiedCycle=cycle.Clone();offObserved=true;}
}
catch(Exception error){eventFailure="SCO observation schema: "+error.Message;}
};
control.Observed+=observeStatus;
control.OriginalReceipt+=value=>
{
commands.Add(value.Clone());Record(new{at=DateTimeOffset.UtcNow,kind="SCO-original-receipt",value});
try{if(OriginalEnable(value,controlThread)){enableCalls++;firstEnable??=value.Clone();if(enableCalls>1)eventFailure="A second original SCO enable is not allowed in this one-shot test.";}}
catch(Exception error){eventFailure="SCO original receipt schema: "+error.Message;}
};
control.OriginalStopReceipt+=value=>
{
stops.Add(value.Clone());Record(new{at=DateTimeOffset.UtcNow,kind="SCO-original-stop",value});
try{if(firstEnable is {} enable&&activeFive&&OriginalReleasedStop(value,enable,controlThread,lastActiveAt)){originalStop=value.Clone();originalOff=true;}}
catch(Exception error){eventFailure="SCO stop receipt schema: "+error.Message;}
};
await driver.SelectAsync(selected,token);await Read();await protection.DisposeAsync();protection=null;
await Phase("docked-maintenance-before");await DockedMaintenance.RunAsync(pid,Path.Combine(directory,"maintenance-before"),token);maintainedBefore=true;
protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);await Phase("original-launch");flightStarted=true;await driver.RequestLaunchAsync(token);var launchLimit=DateTimeOffset.UtcNow.AddMinutes(4);var launchSeen=false;
while(true)
{
var frame=await Read();var launching=True(driver.LastTelemetry!.Value.GetProperty("docking"),"nativeLaunchActive");launchSeen|=launching;Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(launchSeen&&!launching&&!journal.State.Docked&&journal.State.Navigation.UndockedAt>=start.AddSeconds(-1))break;
if(DateTimeOffset.UtcNow>launchLimit)throw new TimeoutException("Original launch did not complete.");await Task.Delay(60,token);
}
driver.ConfirmDockingValidationLaunchCompleted();launched=true;await driver.PrepareAsync(token);await Phase("original-sc-entry");var entryLimit=DateTimeOffset.UtcNow.AddSeconds(90);
while(true)
{
var frame=await Read();Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(frame.Mode==LocalFlightMode.Supercruise){entered=true;await Apply(frame,null,0);break;}
if(DateTimeOffset.UtcNow>entryLimit||frame.TargetDistance>60000)throw new TimeoutException("Bounded original SC entry did not complete.");
var outward=(frame.Position-frame.TargetPosition).Unit();var aligned=Vector3d.AngleDegrees(frame.Forward,outward)<=5;
var action=enterRequests==0&&aligned&&frame.FsdReady?SystemNavigationAction.EnterSupercruise:SystemNavigationAction.None;
var sent=await Apply(frame,outward,aligned?1:.25,actions:action);if(sent&&action!=SystemNavigationAction.None)enterRequests++;await Task.Delay(40,token);
}
await driver.ReleaseSystemFlightAsync();catalogue=await source.ReadLocationsAsync(token);var liveFar=catalogue.Locations.SingleOrDefault(row=>row.WorldAddress==farRow.WorldAddress&&row.Name==farRow.Name)
??throw new InvalidDataException("The chosen original far body did not survive the verified SC scene transition.");
selected=Target(catalogue,liveFar,SystemTargetKind.Orbit);await driver.SelectAsync(selected,token);await Phase("align-real-far-target");var aimLimit=DateTimeOffset.UtcNow.AddSeconds(60);
while(true)
{
var frame=await Read();var aim=(frame.TargetPosition-frame.Position).Unit();Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(frame.TargetObscured||frame.CollisionCourse||frame.TargetDistance<30*LightSecond)throw new InvalidDataException("The actual selected far target is obscured, unsafe or too close.");
await Apply(frame,aim,0);if(Vector3d.AngleDegrees(frame.Forward,aim)<=2)break;if(DateTimeOffset.UtcNow>aimLimit)throw new TimeoutException("The real far target could not be aligned.");await Task.Delay(40,token);
}
await Phase("one-original-sco-pulse");var pulseLimit=DateTimeOffset.UtcNow.AddSeconds(8);
while(!activeFive)
{
var frame=await Read();controlEntity=frame.ShipEntity;controlWorld=frame.ReferenceWorld;Record(new{at=DateTimeOffset.UtcNow,phase,frame,decision=control.LastDecision});
if(stops.Count>0)throw new InvalidOperationException("SCO was already stopped by an interlock before five Active frames; no second pulse will be attempted.");
if(DateTimeOffset.UtcNow>pulseLimit)throw new TimeoutException("Five real SCO Active frames were not observed in the single bounded pulse.");
await Apply(frame,(frame.TargetPosition-frame.Position).Unit(),.25,SystemNavigationPhase.Supercruise);await Task.Delay(35,token);
}
await Phase("explicit-original-sco-off");var offLimit=DateTimeOffset.UtcNow.AddSeconds(2);await control.ReleaseAsync(token);Session();
while(!offObserved&&originalOff&&DateTimeOffset.UtcNow<offLimit)
{
// The original physics timestamp can precede the stop call in
// the same game tick. Read the next original sample; never repeat release.
observeStatus(await new NavigationPipeClient(pid,NativeCruiseControlChannel.Service).CallAsync(new{op="read"},token));Session();
if(!offObserved)await Task.Delay(15,token);
}
if(!originalOff||!offObserved||enableCalls!=1)throw new InvalidDataException("An independent original false-call receipt and a subsequent actual Off sample are required.");
var stoppedFrame=await Read();await Apply(stoppedFrame,null,0);await driver.ReleaseSystemFlightAsync();catalogue=await source.ReadLocationsAsync(token);
home=catalogue.Locations.Single(row=>row.MarketId==4210924803&&row.Name=="Homer Platform");selected=Target(catalogue,home,SystemTargetKind.Station);await driver.SelectAsync(selected,token);
await Phase("software-sc-return-homer");var returnLimit=DateTimeOffset.UtcNow.AddMinutes(5);var navigator=new SystemFlightNavigator();
while(true)
{
var frame=await Read();Record(new{at=DateTimeOffset.UtcNow,phase,frame});
if(frame.Mode==LocalFlightMode.Normal){exited=true;await Apply(frame,null,0);break;}
if(DateTimeOffset.UtcNow>returnLimit)throw new TimeoutException("The bounded original SC return did not complete.");
if(frame.Mode==LocalFlightMode.Transition){await Task.Delay(40,token);continue;}
var decision=navigator.Decide(frame,selected,DateTimeOffset.UtcNow);
if(decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist)||decision.Phase==SystemNavigationPhase.Stopped)throw new InvalidDataException("The return cannot use SCA or an unverified route.");
if(!FreshSourceForInput(frame,lastInputSequence,DateTimeOffset.UtcNow)){await Task.Delay(15,token);continue;}
if(decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage)&&dropRequests>0){await Task.Delay(40,token);continue;}
// Keep the existing software speed/attitude policy while forbidding
// any second SCO pulse during this short qualification return.
var returned=await Apply(frame,decision.Direction,decision.Throttle??-1,actions:decision.Actions);
if(returned&&decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage))dropRequests++;await Task.Delay(40,token);
}
await driver.ReleaseSystemFlightAsync();await protection.DisposeAsync();protection=null;await Phase("original-ai-return-homer");
await OriginalDockingResetAcceptance.RunAsync(pid,Path.Combine(directory,"return-docking.json"),recoveryPackage,requireNonzero:false,sharedStopFile:explicitStop,protectionWorkRoot:root);docked=true;PersistentProtectionProcess.Current=guardian;
await Phase("docked-maintenance-after");await DockedMaintenance.RunAsync(pid,Path.Combine(directory,"maintenance-after"),token);maintainedAfter=true;
finalDock=await pipe.CallAsync(new{op="commerce.docked.status",marketId=4210924803},token);
if(!True(finalDock.Value,"ready")||!True(finalDock.Value,"serviceReady"))throw new InvalidDataException("Final actual Homer services are not ready.");await Phase("passed-one-original-sco-pulse-and-return");
}
catch(Exception error)
{
failure=error.ToString();TryRecord(new{at=DateTimeOffset.UtcNow,phase,failure});await Save();Console.WriteLine(error.Message);
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();}catch(Exception release){TryRecord(new{releaseFailure=release.ToString()});}
if(protection is not null){await protection.DisposeAsync();protection=null;}
if(flightStarted&&!File.Exists(explicitStop)&&!process.HasExited&&!journal.State.Docked&&journal.State.CurrentShipId==5&&journal.State.CargoCount==0&&journal.State.LoadGameAt==initial.LoadGameAt)
{
try
{await Phase("same-process-original-menu-recovery");await OriginalDockingResetAcceptance.EnterPauseAsync(pid,guardian,Path.Combine(directory,"recovery-pause.json"));await SessionRecoveryAcceptance.RunAsync(pid,recoveryPackage,Path.Combine(directory,"recovery-menu.json"),true,root);await GameSessionAcceptance.StartAsync(settings.PluginGameExecutable,Path.Combine(directory,"recovery-solo.json"),pid);recovered=true;}
catch(Exception recovery){TryRecord(new{recoveryFailure=recovery.ToString(),clientTerminated=false});}
}
}
finally
{
try
{
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();}catch(Exception release){TryRecord(new{finalReleaseFailure=release.ToString()});}
if(protection is not null)await protection.DisposeAsync();
}
finally
{
PersistentProtectionProcess.Current=null;await CompleteEvidenceAsync(events,writer,EvidenceFailed,Save);
}
}
if(failure is not null||eventFailure is not null)throw new InvalidOperationException("One-shot SCO qualification failed; evidence was preserved and the pulse was not repeated.");
}
/// <summary>Completes an already failed empty attempt from its new Solo. Does not configure any SCO controller.</summary>
internal static async Task FinishReturnAsync(int pid,string directory,string recoveryPackage,string attemptName)
{
if(attemptName.Length is 0 or >48||attemptName.Any(c=>!(char.IsAsciiLetterLower(c)||char.IsAsciiDigit(c)||c=='-')))throw new ArgumentException("A fresh bounded return attempt name is required.");
directory=Path.GetFullPath(directory);recoveryPackage=Path.GetFullPath(recoveryPackage);var root=Path.GetDirectoryName(directory)!;
var report=Path.Combine(directory,attemptName+".json");var stopFile=Path.Combine(directory,"stop");
if(File.Exists(report)||File.Exists(report+".jsonl")||Directory.Exists(Path.Combine(directory,"maintenance-after")))throw new IOException("Preserve prior return and monetary evidence.");
using var original=JsonDocument.Parse(await File.ReadAllTextAsync(Path.Combine(directory,"result.json")));
using var process=Process.GetProcessById(pid);var ticks=process.StartTime.ToUniversalTime().Ticks;
if(original.RootElement.GetProperty("pid").GetInt32()!=pid||original.RootElement.GetProperty("started").GetInt64()!=ticks||original.RootElement.GetProperty("passed").GetBoolean())throw new InvalidDataException("One actual failed same-process SCO attempt is required.");
using var lease=new FileStream(Path.Combine(root,"docking-fixture-wait.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
using var timeout=new CancellationTokenSource(TimeSpan.FromMinutes(9));var token=timeout.Token;
using var sentinel=new Timer(_=>{if(File.Exists(stopFile))timeout.Cancel();},null,100,100);
using var guardian=await PersistentProtectionProcess.StartOrReuseAsync(pid,root,token,allowStart:false);PersistentProtectionProcess.Current=guardian;
var settings=ToolboxSettings.Load(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),"XFEstudio","XFEEDToolBox","settings.json"));
await using var journal=new JournalMonitor(settings.JournalDirectory);await journal.StartAsync();var initial=journal.State;var start=DateTimeOffset.UtcNow;
var originalStart=original.RootElement.GetProperty("start").GetDateTimeOffset();var soloRecovered=initial.LoadGameAt>originalStart;
ulong originalEntity;
{var first=new Utf8JsonReader(await File.ReadAllBytesAsync(Path.Combine(directory,"frames.jsonl")));using var originalFrame=JsonDocument.ParseValue(ref first);originalEntity=ulong.Parse(originalFrame.RootElement.GetProperty("raw").GetProperty("entityId").GetString()!);}
var originalEntryConfirmed=original.RootElement.GetProperty("enterRequests").GetInt32()==1&&original.RootElement.GetProperty("enableCalls").GetInt32()==0&&original.RootElement.GetProperty("firstEnable").ValueKind==JsonValueKind.Null;
if(!initial.InGame||initial.Docked||initial.CurrentShipId!=5||initial.CargoCount!=0||initial.CargoCapacity!=192||initial.LoadGameAt is null||!soloRecovered&&(!originalEntryConfirmed||initial.Navigation.SupercruiseEnteredAt is not {} scAt||scAt<originalStart)
||NavigationSafety.HasExternalThreat(initial)||NavigationSafety.UnhealthyHull(initial))throw new InvalidDataException("A new healthy empty same-PID Solo entry must be confirmed before return.");
FlightTestProtectionScope? protection=null;NativeSystemFlightControlDriver? driver=null;SystemNavigationTarget? target=null;
var phase="return-preflight";string? failure=null;var inputStarted=false;var entered=false;var exited=false;var docked=false;var maintained=false;var recoveredAgain=false;var enters=0;var drops=0;var rebinds=0;long lastInputSequence=0;JsonElement? finalDock=null,health=null;
await using var log=new StreamWriter(new FileStream(report+".jsonl",FileMode.CreateNew,FileAccess.Write,FileShare.Read));
async Task Record(object value){await log.WriteLineAsync(JsonSerializer.Serialize(value,EvidenceJson));await log.FlushAsync();}
async Task Save()
{
var temp=report+".tmp";await File.WriteAllTextAsync(temp,JsonSerializer.Serialize(new{passed=docked&&maintained&&failure is null,pid,ticks,start,at=DateTimeOffset.UtcNow,phase,entered,exited,docked,maintained,recoveredAgain,enters,drops,rebinds,finalDock,health,failure,
originalAttempt=Path.Combine(directory,"result.json"),originalFailurePreserved=true,originalSoloRecoveryConfirmed=soloRecovered,sameLoadGameContinuation=!soloRecovered,originalEntity,SCOControllerConfigured=false,overchargeCommandsSent=0,gameSupercruiseAssistUsed=false,tradeOrdersSent=0,clientTerminated=false},ToolboxSettings.JsonOptions));File.Move(temp,report,true);
}
async Task Phase(string value){phase=value;Console.WriteLine(value);await Save();}
void Session()
{
token.ThrowIfCancellationRequested();process.Refresh();var state=journal.State;
if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=ticks||!state.InGame||state.CommanderId!=initial.CommanderId||state.LoadGameAt!=initial.LoadGameAt||state.CurrentShipId!=5||state.CargoCount!=0||state.CargoCapacity!=192
||state.Navigation.CurrentSystemAddress!=initial.Navigation.CurrentSystemAddress||NavigationSafety.HasExternalThreat(state)||NavigationSafety.UnhealthyHull(state))throw new InvalidDataException("The same-process empty return identity or safety changed.");
guardian.RequireReady(pid);
}
async Task Protect(CancellationToken cancellation)
{
cancellation.ThrowIfCancellationRequested();Session();
if(protection?.Stopped==true)
{
if(protection.LastIssue!="飞船实体已切换,已暂停驾驶输入并继续监测健康。"||!(journal.State.Navigation.SupercruiseEnteredAt>=start.AddSeconds(-1)||entered&&journal.State.Navigation.SupercruiseExitedAt>=start.AddSeconds(-1)))throw new InvalidDataException("The return protection cannot be rebound without an original SC transition.");
await protection.DisposeAsync();protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);rebinds++;
}
NativeFlightTestProtection.CheckBeforeInput(pid);
}
async Task<SystemFlightFrame> Read()
{
var limit=DateTimeOffset.UtcNow.AddSeconds(20);
while(true)
{
try
{
Session();var frame=await driver!.ReadSystemFlightAsync(token);await Protect(token);var assist=driver.LastTelemetry!.Value.GetProperty("supercruiseAssist");
if(frame.PlayerOverride||frame.ExternalThreat||(frame.HeatPercent??100)>=75||frame.Mode is LocalFlightMode.Glide or LocalFlightMode.OrbitalCruise||!False(assist,"enabled")||!False(assist,"engaged"))throw new InvalidDataException("The return requires healthy software navigation and explicitly disabled SCA.");
return frame;
}
catch(Exception error)when(NavigationRecovery.CanRetry(error,token)&&DateTimeOffset.UtcNow<limit){await Record(new{at=DateTimeOffset.UtcNow,phase,recovering=error.Message});await Task.Delay(40,token);}
}
}
async Task<bool> Apply(SystemFlightFrame frame,Vector3d? aim,double throttle,SystemNavigationAction action=SystemNavigationAction.None)
{
if(!FreshSourceForInput(frame,lastInputSequence,DateTimeOffset.UtcNow)){await Record(new{at=DateTimeOffset.UtcNow,phase,skippedOldSource=true,frame.Sequence,frame.ObservedAt,actionSubmitted=false});return false;}
inputStarted=true;
try{await driver!.ApplySystemFlightAsync(new(SystemNavigationPhase.NormalApproach,phase,aim,throttle,action),frame,target!,frame.ObservedAt.AddMilliseconds(250),token);lastInputSequence=frame.Sequence;return true;}
catch(Exception error)when(ReadNextAxisFrame(error,action,false,token))
{await Record(new{at=DateTimeOffset.UtcNow,phase,skippedStaleAxisFrame=true,error=error.Message,actionReplayed=false});return false;}
}
try
{
protection=await FlightTestProtectionScope.ArmAsync(pid,exitForFailedHandoff:false);var pipe=new NavigationPipeClient(pid);
var inventory=await pipe.CallAsync(new{op="commerce.inventory"},token);if(!True(inventory,"verified")||inventory.GetProperty("count").GetInt32()!=0||inventory.GetProperty("capacity").GetInt32()!=192)throw new InvalidDataException("The original empty inventory is not confirmed.");
using var source=new NativeCurrentSystemSource(pid,NativeSystemLocationReader.GameSha256,()=>journal.State);var catalogue=await source.ReadLocationsAsync(token);var home=catalogue.Locations.Single(row=>row.MarketId==4210924803&&row.Name=="Homer Platform");target=Target(catalogue,home,SystemTargetKind.Station);
driver=new(pid,NativeSystemLocationReader.GameSha256,()=>journal.State,new NativeGameInputChannel(pid,NativeSystemLocationReader.GameSha256,Path.Combine(AppContext.BaseDirectory,"PluginPackage")),Protect,useNativeSupercruiseAssist:false);
await driver.SelectAsync(target,token);await driver.PrepareAsync(token);var frame=await Read();await Record(new{at=DateTimeOffset.UtcNow,phase,frame,raw=await pipe.CallAsync(new{op="read"},token),inventory});
if(frame.TargetDistance>20000000||frame.TargetObscured||frame.CollisionCourse)throw new InvalidDataException("The original Homer return is outside the admitted clear local volume.");
if(!soloRecovered&&(frame.Mode!=LocalFlightMode.Supercruise||frame.ShipEntity==originalEntity))throw new InvalidDataException("The original requested old-to-new main-ship SC transition is not confirmed.");
if(frame.Mode==LocalFlightMode.Normal&&frame.TargetDistance<=40000)exited=true;
else
{
await Phase("return-software-sc-entry");var until=DateTimeOffset.UtcNow.AddSeconds(90);
while(frame.Mode!=LocalFlightMode.Supercruise)
{
if(DateTimeOffset.UtcNow>until)throw new TimeoutException("The single original return SC entry did not complete.");
if(frame.Mode==LocalFlightMode.Transition){await Task.Delay(40,token);frame=await Read();continue;}
if(frame.Mode!=LocalFlightMode.Normal||frame.TargetObscured||frame.CollisionCourse)throw new InvalidDataException("The original local SC return geometry changed.");
var direction=(frame.TargetPosition-frame.Position).Unit();var aligned=Vector3d.AngleDegrees(frame.Forward,direction)<=5;var action=aligned&&frame.FsdReady&&enters==0?SystemNavigationAction.EnterSupercruise:SystemNavigationAction.None;
await Record(new{at=DateTimeOffset.UtcNow,phase,frame});var sent=await Apply(frame,direction,aligned?1:0,action);if(sent&&action!=SystemNavigationAction.None)enters++;await Task.Delay(40,token);frame=await Read();
}
entered=true;await Phase("return-software-sc-approach");var approachUntil=DateTimeOffset.UtcNow.AddMinutes(3);DateTimeOffset? exitUntil=null;var navigator=new SystemFlightNavigator();
while(true)
{
await Record(new{at=DateTimeOffset.UtcNow,phase,frame});if(frame.Mode==LocalFlightMode.Normal){exited=true;await Apply(frame,null,0);break;}
if(DateTimeOffset.UtcNow>(exitUntil??approachUntil)||frame.TargetDistance>20000000)throw new TimeoutException("The original one-shot safe return exit did not complete.");
if(frame.Mode==LocalFlightMode.Transition){await Task.Delay(40,token);frame=await Read();continue;}
if(frame.Mode!=LocalFlightMode.Supercruise)throw new InvalidDataException("Unexpected original mode during safe return.");
if(drops>0){await Task.Delay(40,token);frame=await Read();continue;}
var decision=navigator.Decide(frame,target,DateTimeOffset.UtcNow);
if(decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist)||decision.Phase==SystemNavigationPhase.Stopped)throw new InvalidDataException("The original software return cannot use SCA or unverified geometry.");
var sent=await Apply(frame,decision.Direction,decision.Throttle??-1,decision.Actions);
if(sent&&decision.Actions.HasFlag(SystemNavigationAction.SafeDisengage)){drops++;exitUntil=DateTimeOffset.UtcNow.AddSeconds(45);await Phase("return-original-safe-exit");}
await Task.Delay(40,token);frame=await Read();
}
}
await driver.ReleaseSystemFlightAsync();await protection.DisposeAsync();protection=null;await Phase("return-original-g-docking");
await OriginalDockingResetAcceptance.RunAsync(pid,Path.Combine(directory,attemptName+"-docking.json"),recoveryPackage,requireNonzero:false,sharedStopFile:stopFile,protectionWorkRoot:root);docked=true;PersistentProtectionProcess.Current=guardian;
await Phase("return-original-maintenance");await DockedMaintenance.RunAsync(pid,Path.Combine(directory,"maintenance-after"),token);maintained=true;
finalDock=await pipe.CallAsync(new{op="commerce.docked.status",marketId=4210924803},token);health=await pipe.CallAsync(new{op="health.read"},token);guardian.RequireReady(pid);
if(!True(finalDock.Value,"ready")||!True(finalDock.Value,"serviceReady")||!True(health.Value.GetProperty("shipHealth"),"verified"))throw new InvalidDataException("The returned original Homer services and health are not ready.");await Phase("returned-and-maintained");
}
catch(Exception error)
{
failure=error.ToString();Console.WriteLine(error.Message);await Save();
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();}catch{}
if(protection is not null){await protection.DisposeAsync();protection=null;}
if(inputStarted&&!File.Exists(stopFile)&&!process.HasExited&&!journal.State.Docked&&journal.State.LoadGameAt==initial.LoadGameAt)
{
try{await OriginalDockingResetAcceptance.EnterPauseAsync(pid,guardian,Path.Combine(directory,attemptName+"-pause.json"));await SessionRecoveryAcceptance.RunAsync(pid,recoveryPackage,Path.Combine(directory,attemptName+"-menu.json"),true,root);await GameSessionAcceptance.StartAsync(settings.PluginGameExecutable,Path.Combine(directory,attemptName+"-solo.json"),pid);recoveredAgain=true;}
catch(Exception recovery){failure+="\nReturn recovery: "+recovery;}
}
}
finally
{
try{if(driver is not null)await driver.ReleaseSystemFlightAsync();if(protection is not null)await protection.DisposeAsync();}
finally{PersistentProtectionProcess.Current=null;await Save();}
}
if(failure is not null)throw new InvalidOperationException("Same-attempt return incomplete; the failure is preserved and no SCO pulse was repeated.");
}
internal static void SelfTest()
{
var now=DateTimeOffset.FromUnixTimeMilliseconds(1700000000000);var hash=new string('a',64);var entity=12UL;var world=24UL;
JsonElement Raw(double radius=1,bool includeFar=true)=>JsonSerializer.SerializeToElement(new{observedAtUnixMilliseconds=now.ToUnixTimeMilliseconds(),inGame=true,localPlayerShipVerified=true,entityId="12",referenceWorldAddress="24",
geometry=new{fresh=true,referenceFrameVerified=true,worldAddress="24",position=new[]{0d,0,0},nearbyBodies=new[]{new{bodyAddress="101",centre=new[]{100*LightSecond,0d,0},exclusionRadius=1d},new{bodyAddress="999",centre=new[]{0d,1000000,0},exclusionRadius=radius}}.Skip(includeFar?0:1).ToArray()}});
var far=new SystemLocation(1,101,"real far body","",3,SystemLocationCategory.Planet,null,new(-111,13,17),100*LightSecond,false,hash);
CurrentSystemSnapshot Catalogue(params SystemLocation[] rows)=>new(1,"Fixture",entity,1,now,rows);
var checks=new List<bool>{ClearFarTargets(Catalogue(far),Raw(),now).Length==1,ClearFarTargets(Catalogue(far with{DistanceMetres=1}),Raw(),now).Length==0,
ClearFarTargets(Catalogue(far),Raw(includeFar:false),now).Length==0,ClearFarTargets(Catalogue(far),Raw(2000000),now).Length==0,
ClearFarTargets(Catalogue(far),Raw(),now.AddMilliseconds(251)).Length==0,ClearFarTargets(Catalogue(far with{DistanceMetres=200*LightSecond}),Raw(),now).Length==0,
ClearFarTargets(Catalogue(far with{Category=SystemLocationCategory.Station}),Raw(),now).Length==0,ClearFarTargets(Catalogue(far with{Identity=""}),Raw(),now).Length==0};
var enable=JsonSerializer.SerializeToElement(new{owner=new string('a',32),requestId=new string('b',32),selectionGeneration=3,entityId="12",referenceWorldAddress="24"});
JsonElement Stop(bool called=true,long after=100,string reason="released")=>JsonSerializer.SerializeToElement(new{owner=new string('a',32),startRequestId=new string('b',32),selectionGeneration=3,
entityId="12",referenceWorldAddress="24",nativeMethodRva="2eaacd0",nativeMethodCalled=called,argument=false,owningThread=88,releaseRequestId=new string('c',32),stateBefore=2,stateAfter=3,reason,observedAtUnixMilliseconds=now.AddMilliseconds(after).ToUnixTimeMilliseconds()});
var afterActive=now.AddMilliseconds(90).ToUnixTimeMilliseconds();var stops=new[]{OriginalReleasedStop(Stop(),enable,88,afterActive),!OriginalReleasedStop(Stop(false),enable,88,afterActive),
!OriginalReleasedStop(Stop(after:80),enable,88,afterActive),!OriginalReleasedStop(Stop(reason:"heat"),enable,88,afterActive)};
JsonElement Cycle(ulong selection=3,int off=101)=>JsonSerializer.SerializeToElement(new{overchargeControlQualified=true,qualifiedCycle=new{owner=new string('a',32),startRequestId=new string('b',32),selectionGeneration=selection,entityId="12",referenceWorldAddress="24",activeSamples=5,
lastActiveAtUnixMilliseconds=afterActive,releaseRequestId=new string('c',32),originalStopAtUnixMilliseconds=now.AddMilliseconds(100).ToUnixTimeMilliseconds(),offAtUnixMilliseconds=now.AddMilliseconds(off).ToUnixTimeMilliseconds()}});
var cycles=new[]{QualifiedCycle(Cycle(),enable,Stop(),afterActive,out _),!QualifiedCycle(Cycle(selection:4),enable,Stop(),afterActive,out _),!QualifiedCycle(Cycle(off:100),enable,Stop(),afterActive,out _)};
var stale=new InvalidOperationException("Fresh healthy player ship required for local navigation");var none=SystemNavigationAction.None;
using var stopped=new CancellationTokenSource();stopped.Cancel();
var sourceFrame=new SystemFlightFrame{Sequence=8,ObservedAt=now};
var transitionChecks=new[]{ReadNextAxisFrame(stale,none,false,default),!ReadNextAxisFrame(stale,SystemNavigationAction.EnterSupercruise,false,default),
!ReadNextAxisFrame(stale,SystemNavigationAction.RequestDocking,false,default),!ReadNextAxisFrame(stale,none,true,default),
!ReadNextAxisFrame(new TimeoutException(),none,false,default),!ReadNextAxisFrame(new InvalidOperationException("unknown or foreign owner"),none,false,default),
!ReadNextAxisFrame(new InvalidOperationException("Native target or primary-star envelope changed"),none,false,default),!ReadNextAxisFrame(stale,none,false,stopped.Token),
FreshSourceForInput(sourceFrame,7,now.AddMilliseconds(150)),!FreshSourceForInput(sourceFrame,8,now),!FreshSourceForInput(sourceFrame,7,now.AddMilliseconds(151))};
if(checks.Any(value=>!value)||stops.Any(value=>!value)||cycles.Any(value=>!value)||transitionChecks.Any(value=>!value))throw new InvalidDataException("Original target, independent stop, same-cycle or bounded transition fixtures failed.");
using var encoded=JsonDocument.Parse(JsonSerializer.Serialize(new CruiseOverchargeDecision(false,CruiseOverchargeReason.Unavailable),EvidenceJson));
if(encoded.RootElement.GetProperty("predictedHeatPercent").GetString()!="NaN")throw new InvalidDataException("Unavailable estimates lost their unknown evidence value.");
var channel=Channel.CreateUnbounded<object>();using var sink=new StringWriter();sink.Dispose();using var cancelled=new CancellationTokenSource();var finalSaved=false;Exception? evidenceFailure=null;
void Failed(Exception error){evidenceFailure??=error;cancelled.Cancel();}
channel.Writer.TryWrite(new{value=1});var fault=WriteEvidenceAsync(channel,sink,Failed);
CompleteEvidenceAsync(channel,fault,Failed,()=>{finalSaved=true;return Task.CompletedTask;}).GetAwaiter().GetResult();
if(evidenceFailure is null||!cancelled.IsCancellationRequested||channel.Writer.TryWrite(new{value=2})||!finalSaved)throw new InvalidDataException("Evidence I/O failure must immediately cancel, close the channel and preserve final reporting.");
var atcWaitChecks=OriginalDockingResetAcceptance.SelfTestAtcWait();
Console.WriteLine(JsonSerializer.Serialize(new{passed=true,targetChecks=checks.Count,stopChecks=stops.Length,qualifiedCycleChecks=cycles.Length,transitionChecks=transitionChecks.Length,atcWaitChecks,evidenceChecks=3,gameCallsSent=0,inputsSent=0,entity,world},ToolboxSettings.JsonOptions));
}
internal static void ReplayPreflight(string path)
{
var reader=new Utf8JsonReader(File.ReadAllBytes(path));using var first=JsonDocument.ParseValue(ref reader);var record=first.RootElement;
var catalogue=record.GetProperty("catalogue").Deserialize<CurrentSystemSnapshot>(ToolboxSettings.JsonOptions)??throw new InvalidDataException("Saved original catalogue missing.");
var at=record.GetProperty("at").GetDateTimeOffset();var geometry=record.GetProperty("geometry");var originalRaw=record.TryGetProperty("raw",out var raw);
if(!originalRaw)
{
// Old attempt 01 saved geometry but omitted its raw header. These
// reconstructed clock/identity fields are for geometry replay only;
// they can never authorize live selection, input, or a successful flight.
raw=JsonSerializer.SerializeToElement(new{observedAtUnixMilliseconds=catalogue.ObservedAt.ToUnixTimeMilliseconds(),inGame=true,localPlayerShipVerified=true,
entityId=catalogue.EntityId.ToString(),referenceWorldAddress=geometry.GetProperty("worldAddress").GetString(),geometry});
}
var candidates=ClearFarTargets(catalogue,raw,at);
Console.WriteLine(JsonSerializer.Serialize(new{diagnosticOnly=true,service=NativeCruiseControlChannel.Service,geometryFromOriginalBodyCentres=true,controlCoordinatesFromFit=false,
originalRawHeaderSaved=originalRaw,reconstructedHeaderForOfflineReplayOnly=!originalRaw,candidateCount=candidates.Length,
targets=candidates.Select(row=>new{row.Key,row.WorldAddress,row.Name,distanceLightSeconds=row.DistanceMetres/LightSecond}),liveQualificationPassed=false,gameCallsSent=0,inputsSent=0},ToolboxSettings.JsonOptions));
}
}