XFEstudio/XFEEDToolBox
Fix persistent throttle reset before native auto docking
Connect production local navigation to the pinned C# docking extension, clear the original retained Thrust accumulator, verify real zero output and invoke the eligible original docking activity. Preserve expiring intents, identity checks, original receipts and the two-second takeover bound. Add owned lowest-priority input-context lifetime handling, fixed empty-ship .75 reproduction and safe typed token release. Bundle the extension through the existing build pipeline. Validate two real .75-to-zero/AI/docked/service rounds at Homer, including the production configuration; 48 native fixture calls, 20 docking regressions and a clean WinUI Release build. Polyakov continuous-route verification remains separate and unclaimed.
4056c6e
代码差异
@@ -31,3 +31,5 @@ XFEEDToolBox/PluginPackage/interaction.json
.env
.env.*
!.env.example
XFEEDToolBox/PluginPackage/DockingControl/
@@ -12,7 +12,12 @@ public sealed class NativeTradeNavigation(int processId,string gameHash,Func<Pla
private readonly SemaphoreSlim gate=new(1,1);
public event Action<string>? Status;
private NativeGameInputChannel Inputs()=>new(processId,gameHash,pluginDirectory);
private NativeSystemFlightControlDriver Local()=>new(processId,gameHash,player,Inputs(),ensureProtection,useNativeSupercruiseAssist:false);
private async Task<NativeSystemFlightControlDriver> LocalAsync(CancellationToken token)
{
var driver=new NativeSystemFlightControlDriver(processId,gameHash,player,Inputs(),ensureProtection,useNativeSupercruiseAssist:false);
await driver.ConfigureOriginalDockingControlAsync(Path.Combine(pluginDirectory,"DockingControl"),token);
return driver;
}
private async Task<(CurrentSystemSnapshot Catalogue,SystemLocation Location)> ResolveAsync(Station destination,CancellationToken token)
{
using var source=new NativeCurrentSystemSource(processId,gameHash,player);
@@ -39,7 +44,7 @@ public sealed class NativeTradeNavigation(int processId,string gameHash,Func<Pla
var catalogue=await NavigationRecovery.ReadWithRecoveryAsync(source.ReadLocationsAsync,null,token);
var alternate=catalogue.Locations.FirstOrDefault(value=>value.Category==SystemLocationCategory.Station&&value.MarketId is >0&&value.MarketId!=initial.MarketId)
??throw new InvalidOperationException("当前目录尚未提供可确认的离场目标。");
var departure=Local();
var departure=await LocalAsync(token);
try
{
Status?.Invoke("正在自动离场");await departure.SelectAsync(Target(catalogue.SystemAddress,alternate),token);await departure.PrepareAsync(token);
@@ -65,7 +70,7 @@ public sealed class NativeTradeNavigation(int processId,string gameHash,Func<Pla
await beforeInput(token);await ensureProtection(token);
var resolved=await ResolveAsync(destination,token);
var target=Target(resolved.Catalogue.SystemAddress,resolved.Location);
var local=Local();var arrival=new SystemNavigationRunner(player);string? lastIssue=null;
var local=await LocalAsync(token);var arrival=new SystemNavigationRunner(player);string? lastIssue=null;
arrival.Progress+=value=>{lastIssue=value.Message;Status?.Invoke(value.Message);};
await arrival.RunAsync(target,local,token);
if(arrival.State!=NavigationRunState.Completed||!player().Docked||player().MarketId!=destination.MarketId)
@@ -9,7 +9,7 @@ namespace XFEEDToolBox.Core.Navigation;
public sealed class NavigationPipeClient(int processId,string service="Navigation")
{
public int ProcessId { get; }=processId>0?processId:throw new ArgumentOutOfRangeException(nameof(processId));
private readonly string serviceName=service is "Navigation" or "Interaction" or "SessionRecovery0514" or "SessionRecovery0514c" or "SessionRecovery0514d" or "SessionRecovery0514e" or "SessionRecovery0514f" or "SessionRecovery0514g" or "CruiseObservation0514a" or "CruiseObservation0514b" or "CruiseObservation0514c" or "CruiseControl0514a" or "CruiseControl0514b" or "CruiseControl0514c"?service:throw new ArgumentException("Unknown game service.",nameof(service));
private readonly string serviceName=service is "Navigation" or "Interaction" or "SessionRecovery0514" or "SessionRecovery0514c" or "SessionRecovery0514d" or "SessionRecovery0514e" or "SessionRecovery0514f" or "SessionRecovery0514g" or "SessionRecovery0514h" or "SessionRecovery0514i" or "SessionRecovery0514j" or "SessionRecovery0514k" or "CruiseObservation0514a" or "CruiseObservation0514b" or "CruiseObservation0514c" or "CruiseControl0514a" or "CruiseControl0514b" or "CruiseControl0514c"?service:throw new ArgumentException("Unknown game service.",nameof(service));
private readonly SemaphoreSlim gate=new(1,1);
public async Task<JsonElement> CallAsync(object request,CancellationToken token=default)
{
@@ -1,4 +1,5 @@
using System.Text.Json;
using XFEEDToolBox.Core.Plugins;
namespace XFEEDToolBox.Core.Navigation;
public sealed partial class NativeSystemFlightControlDriver
@@ -7,14 +8,30 @@ public sealed partial class NativeSystemFlightControlDriver
private string? dockingResetRequestId;
private DateTimeOffset dockingResetDeadline;
private bool requestOriginalDockingStart;
private bool emptyDockingDiagnostic=true;
public JsonElement? LastOriginalDockingResetReceipt{get;private set;}
/// <summary>Explicit empty-ship diagnostic only; normal production retains its existing driver.</summary>
/// <summary>Explicit empty-ship validation retains its narrower ship/cargo limits.</summary>
public Task EnableOriginalDockingResetTestAsync(CancellationToken token=default)=>EnableOriginalDockingResetTestAsync(false,token);
public async Task EnableOriginalDockingResetTestAsync(bool startDockingAfterZero,CancellationToken token=default)
=>await ConfigureOriginalDockingAsync(startDockingAfterZero,true,token);
public async Task ConfigureOriginalDockingControlAsync(string packageDirectory,CancellationToken token=default)
{
var native=new NavigationPipeClient(processId,OriginalDockingService);
try{await native.CallAsync(new{op="describe"},token);}
catch(Exception error)when(!token.IsCancellationRequested&&error is IOException or OperationCanceledException)
{
var main=await pipe.CallAsync(new{op="describe"},token);
if(main.GetProperty("processId").GetInt32()!=processId||main.GetProperty("gameSha256").GetString()!=gameHash)
throw new InvalidDataException("Original docking package belongs to another game.");
await RuntimeInteractionModule.LoadAsync(processId,gameHash,main.GetProperty("owningThread").GetInt32(),packageDirectory,token);
}
await ConfigureOriginalDockingAsync(true,false,token);
}
private async Task ConfigureOriginalDockingAsync(bool startDockingAfterZero,bool diagnostic,CancellationToken token)
{
if(dockingResetRequestId is not null)throw new InvalidOperationException("An original docking test request is already immutable.");
const string service="SessionRecovery0514g";
const string service=OriginalDockingService;
var candidate=new NavigationPipeClient(processId,service);var hello=await candidate.CallAsync(new{op="describe"},token);
if(hello.GetProperty("processId").GetInt32()!=processId||hello.GetProperty("gameSha256").GetString()!=gameHash
||hello.GetProperty("service").GetString()!=service||!Flag(hello,"nativeDockingReset")||!Flag(hello,"enabled")
@@ -22,7 +39,7 @@ public sealed partial class NativeSystemFlightControlDriver
||DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()-hello.GetProperty("observedAtUnixMilliseconds").GetInt64() is <0 or >200
||startDockingAfterZero&&!Flag(hello,"nativeDockingStart"))
throw new NotSupportedException("Verified original docking reset test extension is not loaded.");
dockingResetPipe=candidate;requestOriginalDockingStart=startDockingAfterZero;
dockingResetPipe=candidate;requestOriginalDockingStart=startDockingAfterZero;emptyDockingDiagnostic=diagnostic;
}
private void AdoptOriginalResetReceipt(JsonElement value)
{
@@ -33,10 +50,11 @@ public sealed partial class NativeSystemFlightControlDriver
}
private async Task OriginalDockingResetAsync(SystemFlightFrame frame,DateTimeOffset deadline,CancellationToken token)
{
var service=dockingResetPipe??throw new InvalidOperationException("Original reset diagnostic is not enabled.");
var service=dockingResetPipe??throw new InvalidOperationException("Original docking reset is not configured.");
if(frame.Mode!=LocalFlightMode.Normal||frame.PlayerOverride||frame.ExternalThreat||selected is null||frame.Clearance?.Allows(selected,DateTimeOffset.UtcNow)!=true
||frame.TargetDistance>7500||player().CargoCount!=0||player().CurrentShipId!=5||player().CargoCapacity!=192)
throw new InvalidOperationException("Original reset diagnostic requires the healthy empty selected ship and current original docking clearance.");
||frame.TargetDistance>7500||player().CurrentShipId is null
||emptyDockingDiagnostic&&(player().CargoCount!=0||player().CurrentShipId!=5||player().CargoCapacity!=192))
throw new InvalidOperationException("Original reset requires the same selected main ship, current ATC and the configured validation scope.");
if(dockingResetRequestId is not null)
{
if(LastOriginalDockingResetReceipt is {} prior&&Flag(prior,"applied")
@@ -0,0 +1,34 @@
using System.Text.Json;
namespace XFEEDToolBox.Core.Navigation;
public sealed partial class NativeSystemFlightControlDriver
{
public const string OriginalDockingService="SessionRecovery0514k";
public async Task<JsonElement> ArmOriginalPersistentThrottleTrialAsync(string trialId,SystemFlightFrame frame,CancellationToken token)
{
if(frame.Mode!=LocalFlightMode.Normal||frame.PlayerOverride||frame.ExternalThreat||selected is null
||frame.Clearance?.Allows(selected,DateTimeOffset.UtcNow)!=true||frame.TargetDistance is <4500 or >7200||frame.Speed>15
||player().CurrentShipId!=5||player().CargoCount!=0||player().CargoCapacity!=192)
throw new InvalidOperationException("Fixed .75 trial requires the empty selected main ship, low speed and original ATC grant.");
await pipe.CallAsync(new{op="local.axes.release",owner,generation},token);
return await OriginalFlightTrialAsync("flight.context.arm",trialId,frame,token);
}
private async Task<JsonElement> OriginalFlightTrialAsync(string op,string trialId,SystemFlightFrame? frame,CancellationToken token,bool preserve=false)
{
if(trialId.Length!=32||trialId.Any(c=>c is not(>='0' and <='9') and not(>='a' and <='f')))throw new ArgumentException("One immutable original trial ID required.");
var native=new NavigationPipeClient(processId,OriginalDockingService);
if(frame is null)return await native.CallAsync(new{op,requestId=trialId,owner,preserveOwnedSeedForReset=preserve},token);
var deadline=frame.ObservedAt.AddMilliseconds(250);var remaining=(int)Math.Floor((deadline-DateTimeOffset.UtcNow).TotalMilliseconds);
if(remaining is <1 or >250)throw new FlightTelemetryUnavailableException("Original Flight trial frame has expired.");
return await native.CallAsync(new{op,requestId=trialId,owner,purpose="after-grant-persistent075",selectionGeneration=generation,
sequence=frame.Sequence,ttlMilliseconds=remaining,expiresAtUnixMilliseconds=deadline.ToUnixTimeMilliseconds()},token);
}
public Task<JsonElement> ReadOriginalPersistentThrottleTrialAsync(string id,CancellationToken token)
=>OriginalFlightTrialAsync("flight.context.status",id,null,token);
public Task<JsonElement> RenewOriginalPersistentThrottleTrialAsync(string id,SystemFlightFrame frame,CancellationToken token)
=>OriginalFlightTrialAsync("flight.context.renew",id,frame,token);
public Task<JsonElement> SeedOriginalPersistentThrottleTrialAsync(string id,CancellationToken token)
=>OriginalFlightTrialAsync("flight.context.seed",id,null,token);
public Task<JsonElement> ReleaseOriginalPersistentThrottleTrialAsync(string id,bool preserveForReset,CancellationToken token)
=>OriginalFlightTrialAsync("flight.context.release",id,null,token,preserveForReset);
}
@@ -0,0 +1,15 @@
# 原始油门归零与自动降落
停靠前单独清除控制器油门不能清除原始 `Thrust` 节点保留的积分。该节点下一次更新会再次发布非零油门,导致自动降落没有获得真实零油门条件。
正式导航现在明确接入独立 C# / NativeAOT 停靠扩展。取得匹配主船和目标站的原空管授权后,程序核对原输入来源及订阅,调用原注册 Reset 回调 `8F5320`,等待原 mapper 发布零值,再通过原 `EE9F30` 和原空管通知发起自动降落。实际输入、控制器、原来源及积分均需确认归零,保留两秒内 AI 接管和独立受损退出保护。
扩展使用既有 ABI,通过 `PluginPackage/DockingControl` 的独立清单部署。原游戏 PID、构建、线程、目标和请求期限均需匹配。未知回执只按原请求 ID 核对,不重新提交原操作。
固定 `.75` 复现仅供空载验收:使用原 `Bindings_Flight` 最低优先级上下文和一个原 absolute 输入帧;撤回该输入后,至少三个不同原帧必须同时观测到积分、来源和实际油门为 `.75`。上下文通过原 `77A6A0` 释放,注册表析构前处理自有 token,不直接修改 Active、引用计数、积分或 AI。原 `25E9360` 的 `-100` 是控制器 override 已释放的标记,不能当作最终实际归零证据。
先取得空管许可再复现,避免保持非零油门等待服务器。原 profile 启用后可能发布有限的 `.05` 基线;absolute `.75` 复现接受这一有效基线,保留原速度、几何、空管、期限及最终零值门槛。
Homer 已完成两次同轮 `.75` 持续、原 Reset、原 AI、实际停靠和维护,其中第二次使用正式导航相同的配置入口。测试要求三个不同原帧均为 `.75`,原输入命令已撤回,归零后实际输入、控制器、来源和积分均为零。Polyakov 连续航程尚未复测,SCO、拦截和后续跑商不属于本次通过的验收项目。
构建使用 `scripts/Build-DockingControl.ps1`;完整插件构建会包含该步骤。独立交付快照的 WinUI Release 构建为 0 警告、0 错误,相关 20 项 Docking 回归通过。验收工具命令为 `accept-flight-context-admission` 和 `accept-persistent-docking-throttle --production-reset`,不创建定时任务或模拟键鼠。
@@ -0,0 +1,175 @@
#if PLUGIN_SESSION_RECOVERY
using System.Text;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
// The returned token is an original intrusive-ref object, NOT a binding
// wrapper. Its release ABI is 77A6A0(slot), never 204010(bindingSlot).
internal sealed class RecoveryFlightContextBinding
{
internal RecoveryResetBinding ship=null!;
internal nuint entry,active;
internal bool reproduction;
}
internal sealed record RecoveryFlightContextLayers(nuint entry,nuint data,ulong count,nuint vm,byte active,
ulong[] occupancy,bool ownMember,ulong foreignCount)
{
internal bool Empty=>occupancy.All(value=>value==0);
internal bool OnlyOwn=>ownMember&&occupancy.Length>0&&occupancy[0]==1&&foreignCount==0;
}
private static bool FlightContextHeap(nuint value)=>value>=0x10000&&value<0x7fffffff0000&&(value&7)==0&&!InImage(value);
internal static bool FlightContextNoSeedThrottle(float[] values)=>values.Length==4
&&values.Take(3).All(value=>float.IsFinite(value)&&Math.Abs(value)<=.001f)
// Original 25E9360 clears the optional controller override to -100.
// This is kept as a raw released marker, never reported as throttle 0.
&&float.IsFinite(values[3])&&(Math.Abs(values[3])<=.001f||values[3]==-100f);
private static bool FlightContextName(nuint value)
{
if(!FlightContextHeap(value)||Read<ulong>(value)!=15)return false;
var name=Platform.ReadBytes(value+0x14,15);
return Encoding.ASCII.GetString(name).Equals("Bindings_Flight",StringComparison.OrdinalIgnoreCase);
}
private static nuint FlightContextEntry(nuint registry,nuint expected=0)
{
if(!FlightContextHeap(registry)||Pointer(registry)!=image+0x512FD90||Pointer(Pointer(registry)+0x20)!=image+0x8FA080
||Pointer(Pointer(registry)+0x10)!=image+0x8ED50C)throw new InvalidOperationException("Original Flight registry ABI differs");
var buckets=Pointer(registry+0x78);var count=Read<ulong>(registry+0x80);
if(!FlightContextHeap(buckets)||count is 0 or >4096)throw new InvalidOperationException("Original Flight registry map bound differs");
var seen=new HashSet<nuint>();nuint match=0;
for(nuint i=0;i<(nuint)count;i++)
{
var head=buckets+i*8;var item=Pointer(head);
while(item<buckets||item>=buckets+(nuint)count*8)
{
if(!FlightContextHeap(item)||!seen.Add(item)||seen.Count>4096)throw new InvalidOperationException("Original Flight registry chain differs");
if(FlightContextName(Pointer(item+8))){if(match!=0)throw new InvalidOperationException("Original Flight profile is ambiguous");match=item;}
item=Pointer(item);
}
}
if(match==0||expected!=0&&match!=expected||Pointer(registry+0x78)!=buckets||Read<ulong>(registry+0x80)!=count)
throw new InvalidOperationException("Original Flight registry changed");
return match;
}
internal static RecoveryFlightContextBinding BindRecoveryFlightContext(ulong entity,ulong world,JsonElement target,double hull,double? shield,nuint node,bool reproduction)
{
var ship=BindRecoveryReset(entity,world,target,hull,shield,node);
var active=Pointer(node+0x90);
if(!FixtureBindingName(active,"Bindings_Flight_Active")||Pointer(active+0x10)!=ship.registry+0xB8)
throw new InvalidOperationException("Original current-source Flight Active binding differs");
return new(){ship=ship,entry=FlightContextEntry(ship.registry),active=active,reproduction=reproduction};
}
internal static RecoveryFlightContextLayers ReadRecoveryFlightContextLayers(RecoveryFlightContextBinding proof,nuint token)
{
var ship=proof.ship;var registry=ship.registry;var entry=FlightContextEntry(registry,proof.entry);
var header=Platform.ReadBytes(entry+0x10,0x30);var count=Read<ulong>(entry+0x28);var data=Pointer(entry+0x30);var vm=Pointer(entry+0x38);
if(count>256||count>0&&!FlightContextHeap(data)||vm!=0&&!FlightContextHeap(vm))throw new InvalidOperationException("Original Flight layer array exceeds its bound");
var raw=count==0?[]:Platform.ReadBytes(data,checked((int)count*32));var occupancy=new ulong[count];var member=false;ulong foreign=0;
for(nuint i=0;i<(nuint)count;i++)
{
var set=data+i*32;var used=Read<ulong>(set+0x18);occupancy[i]=used;
if(used>4096)throw new InvalidOperationException("Original Flight layer occupancy exceeds its bound");
if(used==0)continue;
// 9091E0 inserts the exact raw token into this original hash set.
var buckets=Pointer(set);var capacity=Read<ulong>(set+8);
if(!FlightContextHeap(buckets)||capacity is 0 or >8192)throw new InvalidOperationException("Original Flight token set bound differs");
var seen=new HashSet<nuint>();ulong matches=0;
for(nuint b=0;b<(nuint)capacity;b++)
{
var head=buckets+b*8;var item=Pointer(head);
while(item!=head)
{
if(!FlightContextHeap(item)||!seen.Add(item)||seen.Count>4096)throw new InvalidOperationException("Original Flight token set chain differs");
var value=Pointer(item+8);if(token!=0&&i==0&&value==token)matches++;else foreign++;
item=Pointer(item);
}
}
if((ulong)seen.Count!=used||matches>1||Pointer(set)!=buckets||Read<ulong>(set+8)!=capacity||Read<ulong>(set+0x18)!=used)
throw new InvalidOperationException("Original Flight token membership changed");
member|=matches==1;
}
var active=Read<byte>(proof.active+0x24);
if(Pointer(proof.ship.node+0x90)!=proof.active||!FixtureBindingName(proof.active,"Bindings_Flight_Active")||Pointer(proof.active+0x10)!=registry+0xB8
||!header.AsSpan().SequenceEqual(Platform.ReadBytes(entry+0x10,header.Length))
||raw.Length!=0&&!raw.AsSpan().SequenceEqual(Platform.ReadBytes(data,raw.Length))||active!=Read<byte>(proof.active+0x24))
throw new InvalidOperationException("Original Flight layer metadata changed during verification");
return new(entry,data,count,vm,active,occupancy,member,foreign);
}
internal static void CheckRecoveryFlightContextFrame(RecoveryFlightContextBinding proof,bool initial,bool seed=false)
{
var baseline=proof.ship.baseline;var current=RecoveryPlayer(baseline.entity);
if(current.life!=baseline.life||current.fsd!=baseline.fsd||current.physics!=baseline.physics||current.inputRoot!=baseline.inputRoot
||!Sample(current.physics,out var frame)||frame.entity!=baseline.entity||frame.life!=baseline.life||frame.referenceWorld!=baseline.referenceWorld
||!frame.healthValid||frame.hullFraction<.95||HealthDamageEmergency(baseline,frame)||frame.rawFsdState is not(1 or 2)||!BoundFsd(frame,frame.fsd))
throw new InvalidOperationException("Flight scope current main ship, normal space or health changed");
var ship=proof.ship;var controller=Pointer(frame.inputRoot+0x2F8);var inventory=ReadCommerceShipInventory(frame);
if(!inventory.verified||inventory.count!=0||inventory.capacity!=192||LocalDockingComputer(frame)!=ship.computer||Pointer(ship.computer+0x240)!=controller
||Pointer(frame.fsd+0x6F8)!=ship.registry||Pointer(ship.computer+0x230)!=ship.registry||Pointer(ship.node)!=image+0x512E330
||Pointer(ship.node+0x40)!=ship.registry||Pointer(ship.node+0x78)!=ship.source||Pointer(ship.computer+0x260)!=ship.source||!RecoverySubscriber(ship))
throw new InvalidOperationException("Flight scope original empty main ship input ownership changed");
if(!SampleLocalFlight(frame,out var local,out var stage))throw new InvalidOperationException("Flight scope original local telemetry unavailable: "+stage);
var target=ship.target;
if(!local.selected||local.target.key!=target.GetProperty("key").GetInt32()||local.target.body.ToString()!=target.GetProperty("bodyAddress").GetString()
||local.target.kind!=target.GetProperty("kind").GetUInt32()||local.target.market!=target.GetProperty("marketId").GetUInt32()
||LocalIdentityName(local.target)!=target.GetProperty("name").GetString()||local.target.market==0||local.inSupercruise
||local.nativeDockingActive||local.nativeLaunchActive||local.nativeLaunchRequested||local.cruiseAssistEnabled||local.cruiseAssistEngaged
||!double.IsFinite(local.distance)||local.distance>7500||!double.IsFinite(local.speed)||local.speed>(initial||seed?15:100))
throw new InvalidOperationException("Flight scope original target, duty, SCA or bounded geometry changed");
if(proof.reproduction)
{
var outward=Subtract(local.ship.position,local.destination.position);var length=Math.Sqrt(Dot(outward,outward));
if(local.distance<4500||local.distance>7200||length<=0||Dot(outward,local.ship.forward)/length<.98||!local.dockingVerified||!local.dockingGranted)
throw new InvalidOperationException("Flight reproduction requires original ATC and must face away from its original station");
}
else if(local.dockingState!=5||local.speed>.1)throw new InvalidOperationException("No-seed Flight admission probe must remain actually docked");
var values=new[]{Read<float>(ship.node+0xD0),Read<float>(ship.source+0x18),Read<float>(frame.inputRoot+0x70),Read<float>(controller+0x680)};
if(!proof.reproduction)
{if(!FlightContextNoSeedThrottle(values))throw new InvalidOperationException("No-seed Flight scope requires zero actual/source/accumulator and zero or originally released override");}
else if(values.Take(3).Any(value=>!float.IsFinite(value)||Math.Abs(value)>1.001f)
||!float.IsFinite(values[3])||Math.Abs(values[3])>1.001f&&values[3]!=-100f
||initial&&values.Take(3).Any(value=>Math.Abs(value)>.001f))throw new InvalidOperationException("Flight scope original throttle is not eligible");
}
internal static bool RecoveryFlightContextTokenValid(RecoveryFlightContextBinding proof,nuint token)
=>FlightContextHeap(token)&&Pointer(token)==image+0x512CF40&&Read<int>(token+8)>0&&Read<int>(token+8)<=32
&&Pointer(token+0x10)==proof.ship.registry-0x58&&Pointer(token+0x18)==proof.entry+0x10&&Read<ulong>(token+0x20)==0;
internal static float[] ReadRecoveryFlightContextThrottle(RecoveryFlightContextBinding proof)
{
var ship=proof.ship;var controller=Pointer(ship.baseline.inputRoot+0x2F8);
if(Pointer(ship.node+0x78)!=ship.source||Pointer(ship.computer+0x240)!=controller||LocalPlayerIdentity.ReadLife(ship.baseline.inputRoot,image,imageEnd)!=ship.baseline.life)
throw new InvalidOperationException("Original Flight scope throttle identity changed");
var values=new[]{Read<float>(ship.node+0xD0),Read<float>(ship.source+0x18),Read<float>(ship.baseline.inputRoot+0x70),Read<float>(controller+0x680)};
if(values.Any(value=>!float.IsFinite(value)))throw new InvalidOperationException("Original Flight scope throttle sample is non-finite");return values;
}
internal static void CreateRecoveryFlightContext(RecoveryFlightContextBinding proof,ref nuint owned)
{
if(owned!=0)throw new InvalidOperationException("Original Flight output slot is already owned");
CheckRecoveryFlightContextFrame(proof,true);var layers=ReadRecoveryFlightContextLayers(proof,0);
if(!layers.Empty||layers.active!=0||layers.vm!=0)throw new InvalidOperationException("Every original Flight layer must be empty before the lowest scope is created");
if(!Platform.ReadBytes(image+0x8FA080,22).AsSpan().SequenceEqual(new byte[]{0x48,0x89,0x5C,0x24,0x10,0x48,0x89,0x6C,0x24,0x18,0x48,0x89,0x74,0x24,0x20,0x57,0x41,0x56,0x41,0x57,0x48,0x83}))
throw new InvalidOperationException("Original Flight context constructor signature differs");
var name=Encoding.ASCII.GetBytes("Bindings_Flight\0");
fixed(byte* text=name)
fixed(nuint* slot=&owned)
{
// Win64: RCX registry, RDX address of SDK-owned slot, R8 char*,
// R9 priority=0. RAX is that same output-slot address.
var returned=Call(image+0x8FA080,(ulong)proof.ship.registry,(ulong)(nuint)slot,(ulong)(nuint)text,0);
if(returned!=(ulong)(nuint)slot)throw new InvalidOperationException("Original Flight constructor returned a different output slot");
}
}
internal static void ReleaseRecoveryFlightContext(RecoveryFlightContextBinding proof,ref nuint owned,Action dispatching)
{
if(owned==0)return;
if(!RecoveryFlightContextTokenValid(proof,owned)||!ReadRecoveryFlightContextLayers(proof,owned).ownMember)
throw new InvalidOperationException("Original Flight token lifetime or exact set membership differs; no stale destructor will be called");
if(!Platform.ReadBytes(image+0x77A6A0,46).AsSpan().SequenceEqual(new byte[]{0x40,0x53,0x48,0x83,0xEC,0x20,0x48,0x8B,0xD9,0x48,0x8B,0x09,0x48,0x85,0xC9,0x74,0x17,0x83,0x41,0x08,0xFF,0x75,0x0A,0x48,0x8B,0x01,0xBA,0x01,0,0,0,0xFF,0x10,0x48,0xC7,0x03,0,0,0,0,0x48,0x83,0xC4,0x20,0x5B,0xC3}))
throw new InvalidOperationException("Original typed Flight release signature differs");
fixed(nuint* slot=&owned)
{
dispatching();
Call(image+0x77A6A0,(ulong)(nuint)slot);
}
if(owned!=0)throw new InvalidOperationException("Original typed Flight release did not clear its owned slot");
}
}
#endif
@@ -0,0 +1,50 @@
#if PLUGIN_SESSION_RECOVERY
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
internal struct RecoveryThrottlePulse
{
internal nuint node,binding,source;
internal float before;
internal bool applied;
}
internal static RecoveryThrottlePulse BeginRecoveryThrottleTrial(RecoveryFlightContextBinding proof)
{
if(!proof.reproduction)throw new InvalidOperationException("A fixed after-grant empty trial is required");
// This is an absolute .75 input. Original profile activation can
// publish its finite .05 baseline before this mapper frame; requiring
// it to still be zero would reject a valid reproduction. Admission
// itself still starts from zero, and the bounded original frame,
// ATC, ownership and eventual four-place zero gates are unchanged.
CheckRecoveryFlightContextFrame(proof,false,seed:true);
var node=proof.ship.node;var absolute=Pointer(node+0xB0);var source=proof.ship.source;
if(Read<byte>(proof.active+0x24)!=1||!FixtureBindingName(absolute,"ForwardReverseThrust_Set")
||!BeginDockingFixturePulse(node,absolute,source,out var pulse))throw new InvalidOperationException("Original absolute command was not eligible");
return new(){node=node,binding=absolute,source=source,before=pulse.beforeAccumulator,applied=true};
}
internal static bool EndRecoveryThrottleTrial(in RecoveryThrottlePulse pulse)
{
EndDockingFixturePulse(new(){node=pulse.node,binding=pulse.binding,source=pulse.source,beforeAccumulator=pulse.before,applied=pulse.applied});
return pulse.applied&&Pointer(pulse.node+0xB0)==pulse.binding&&Pointer(pulse.node+0x78)==pulse.source&&Read<uint>(pulse.binding+0x18)==0;
}
internal static float RecoveryThrottleTrialCommand(RecoveryFlightContextBinding proof)=>Read<float>(Pointer(proof.ship.node+0xB0)+0x18);
internal static bool CleanupRecoveryThrottleTrial(RecoveryFlightContextBinding proof)
{
var ship=proof.ship;var baseline=ship.baseline;var current=RecoveryPlayer(baseline.entity);
if(current.life!=baseline.life||current.fsd!=baseline.fsd||current.physics!=baseline.physics||current.inputRoot!=baseline.inputRoot
||!Sample(current.physics,out var frame)||frame.life!=baseline.life||frame.entity!=baseline.entity||!BoundFsd(frame,frame.fsd))return false;
var controller=Pointer(frame.inputRoot+0x2F8);var value=ReadRecoveryFlightContextThrottle(proof);
if(Pointer(ship.node)!=image+0x512E330||Pointer(ship.node+0x40)!=ship.registry||Pointer(ship.node+0x78)!=ship.source
||LocalDockingComputer(frame)!=ship.computer||Pointer(ship.computer+0x240)!=controller||Pointer(ship.computer+0x260)!=ship.source
||!RecoverySubscriber(ship)||!FixtureBindingName(Pointer(ship.node+0xB0),"ForwardReverseThrust_Set")
||RecoveryThrottleTrialCommand(proof)!=0||Math.Abs(value[0]-.75f)>.001f||Math.Abs(value[1]-.75f)>.001f
||value[3]!=-100f&&Math.Abs(value[3])>.001f||Pointer(Pointer(controller)+0x158)!=image+ThrottleSetter
||!Platform.ReadBytes(image+0x8F5320,18).AsSpan().SequenceEqual(new byte[]{0xC6,0x81,0xC0,0,0,0,0,0xC7,0x81,0xB0,0,0,0,0,0,0,0,0xC3}))return false;
// Undo only this trial's unchanged, retained command. These original
// methods own the controller override and the source accumulator.
Call(image+ThrottleSetter,(ulong)controller,F32(0));
Call(image+0x8F5320,(ulong)(ship.node+0x20));return true;
}
}
#endif
@@ -0,0 +1,290 @@
#if PLUGIN_SESSION_RECOVERY
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class SessionRecoveryRuntime
{
private const nuint RegistryDestroy=0x8E9D50;
private static nuint originalRegistryDestroy;
private sealed class FlightContextWork
{
internal string id="",owner="",phase="preparing",issue="";
internal ulong generation,entity,deadline,absoluteEnd,bridgeAt,createdAt,activeAt,releasedAt,inactiveAt;
internal bool pending,createAttempted,created,activeObserved,releaseRequested,explicitRelease,releaseAttempted,releaseCalled,inactiveObserved,failed,registryDestroyed,abandoned;
internal nuint token;
internal float[]? throttle;
internal ulong throttleAt;
internal Navigation.RecoveryFlightContextBinding? proof;
internal Navigation.RecoveryFlightContextLayers? layers;
internal bool reproduction,seedRequested,seedAttempted,seedApplied,seedRestored,seedOutstanding,preserveSeed,seedCleanupCalled,seedZeroObserved;
internal uint unseededFrames;
internal ulong seedAt,lastTrialFrame,seedCleanupDeadline;
internal JsonObject[] trialSamples=[];
}
private static readonly object flightContextGate=new();
private static readonly Dictionary<string,FlightContextWork> flightContextReceipts=new(StringComparer.Ordinal);
private static FlightContextWork? flightContext;
private static bool stationaryContextQualified;
private readonly record struct FlightContextGate(ulong now,ulong deadline,ulong absoluteEnd,ulong bridgeAt,
bool inGame,bool owner,bool sameShip,bool health,bool empty,bool ownLayer,bool overridePresent,bool sceneChange);
private static bool FlightContextAllowed(in FlightContextGate value)
=>value.inGame&&value.owner&&value.sameShip&&value.health&&value.empty&&value.ownLayer&&!value.overridePresent&&!value.sceneChange
&&value.now<value.deadline&&value.deadline-value.now<=250&&value.now<value.absoluteEnd
&&value.bridgeAt<=value.now&&value.now-value.bridgeAt<=200;
private static JsonObject FlightContextDescription(FlightContextWork? selected=null)
{
lock(flightContextGate)
{
var value=selected??flightContext;var layers=value?.layers;
return Obj(("requestId",value?.id),("owner",value?.owner),("selectionGeneration",value?.generation??0),
("phase",value?.phase),("issue",value?.issue),("pending",value?.pending==true),("failed",value?.failed==true),
("created",value?.created==true),("createAttempted",value?.createAttempted==true),("activeObserved",value?.activeObserved==true),
("releaseRequested",value?.releaseRequested==true),("explicitRelease",value?.explicitRelease==true),("releaseAttempted",value?.releaseAttempted==true),("originalReleaseCalled",value?.releaseCalled==true),
("inactiveObserved",value?.inactiveObserved==true),("owned",value is not null&&value.token!=0),("token",value?.token.ToString("x")),
("registryDestroyed",value?.registryDestroyed==true),("abandonedWithoutStaleCall",value?.abandoned==true),
("entityId",value?.entity.ToString()),("registry",value?.proof?.ship.registry.ToString("x")),("profileEntry",value?.proof?.entry.ToString("x")),
("node",value?.proof?.ship.node.ToString("x")),("source",value?.proof?.ship.source.ToString("x")),
("activeFlag",layers?.active),("originalVm",layers?.vm.ToString("x")),("ownTokenMember",layers?.ownMember==true),
("foreignTokens",layers?.foreignCount??0),("layerOccupancies",layers is null?null:Arr(layers.occupancy.Select(v=>(JsonNode?)JsonValue.Create(v)).ToArray())),
("accumulator",value?.throttle?[0]),("sourceValue",value?.throttle?[1]),("actualThrottle",value?.throttle?[2]),("controllerThrottle",value?.throttle?[3]),
("throttleObservedAtUnixMilliseconds",value?.throttleAt??0),
("createdAtUnixMilliseconds",value?.createdAt??0),("activeAtUnixMilliseconds",value?.activeAt??0),
("originalReleaseAtUnixMilliseconds",value?.releasedAt??0),("inactiveAtUnixMilliseconds",value?.inactiveAt??0),
("expiresAtUnixMilliseconds",value?.deadline??0),("absoluteEndAtUnixMilliseconds",value?.absoluteEnd??0),
("stationaryContextQualified",stationaryContextQualified),("fixedContext","Bindings_Flight"),("fixedPriority",0),
("reproduction",value?.reproduction==true),("seedAttempted",value?.seedAttempted==true),("originalSeedApplied",value?.seedApplied==true),
("commandRestored",value?.seedRestored==true),("unseededFrames",value?.unseededFrames??0),("persistent075",value?.unseededFrames>=3),
("seedOutstanding",value?.seedOutstanding==true),("seedCleanupCalled",value?.seedCleanupCalled==true),("seedZeroObserved",value?.seedZeroObserved==true),
("seedAtUnixMilliseconds",value?.seedAt??0),("seedCleanupDeadline",value?.seedCleanupDeadline??0),
("trialSamples",Arr((value?.trialSamples??[]).Select(v=>(JsonNode?)v.DeepClone()).ToArray())),
("originalConstructorRva","8fa080"),("originalTypedReleaseRva","77a6a0"),("gameInputWritten",value?.seedApplied==true),
("gameActiveWritten",false),("gameReferenceCountWritten",false),("gameAccumulatorWritten",false),("osInputSent",false));
}
}
private static void CheckFlightContextBridge(JsonElement frame,JsonElement health,JsonElement local,ulong sequence,bool reproduction)
{
var now=Utc();var ship=health.GetProperty("shipHealth");var docking=local.GetProperty("docking");var sca=local.GetProperty("supercruiseAssist");
if(!ResetFresh(frame,now)||!ResetFresh(health,now)||!ResetFresh(local,now)||!frame.GetProperty("inGame").GetBoolean()
||!frame.GetProperty("localPlayerShipVerified").GetBoolean()||!health.GetProperty("localPlayerShipVerified").GetBoolean()
||!ship.GetProperty("verified").GetBoolean()||ship.GetProperty("hullFraction").GetDouble()<.95||ship.GetProperty("emergency").GetBoolean()
||frame.GetProperty("entityId").GetString()!=health.GetProperty("entityId").GetString()||frame.GetProperty("entityId").GetString()!=local.GetProperty("entityId").GetString()
||frame.GetProperty("playerOverride").GetBoolean()||frame.GetProperty("fsd").GetProperty("ownedCharging").GetBoolean()
||frame.GetProperty("rawFsdState").GetInt32() is not(1 or 2)||frame.GetProperty("sequence").GetUInt64()<sequence||frame.GetProperty("sequence").GetUInt64()-sequence>32
||!local.GetProperty("geometryVerified").GetBoolean()||!local.GetProperty("selected").GetBoolean()||local.GetProperty("inSupercruise").GetBoolean()
||local.GetProperty("distanceMetres").GetDouble()>7500||local.GetProperty("speedMetresPerSecond").GetDouble()>(reproduction?100:.1)
||(!reproduction&&local.GetProperty("rawDockingState").GetInt32()!=5)
||reproduction&&(!docking.GetProperty("clearanceGranted").GetBoolean()||local.GetProperty("rawDockingState").GetInt32() is not(3 or 4)
||local.GetProperty("rawRequestState").GetInt32()!=2||local.GetProperty("rawPad").GetInt32()==255)
||docking.GetProperty("nativeLaunchActive").GetBoolean()
||docking.GetProperty("nativeLaunchRequested").GetBoolean()||docking.GetProperty("nativeDutyActive").GetBoolean()
||sca.GetProperty("enabled").GetBoolean()||sca.GetProperty("engaged").GetBoolean())
throw new InvalidOperationException("Stationary no-seed context probe requires the fresh healthy empty docked main ship and no SCA or original duty");
}
private static JsonNode FlightContextRequest(string op,JsonElement request)
{
var id=request.Str("requestId");var owner=request.Str("owner");if(!ResetId(id)||!ResetId(owner))throw new InvalidOperationException("Explicit one-use original Flight scope identity required");
FlightContextWork wanted;
lock(flightContextGate)
{
if(flightContextReceipts.TryGetValue(id,out var known))
{
if(known.owner!=owner)throw new InvalidOperationException("Original Flight scope belongs to another owner");
if(op=="flight.context.seed")
{
if(!known.reproduction||!known.activeObserved||known.token==0||known.failed||known.releaseRequested||known.seedAttempted||known.seedRequested||Utc()>=known.deadline)
throw new InvalidOperationException("Only one active original after-grant seed is allowed");
known.seedRequested=true;return FlightContextDescription(known);
}
if(op=="flight.context.release")
{
known.releaseRequested=true;known.explicitRelease=true;known.pending=false;
known.preserveSeed=known.reproduction&&known.unseededFrames>=3&&request.TryGetProperty("preserveOwnedSeedForReset",out var preserve)&&preserve.ValueKind==JsonValueKind.True;
known.seedCleanupDeadline=Math.Min(known.absoluteEnd,Utc()+200);
}
if(op!="flight.context.renew")return FlightContextDescription(known);
if(known.failed||known.releaseRequested||known.releaseAttempted||known.token==0||Utc()>=known.deadline||Utc()>=known.absoluteEnd)
throw new InvalidOperationException("Original Flight scope is no longer renewable");
wanted=known;
}
else
{
if(op!="flight.context.arm"||flightContextReceipts.Count>=32||flightContext is {} current&&(current.token!=0||current.pending))
throw new InvalidOperationException("No new original Flight scope is available");
var purpose=request.Str("purpose");var reproduction=purpose=="after-grant-persistent075";
if(purpose!="stationary-no-seed"&&!reproduction||reproduction&&!stationaryContextQualified)
throw new InvalidOperationException("The fixed after-grant trial requires its original no-seed admission proof");
wanted=new(){id=id,owner=owner,entity=0,generation=request.U64("selectionGeneration"),absoluteEnd=Utc()+(reproduction?1800UL:3000UL),reproduction=reproduction};
flightContextReceipts.Add(id,wanted);flightContext=wanted;
}
}
try
{
var deadline=request.U64("expiresAtUnixMilliseconds");var ttl=request.Int("ttlMilliseconds");var sequence=request.U64("sequence");
if(!enabled||!ResetDeadline(deadline,Utc(),ttl)||sequence==0||wanted.generation==0)throw new InvalidOperationException("Original Flight scope deadline or selection is unavailable");
Root context;lock(gate)context=root;
if(!context.valid||context.state!="InGame"||context.next!=context.state||!Fresh(context.tick,GetTickCount64()))throw new InvalidOperationException("Stable original InGame required for Flight scope");
var frame=ResetNav(Obj(("op","read")),deadline);var health=ResetNav(Obj(("op","health.read")),deadline);var local=ResetNav(Obj(("op","local.read")),deadline);
CheckFlightContextBridge(frame,health,local,sequence,wanted.reproduction);
var selected=ResetNav(Obj(("op","local.status"),("owner",owner),("generation",wanted.generation)),deadline);
if(!selected.GetProperty("selected").GetBoolean()||!selected.GetProperty("telemetryReady").GetBoolean()||!SameResetTarget(selected.GetProperty("target"),local.GetProperty("target")))
throw new InvalidOperationException("Flight context current base owner or selection changed");
var entity=ulong.Parse(frame.GetProperty("entityId").GetString()!);var world=ulong.Parse(frame.GetProperty("referenceWorldAddress").GetString()!);
if(wanted.proof is null)
{
JsonObject[] candidates;lock(gate)candidates=mapperNodes.Values.Where(row=>row["expectedType"]?.GetValue<bool>()==true
&&Utc()>=row["observedAtUnixMilliseconds"]!.GetValue<ulong>()&&Utc()-row["observedAtUnixMilliseconds"]!.GetValue<ulong>()<=200).ToArray();
var h=health.GetProperty("shipHealth");Navigation.RecoveryFlightContextBinding? proof=null;
foreach(var candidate in candidates)
{
Navigation.RecoveryFlightContextBinding bound;
try{bound=Navigation.BindRecoveryFlightContext(entity,world,local.GetProperty("target"),h.GetProperty("hullFraction").GetDouble(),
h.GetProperty("shieldFraction").ValueKind==JsonValueKind.Number?h.GetProperty("shieldFraction").GetDouble():null,
(nuint)Convert.ToUInt64(candidate["node"]!.GetValue<string>(),16),wanted.reproduction);}catch{continue;}
if(proof!=null)throw new InvalidOperationException("Current Flight context mapper is ambiguous");proof=bound;
}
if(proof is null)throw new InvalidOperationException("Current owned Flight mapper has not been observed");wanted.proof=proof;wanted.entity=entity;
}
if(entity!=wanted.entity||world!=wanted.proof.ship.baseline.referenceWorld||!SameResetTarget(local.GetProperty("target"),wanted.proof.ship.target))
throw new InvalidOperationException("Flight context main ship, world or target changed");
lock(flightContextGate)
{
if(wanted.releaseRequested||wanted.failed||Utc()>=deadline||Utc()>=wanted.absoluteEnd)throw new InvalidOperationException("Original Flight context was cancelled or expired while checking");
wanted.deadline=deadline;wanted.bridgeAt=Utc();wanted.pending=!wanted.created;wanted.phase=wanted.created?"owned-renewed":"armed";
}
}
catch(Exception error){lock(flightContextGate){wanted.failed=true;wanted.releaseRequested=true;wanted.pending=false;wanted.issue=error.ToString();wanted.phase="bridge-rejected";}}
return FlightContextDescription(wanted);
}
private static void ReleaseFlightContext(FlightContextWork wanted,string reason,bool destroying=false)
{
if(wanted.token==0)return;
if(GetCurrentThreadId()!=expectedThread)throw new InvalidOperationException("Original Flight token may only be released on its owning thread");
if(wanted.proof is null||wanted.registryDestroyed)throw new InvalidOperationException("No live original Flight token proof remains");
wanted.pending=false;wanted.phase="checking-original-token-release";
var submitted=false;
try
{
Navigation.ReleaseRecoveryFlightContext(wanted.proof,ref wanted.token,()=>
{submitted=true;wanted.releaseAttempted=true;wanted.phase="releasing-original-token";});
wanted.releaseCalled=true;wanted.releasedAt=Utc();wanted.phase="original-token-released";
if(reason!="explicit-release")wanted.issue=reason+(wanted.issue.Length==0?"":": "+wanted.issue);
}
catch(Exception error)
{
wanted.failed=true;wanted.releaseRequested=true;wanted.issue=reason+": "+error;
if(submitted||destroying)
{
// An uncertain original call is never replayed, and a token
// cannot outlive its raw entry when the registry is destroyed.
wanted.token=0;wanted.abandoned=true;wanted.phase="release-unresolved-no-replay";
}
else
{
// Validation did not invoke native code. Keep the owned slot
// so a later stable frame can release it instead of leaking
// an enabled original input layer after a transient refusal.
wanted.phase="release-precheck-refused";
}
if(!destroying)throw;
}
}
private static void PollFlightContextBeforeOriginal()
{
FlightContextWork? wanted;lock(flightContextGate)wanted=flightContext;
if(wanted is null||wanted.proof is null||wanted.registryDestroyed)return;
Root context;lock(gate)context=root;var now=Utc();
CleanupFlightThrottleTrial(wanted);
var allowed=FlightContextAllowed(new(now,wanted.deadline,wanted.absoluteEnd,wanted.bridgeAt,
enabled&&context.valid&&context.state=="InGame",true,true,true,true,true,false,context.next!=context.state||!Fresh(context.tick,GetTickCount64())));
try
{
if(GetCurrentThreadId()!=expectedThread)throw new InvalidOperationException("Original Flight scope frame is on another thread");
if(wanted.token!=0)
{
if(!allowed||wanted.releaseRequested||wanted.failed){ReleaseFlightContext(wanted,wanted.releaseRequested?"explicit-release":"expired-or-scene-changed");return;}
Navigation.CheckRecoveryFlightContextFrame(wanted.proof,false);wanted.layers=Navigation.ReadRecoveryFlightContextLayers(wanted.proof,wanted.token);
if(!wanted.layers.OnlyOwn){wanted.failed=true;ReleaseFlightContext(wanted,"non-owned-original-input-layer");}
}
else if(wanted.pending&&!wanted.createAttempted)
{
if(!allowed){wanted.pending=false;wanted.failed=true;wanted.phase="expired-before-create";return;}
wanted.createAttempted=true;wanted.pending=false;
Navigation.CreateRecoveryFlightContext(wanted.proof,ref wanted.token);
if(wanted.token==0||!Navigation.RecoveryFlightContextTokenValid(wanted.proof,wanted.token))throw new InvalidOperationException("Original lowest-priority Flight token was not created");
wanted.created=true;wanted.createdAt=Utc();wanted.layers=Navigation.ReadRecoveryFlightContextLayers(wanted.proof,wanted.token);
if(!wanted.layers.OnlyOwn)throw new InvalidOperationException("Original Flight layer0 does not contain exactly the owned token");
wanted.phase="created-awaiting-original-active";
}
}
catch(Exception error)
{
wanted.failed=true;wanted.pending=false;wanted.issue=error.ToString();wanted.phase="original-scope-refused";
if(wanted.token!=0&&!wanted.releaseAttempted)try{ReleaseFlightContext(wanted,"native-guard-refusal");}catch{}
}
}
private static void ObserveFlightContextAfterMapper(nuint node)
{
FlightContextWork? wanted;lock(flightContextGate)wanted=flightContext;
if(wanted?.proof is null||wanted.registryDestroyed||node!=wanted.proof.ship.node||GetCurrentThreadId()!=expectedThread||!wanted.created)return;
try
{
wanted.throttle=Navigation.ReadRecoveryFlightContextThrottle(wanted.proof);wanted.throttleAt=Utc();
wanted.layers=Navigation.ReadRecoveryFlightContextLayers(wanted.proof,wanted.token);
if(wanted.token!=0)
{
if(!wanted.layers.OnlyOwn){wanted.failed=true;ReleaseFlightContext(wanted,"original-ui-or-foreign-layer");return;}
if(wanted.layers.active!=0&&wanted.layers.vm!=0)
{wanted.activeObserved=true;if(wanted.activeAt==0)wanted.activeAt=Utc();wanted.phase="original-active-observed";}
}
else if(wanted.releaseCalled&&wanted.layers.Empty&&wanted.layers.active==0&&wanted.layers.vm==0)
{
wanted.inactiveObserved=true;if(wanted.inactiveAt==0)wanted.inactiveAt=Utc();wanted.phase="original-inactive-observed";
stationaryContextQualified|=!wanted.reproduction&&wanted.activeObserved&&wanted.explicitRelease&&!wanted.failed&&!wanted.abandoned;
}
}
catch(Exception error){wanted.failed=true;wanted.releaseRequested=true;wanted.issue=error.ToString();}
}
private static bool PrepareFlightContextDeactivate()
{
lock(flightContextGate)
{
if(flightContext is not {} wanted)return true;
wanted.pending=false;wanted.releaseRequested=true;
return wanted.token==0;
}
}
[UnmanagedCallersOnly]private static void RegistryDestroyHook(nuint registryRoot)
{
// 8ED50C's adjusting destructor subtracts 58 then calls this exact
// root-only void destructor. Its original profile map is still alive
// here. Always forward once, with RCX unmodified, after our cleanup.
FlightContextWork? wanted;lock(flightContextGate)wanted=flightContext;
if(wanted?.proof is {} proof&&proof.ship.registry==registryRoot+0x58)
{
try{if(wanted.token!=0)ReleaseFlightContext(wanted,"registry-destroy-before-original",true);}
catch(Exception error){wanted.failed=true;wanted.abandoned=true;wanted.issue=error.ToString();wanted.token=0;}
finally{wanted.registryDestroyed=true;wanted.pending=false;wanted.failed=true;wanted.phase="registry-destroyed-no-late-release";}
}
try{Call(originalRegistryDestroy,(ulong)registryRoot);}catch{}
}
internal static uint FlightContextSelfTest()
{
if(started||ImageHash(ModulePath())==GameHash)return 0;uint mask=0;
var valid=new FlightContextGate(1000,1200,4000,950,true,true,true,true,true,true,false,false);
if(FlightContextAllowed(valid))mask|=1;
if(!FlightContextAllowed(valid with{deadline=1000})&&!FlightContextAllowed(valid with{deadline=1251}))mask|=2;
if(!FlightContextAllowed(valid with{bridgeAt=799})&&!FlightContextAllowed(valid with{bridgeAt=1001}))mask|=4;
if(!FlightContextAllowed(valid with{overridePresent=true})&&!FlightContextAllowed(valid with{sceneChange=true}))mask|=8;
if(!FlightContextAllowed(valid with{owner=false})&&!FlightContextAllowed(valid with{sameShip=false}))mask|=16;
if(!FlightContextAllowed(valid with{health=false})&&!FlightContextAllowed(valid with{empty=false})&&!FlightContextAllowed(valid with{ownLayer=false}))mask|=32;
var own=new Navigation.RecoveryFlightContextLayers(1,2,3,4,1,[1,0,0],true,0);
if(own.OnlyOwn&&!own.Empty&&!((own with{ownMember=false}).OnlyOwn)&&!((own with{foreignCount=1}).OnlyOwn)&&!((own with{occupancy=[2,0,0]}).OnlyOwn))mask|=64;
var empty=own with{vm=0,active=0,occupancy=[0,0,0],ownMember=false};
if(empty.Empty&&!empty.OnlyOwn&&!FlightContextAllowed(valid with{absoluteEnd=1000})&&!FlightContextAllowed(valid with{inGame=false}))mask|=128;
if(Navigation.FlightContextNoSeedThrottle([0,0,0,0])&&Navigation.FlightContextNoSeedThrottle([0,0,0,-100])
&&!Navigation.FlightContextNoSeedThrottle([0,0,.75f,-100])&&!Navigation.FlightContextNoSeedThrottle([0,.75f,0,-100])
&&!Navigation.FlightContextNoSeedThrottle([.75f,0,0,-100])&&!Navigation.FlightContextNoSeedThrottle([0,0,0,-1])
&&!Navigation.FlightContextNoSeedThrottle([0,0,0,float.NaN]))mask|=256;
return mask;
}
}
#endif
@@ -0,0 +1,89 @@
#if PLUGIN_SESSION_RECOVERY
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class SessionRecoveryRuntime
{
private static bool PersistentThrottleTrialFrame(ulong frame,ulong previous,bool restored,float command,float[] values)
=>frame>previous&&restored&&command==0&&values.Length==4&&values.Take(3).All(value=>float.IsFinite(value)&&Math.Abs(value-.75f)<=.001f)
&&float.IsFinite(values[3])&&(Math.Abs(values[3])<=1.001f||values[3]==-100);
private static Navigation.RecoveryThrottlePulse BeginFlightThrottleTrial(nuint node,float dt)
{
FlightContextWork? wanted;lock(flightContextGate)wanted=flightContext;
if(wanted?.proof is null||!wanted.reproduction||!wanted.seedRequested||wanted.seedAttempted||wanted.proof.ship.node!=node)return default;
wanted.seedAttempted=true;
try
{
if(!enabled||GetCurrentThreadId()!=expectedThread||!float.IsFinite(dt)||dt<=0||dt>.25f||wanted.token==0||wanted.releaseRequested
||wanted.failed||Utc()>=wanted.deadline||Utc()>=wanted.absoluteEnd||Utc()<wanted.bridgeAt||Utc()-wanted.bridgeAt>200
||!Navigation.ReadRecoveryFlightContextLayers(wanted.proof,wanted.token).OnlyOwn)throw new InvalidOperationException("Original seed lease or exact layer changed");
var pulse=Navigation.BeginRecoveryThrottleTrial(wanted.proof);wanted.seedApplied=pulse.applied;wanted.seedOutstanding=pulse.applied;wanted.seedAt=Utc();
wanted.phase="original-absolute-command-once";return pulse;
}
catch(Exception error){wanted.failed=true;wanted.releaseRequested=true;wanted.issue=error.ToString();wanted.phase="seed-refused-no-replay";return default;}
}
private static void EndFlightThrottleTrial(in Navigation.RecoveryThrottlePulse pulse)
{
if(!pulse.applied)return;FlightContextWork? wanted;lock(flightContextGate)wanted=flightContext;
if(wanted?.proof is null||wanted.proof.ship.node!=pulse.node)return;
wanted.seedRestored=Navigation.EndRecoveryThrottleTrial(pulse);
if(!wanted.seedRestored){wanted.failed=true;wanted.releaseRequested=true;wanted.issue="Original one-frame absolute command was not restored";}
}
private static void ObserveFlightThrottleTrial(nuint node)
{
FlightContextWork? wanted;lock(flightContextGate)wanted=flightContext;
if(wanted?.proof is null||!wanted.reproduction||!wanted.seedApplied||wanted.registryDestroyed||wanted.proof.ship.node!=node||GetCurrentThreadId()!=expectedThread)return;
var values=Navigation.ReadRecoveryFlightContextThrottle(wanted.proof);var command=Navigation.RecoveryThrottleTrialCommand(wanted.proof);
if(values.Take(3).All(value=>Math.Abs(value)<=.001f))
{wanted.seedZeroObserved=true;wanted.seedOutstanding=false;return;}
Root rootFrame;lock(gate)rootFrame=root;
if(rootFrame.sequence==wanted.lastTrialFrame||!wanted.seedRestored||command!=0)return;
var previous=wanted.lastTrialFrame;wanted.lastTrialFrame=rootFrame.sequence;
if(PersistentThrottleTrialFrame(rootFrame.sequence,previous,wanted.seedRestored,command,values))
{
wanted.unseededFrames++;
if(wanted.trialSamples.Length<5)wanted.trialSamples=[..wanted.trialSamples,Obj(("frameSequence",rootFrame.sequence),("observedAtUnixMilliseconds",Utc()),
("accumulator",values[0]),("source",values[1]),("actualThrottle",values[2]),("controllerOverride",values[3]),("absoluteCommand",command))];
if(wanted.unseededFrames>=3)wanted.phase="persistent075-original-graph";
}
else if(wanted.unseededFrames>0&&wanted.token!=0)
{wanted.failed=true;wanted.releaseRequested=true;wanted.issue="Original graph did not retain the fixed .75 trial";}
}
private static void CleanupFlightThrottleTrial(FlightContextWork wanted)
{
if(!wanted.seedOutstanding||wanted.seedCleanupCalled||wanted.proof is null||wanted.registryDestroyed)return;
Root context;lock(gate)context=root;
if(context.state!="InGame"||context.next!=context.state)return;
var due=wanted.failed||Utc()>=wanted.deadline||Utc()>=wanted.absoluteEnd
||wanted.releaseRequested&&(!wanted.preserveSeed||Utc()>=wanted.seedCleanupDeadline);
if(!due)return;
try
{
var layers=Navigation.ReadRecoveryFlightContextLayers(wanted.proof,wanted.token);
if(wanted.token!=0?!layers.OnlyOwn:!layers.Empty)
{wanted.failed=true;wanted.issue="Original UI or foreign layer owns input; owned seed cleanup will not compete";return;}
wanted.seedCleanupCalled=Navigation.CleanupRecoveryThrottleTrial(wanted.proof);
wanted.failed=true;wanted.issue=wanted.seedCleanupCalled?"Owned .75 trial expired or was cancelled; original cleanup dispatched":
"Owned .75 cleanup refused changed identity or original input; no user input was overwritten";
}
catch(Exception error){wanted.failed=true;wanted.issue="Owned seed cleanup failed: "+error;}
}
internal static uint ThrottleTrialSelfTest()
{
if(started||ImageHash(ModulePath())==GameHash)return 0;uint mask=0;float[] actual=[.75f,.75f,.75f,-100];
if(PersistentThrottleTrialFrame(2,1,true,0,actual))mask|=1;
if(!PersistentThrottleTrialFrame(1,1,true,0,actual)&&!PersistentThrottleTrialFrame(0,1,true,0,actual))mask|=2;
if(!PersistentThrottleTrialFrame(2,1,false,0,actual)&&!PersistentThrottleTrialFrame(2,1,true,.75f,actual))mask|=4;
if(!PersistentThrottleTrialFrame(2,1,true,0,[0,.75f,.75f,-100]))mask|=8;
if(!PersistentThrottleTrialFrame(2,1,true,0,[.75f,0,.75f,-100]))mask|=16;
if(!PersistentThrottleTrialFrame(2,1,true,0,[.75f,.75f,0,-100]))mask|=32;
if(!PersistentThrottleTrialFrame(2,1,true,0,[.75f,.75f,float.NaN,-100])&&!PersistentThrottleTrialFrame(2,1,true,0,[.75f,.75f,.75f,float.NaN]))mask|=64;
if(!PersistentThrottleTrialFrame(2,1,true,0,[.75f,.75f,.75f,-1.5f])&&PersistentThrottleTrialFrame(2,1,true,0,[.75f,.75f,.75f,0]))mask|=128;
return mask;
}
}
internal static class FlightThrottleTrialExports
{
[UnmanagedCallersOnly(EntryPoint="XFEFlightThrottleTrialSelfTest")]
internal static uint Test(){try{return SessionRecoveryRuntime.ThrottleTrialSelfTest();}catch{return 0;}}
}
#endif
@@ -8,7 +8,7 @@ namespace XFEEDToolBox.Plugin;
internal static unsafe partial class SessionRecoveryRuntime
{
private const nuint ContextUpdate=0x21697F0,MenuPoll=0x488AC60,MapperThunk=0x8D60D0,FlowAction=0x1D65500,PauseSelect=0x4838B50;
private const string Service="SessionRecovery0514g";
private const string Service="SessionRecovery0514k";
private static nuint image,end,originalContext,originalMenu,originalMapper;
private static uint expectedThread;
private static bool started,enabled;
@@ -115,6 +115,10 @@ internal static unsafe partial class SessionRecoveryRuntime
}
[UnmanagedCallersOnly]private static ulong ContextHook(nuint context)
{
// Release an expired scope or one about to leave InGame before the
// original context callback can begin scene teardown. The registry's
// real destructor additionally owns an independent pre-destroy hook.
try{CaptureRoot(context);PollFlightContextBeforeOriginal();}catch{}
ulong result;try{result=Call(originalContext,(ulong)context);}catch{return 0;}
try
{
@@ -147,9 +151,14 @@ internal static unsafe partial class SessionRecoveryRuntime
{
ResetWork? resetting=null;
try{resetting=BeginOriginalReset(node,dt);}catch(Exception error){FailOriginalReset(error.Message);}
Navigation.RecoveryThrottlePulse trial=default;
try{trial=BeginFlightThrottleTrial(node,dt);}catch{}
float? before=null;try{if(enabled)before=Read<float>(node+0xD0);}catch{}
ulong result;try{result=Call(originalMapper,(ulong)node,F32(dt));}catch{FailOriginalReset("Original mapper call failed");return 0;}
finally{try{EndFlightThrottleTrial(trial);}catch{}}
try{FinishOriginalReset(resetting,node);}catch(Exception error){FailOriginalReset(error.Message);}
try{ObserveFlightThrottleTrial(node);}catch{}
try{ObserveFlightContextAfterMapper(node);}catch{}
try
{
if(!enabled)return result;mapperCalls++;
@@ -178,6 +187,7 @@ internal static unsafe partial class SessionRecoveryRuntime
var actions=new JsonArray();
for(var i=0;i<m.names.Length;i++)actions.Add((JsonNode)Obj(("name",m.names[i]),("enabled",m.allowed[i]),("returnToMenu",ReturnToMenu(m.names[i])),("recoveryAction",AllowedAction(m,m.names[i]))));
return Obj(("name","XFEEDToolBox.SessionRecovery"),("schema",1),("processId",ProcessId),("gameSha256",GameHash),("service",Service),("nativeDockingReset",true),("nativeDockingStart",true),("dockingReset",ResetDescription()),
("nativeFlightContextProbe",true),("flightContext",FlightContextDescription()),
("enabled",enabled),("owningThread",expectedThread),("contextCalls",contextCalls),("menuCalls",menuCalls),("menuMatches",menuMatches),
("menuReadErrors",menuReadErrors),("lastMenuIssue",lastMenuIssue),("mapperCalls",mapperCalls),("mapperNodes",nodes),
("sequence",r.sequence),("observedAtUnixMilliseconds",r.utc),("flow",r.flow.ToString("x")),("csm",r.csm.ToString("x")),
@@ -190,18 +200,21 @@ internal static unsafe partial class SessionRecoveryRuntime
private static JsonNode Handle(JsonElement request)
{
var op=request.Str("op");
if(op is "flight.context.arm" or "flight.context.renew" or "flight.context.release" or "flight.context.status" or "flight.context.seed")return FlightContextRequest(op,request);
if(op=="docking.reset.probe")return ResetBridgeProbe();
if(op is "docking.reset.arm" or "docking.reset.status" or "docking.reset.cancel")return ResetRequest(op,request);
if(op is "describe" or "read")return Description();
if(op=="deactivate")
{
if(!PrepareFlightContextDeactivate())return Description();
lock(gate){enabled=false;work.pending=false;}
CancelOriginalReset();
HookDisable(image+MapperThunk);HookDisable(image+MenuPoll);HookDisable(image+ContextUpdate);
HookDisable(image+MapperThunk);HookDisable(image+MenuPoll);HookDisable(image+ContextUpdate);HookDisable(image+RegistryDestroy);
// Keep pinned code and trampolines alive for in-flight callbacks.
return Description();
}
if(op!="session.recovery.leave")throw new InvalidOperationException("Unsupported purpose-built session recovery operation");
if(!PrepareFlightContextDeactivate())throw new InvalidOperationException("Original Flight scope must be released before a menu action is queued");
var id=request.Str("requestId");var action=request.Str("action");var ttl=request.Int("ttlMilliseconds");var expected=request.U64("menuSequence");
if(id.Length!=32||id.Any(c=>!char.IsAsciiHexDigit(c))||action is not("ExitOptions" or "Confirmed Quit To Main Menu")||ttl is <1 or >250)throw new InvalidOperationException("Bounded original return-to-menu request required");
lock(gate)
@@ -224,12 +237,16 @@ internal static unsafe partial class SessionRecoveryRuntime
Navigation.ConfigureRecoveryReadOnlyImage();
if(!Prefix(ContextUpdate,[0x40,0x55,0x53,0x56,0x57,0x41,0x54,0x41,0x56,0x41,0x57]))return 0;
if(!Prefix(MenuPoll,[0x4C,0x8B,0xDC,0x49,0x89,0x4B,0x08,0x55,0x41,0x55])||!Prefix(MapperThunk,[0xE9,0x2B,0xD4,0x01,0x00]))return 0;
if(!Prefix(RegistryDestroy,[0x48,0x89,0x5C,0x24,0x08,0x48,0x89,0x6C,0x24,0x10,0x48,0x89,0x74,0x24,0x18,0x57]))return 0;
if(!Prefix(0x25E9360,[0xC7,0x81,0x80,0x06,0,0,0,0,0xC8,0xC2,0xC3]))return 0;
var status=HookInitialize();if(status is not(0 or 1))return 0;
if(HookCreate(image+ContextUpdate,(nuint)(delegate* unmanaged<nuint,ulong>)&ContextHook,out originalContext)!=0)return 0;
if(HookCreate(image+MenuPoll,(nuint)(delegate* unmanaged<nuint,ulong>)&MenuHook,out originalMenu)!=0){HookRemove(image+ContextUpdate);return 0;}
if(HookCreate(image+MapperThunk,(nuint)(delegate* unmanaged<nuint,float,ulong>)&MapperHook,out originalMapper)!=0){HookRemove(image+MenuPoll);HookRemove(image+ContextUpdate);return 0;}
if(!Pin((nuint)(delegate* unmanaged<nuint,ulong>)&ContextHook)||HookQueueEnable(image+ContextUpdate)!=0||HookQueueEnable(image+MenuPoll)!=0||HookQueueEnable(image+MapperThunk)!=0||HookApplyQueued()!=0)
{HookDisable(image+MapperThunk);HookDisable(image+MenuPoll);HookDisable(image+ContextUpdate);return 0;}
if(HookCreate(image+RegistryDestroy,(nuint)(delegate* unmanaged<nuint,void>)&RegistryDestroyHook,out originalRegistryDestroy)!=0)
{HookRemove(image+MapperThunk);HookRemove(image+MenuPoll);HookRemove(image+ContextUpdate);return 0;}
if(!Pin((nuint)(delegate* unmanaged<nuint,ulong>)&ContextHook)||HookQueueEnable(image+RegistryDestroy)!=0||HookQueueEnable(image+ContextUpdate)!=0||HookQueueEnable(image+MenuPoll)!=0||HookQueueEnable(image+MapperThunk)!=0||HookApplyQueued()!=0)
{HookDisable(image+MapperThunk);HookDisable(image+MenuPoll);HookDisable(image+ContextUpdate);HookDisable(image+RegistryDestroy);return 0;}
enabled=true;started=true;PipeServer.StartJson(Service,32768,Handle);return 1;
}
}
@@ -263,5 +280,6 @@ internal static class SessionRecoveryExports
[UnmanagedCallersOnly(EntryPoint="XFEDockingResetSelfTest")]internal static uint ResetSelfTest(){try{return SessionRecoveryRuntime.ResetSelfTest();}catch{return 0;}}
[UnmanagedCallersOnly(EntryPoint="XFEDockingResetTelemetrySelfTest")]internal static uint ResetTelemetrySelfTest(){try{return SessionRecoveryRuntime.ResetTelemetrySelfTest();}catch{return 0;}}
[UnmanagedCallersOnly(EntryPoint="XFEDockingStartSelfTest")]internal static uint DockingStartSelfTest(){try{return Navigation.RecoveryDockingStartSelfTest();}catch{return 0;}}
[UnmanagedCallersOnly(EntryPoint="XFEFlightContextSelfTest")]internal static uint FlightContextSelfTest(){try{return SessionRecoveryRuntime.FlightContextSelfTest();}catch{return 0;}}
}
#endif
@@ -0,0 +1,120 @@
using System.Diagnostics;
using System.Text.Json;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
internal static class FlightContextAdmissionAcceptance
{
private const string Service=NativeSystemFlightControlDriver.OriginalDockingService;
private static bool Flag(JsonElement value,string name)=>value.TryGetProperty(name,out var item)&&item.ValueKind==JsonValueKind.True;
internal static async Task RunAsync(int pid,string workRoot,string report)
{
workRoot=Path.GetFullPath(workRoot);report=Path.GetFullPath(report);
if(File.Exists(report))throw new IOException("Preserve the previous original Flight admission evidence.");
Directory.CreateDirectory(Path.GetDirectoryName(report)!);
using var exclusive=new FileStream(Path.Combine(workRoot,"docking-fixture-wait.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
using var timeout=new CancellationTokenSource(TimeSpan.FromSeconds(12));var token=timeout.Token;
using var process=Process.GetProcessById(pid);var started=process.StartTime.ToUniversalTime().Ticks;
using var guardian=await PersistentProtectionProcess.StartOrReuseAsync(pid,workRoot,token,allowStart:false);
var nav=new NavigationPipeClient(pid);var scope=new NavigationPipeClient(pid,Service);
var owner=Guid.NewGuid().ToString("N");var requestId=Guid.NewGuid().ToString("N");
var observations=new List<JsonElement>();JsonElement? admission=null,finalDock=null;
var armed=false;var releaseSent=false;var passed=false;Exception? failure=null;var phase="preflight";
async Task Save()=>await File.WriteAllTextAsync(report,JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,pid,started,phase,passed,
owner,requestId,admission,observations,finalDock,failure=failure?.ToString(),inputsSent=0,flightStarted=false,tradeOrdersSent=0,
originalContextOnly=true,protectionPid=guardian.ObserverPid},ToolboxSettings.JsonOptions));
void CheckProcess(){process.Refresh();if(process.HasExited||process.StartTime.ToUniversalTime().Ticks!=started)throw new IOException("Game process identity changed.");guardian.RequireReady(pid);}
async Task<JsonElement> Status()
{
CheckProcess();var value=await scope.CallAsync(new{op="flight.context.status",owner,requestId},token);
observations.Add(value.Clone());return value;
}
async Task Release()
{
if(!armed||releaseSent)return;CheckProcess();releaseSent=true;
using var cleanup=new CancellationTokenSource(TimeSpan.FromSeconds(2));
observations.Add((await scope.CallAsync(new{op="flight.context.release",owner,requestId},cleanup.Token)).Clone());
}
try
{
CheckProcess();var hello=await scope.CallAsync(new{op="describe"},token);
if(hello.GetProperty("processId").GetInt32()!=pid||hello.GetProperty("service").GetString()!=Service
||hello.GetProperty("gameSha256").GetString()!=NativeSystemLocationReader.GameSha256||!Flag(hello,"enabled")
||hello.GetProperty("state").GetString()!="InGame"||hello.GetProperty("nextState").GetString()!="InGame")
throw new InvalidDataException("Pinned original Flight context extension is unavailable.");
var local=await nav.CallAsync(new{op="local.read"},token);var target=local.GetProperty("target").Clone();
var market=target.GetProperty("marketId").GetUInt32();
var dock=await nav.CallAsync(new{op="commerce.docked.status",marketId=market},token);
if(!Flag(dock,"ready")||!Flag(dock,"serviceReady")||!Flag(dock,"healthy")||!Flag(dock,"fresh")||dock.GetProperty("rawDockingState").GetInt32()!=5)
throw new InvalidDataException("No-seed admission must remain actually docked and service ready.");
var selected=await nav.CallAsync(new{op="local.select",owner,key=target.GetProperty("key").GetInt32(),bodyAddress=target.GetProperty("bodyAddress").GetString(),
kind=target.GetProperty("kind").GetUInt32(),marketId=market,name=target.GetProperty("name").GetString()},token);
var generation=selected.GetProperty("generation").GetUInt64();var until=DateTimeOffset.UtcNow.AddSeconds(2);
while(!Flag(selected,"selected"))
{
if(!Flag(selected,"pending")||DateTimeOffset.UtcNow>=until)throw new InvalidDataException("Original station selection was not confirmed.");
await Task.Delay(10,token);selected=await nav.CallAsync(new{op="local.status",owner,generation},token);
}
await Save();
var frame=await nav.CallAsync(new{op="read"},token);var deadline=DateTimeOffset.UtcNow.AddMilliseconds(250);
phase="original-context-arm";armed=true;
admission=await scope.CallAsync(new{op="flight.context.arm",owner,requestId,purpose="stationary-no-seed",selectionGeneration=generation,
sequence=frame.GetProperty("sequence").GetUInt64(),ttlMilliseconds=250,expiresAtUnixMilliseconds=deadline.ToUnixTimeMilliseconds()},token);
observations.Add(admission.Value.Clone());until=DateTimeOffset.UtcNow.AddSeconds(2);
while(true)
{
var state=await Status();
if(Flag(state,"failed"))throw new InvalidDataException("Original Flight admission refused: "+state.GetProperty("issue").GetString());
if(Flag(state,"activeObserved"))
{
if(!Flag(state,"created")||!Flag(state,"owned")||!Flag(state,"ownTokenMember")||state.GetProperty("foreignTokens").GetUInt64()!=0
||state.GetProperty("activeFlag").GetInt32()!=1||state.GetProperty("fixedPriority").GetInt32()!=0)
throw new InvalidDataException("No exact original owned Flight layer was observed.");
break;
}
if(DateTimeOffset.UtcNow>=until)throw new TimeoutException("Original Flight activation was not observed.");
if(deadline-DateTimeOffset.UtcNow<TimeSpan.FromMilliseconds(100))
{
frame=await nav.CallAsync(new{op="read"},token);deadline=DateTimeOffset.UtcNow.AddMilliseconds(250);
observations.Add((await scope.CallAsync(new{op="flight.context.renew",owner,requestId,selectionGeneration=generation,
sequence=frame.GetProperty("sequence").GetUInt64(),ttlMilliseconds=250,expiresAtUnixMilliseconds=deadline.ToUnixTimeMilliseconds()},token)).Clone());
}
await Task.Delay(10,token);
}
phase="original-context-release";await Release();until=DateTimeOffset.UtcNow.AddSeconds(2);
while(true)
{
var state=await Status();
if(Flag(state,"failed"))throw new InvalidDataException("Original Flight release failed: "+state.GetProperty("issue").GetString());
if(Flag(state,"inactiveObserved"))
{
if(Flag(state,"owned")||!Flag(state,"originalReleaseCalled")||!Flag(state,"explicitRelease")||!Flag(state,"stationaryContextQualified")
||state.GetProperty("activeFlag").GetInt32()!=0||state.GetProperty("originalVm").GetString()!="0"
||state.GetProperty("layerOccupancies").EnumerateArray().Any(v=>v.GetUInt64()!=0))
throw new InvalidDataException("Original Flight release did not restore the original inactive graph.");
foreach(var key in new[]{"accumulator","sourceValue","actualThrottle"})
if(!state.TryGetProperty(key,out var zero)||zero.ValueKind!=JsonValueKind.Number||Math.Abs(zero.GetDouble())>.001)
throw new InvalidDataException("No-seed admission changed a real throttle source.");
var controller=state.GetProperty("controllerThrottle").GetDouble();
if(!double.IsFinite(controller)||Math.Abs(controller)>.001&&controller!=-100)
throw new InvalidDataException("No-seed admission left an unexpected controller override.");
break;
}
if(DateTimeOffset.UtcNow>=until)throw new TimeoutException("Original inactive graph was not observed after release.");
await Task.Delay(10,token);
}
finalDock=await nav.CallAsync(new{op="commerce.docked.status",marketId=market},token);
passed=Flag(finalDock.Value,"ready")&&Flag(finalDock.Value,"serviceReady")&&Flag(finalDock.Value,"fresh")&&Flag(finalDock.Value,"healthy");
if(!passed)throw new InvalidDataException("No-seed probe did not finish at the original ready dock.");phase="passed-original-active-release-inactive";
}
catch(Exception error){failure=error;phase="failed-original-admission";}
finally
{
try{await Release();}catch(Exception cleanup){failure=failure is null?cleanup:new AggregateException(failure,cleanup);passed=false;}
await Save();
}
Console.WriteLine(JsonSerializer.Serialize(new{passed,phase,report,inputsSent=0,flightStarted=false}));
if(!passed)throw new InvalidOperationException("Original Flight admission did not pass; no flight or seed was attempted.",failure);
}
}