using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using static XFEEDToolBox.Plugin.JsonWire;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
private struct LocalIdentity { public int key; public uint kind, market; public ulong body; public fixed byte name[513]; }
private struct LocalSelection { public LocalIdentity target; public ulong generation, expires, entity, epoch; public nuint life, fsd, inputRoot; }
private struct LocalTargetObservation
{
public LocalIdentity target;
public ulong tick, entity, epoch, world;
public nuint life, fsd, inputRoot;
public bool valid;
}
private struct LocalFlightObservation
{
public ulong tick, utc, sequence, entity, world;
public LocalIdentity target;
public AffinePose ship, destination;
public Vector velocity, targetVelocity, angularVelocity;
public double distance, speed, throttle, normalSpeedLimit, cruiseSpeedLimit, accelerationLowerBound;
public double safeDropDistance, safeDropSpeed, safeDropAngle, cruiseApproachSeconds, cruiseApproachThrottle;
public bool safeDisengage, cruiseParametersVerified, valid, velocityValid, selected, angularVelocityValid;
public uint dockingState, requestState, pad;
public nuint stationLife;
public bool inSupercruise, cruiseAssistAvailable, cruiseAssistEnabled, cruiseAssistEngaged, cruiseAssistMatches, cruiseAssistObscured;
public bool dockingVerified, dockingGranted, dockingAssistAllowed, dockingAssistEnabled, dockingAssistEngaged;
public bool dockingComputerAvailable, nativeDockingActive, nativeLaunchActive, nativeLaunchRequested, dockingThrottleZero, dockingZeroInputSent, speedZeroInputSent;
}
private struct LocalActionRequest { public ulong generation, selection, expires, sequence, entity, epoch, world; public nuint life, inputRoot; public uint action; }
private static readonly object localLock = new();
private static LocalSelection localSelection = new() { target = new() { key = -1 } };
private static LocalTargetObservation localTargetObservation = new() { target = new() { key = -1 } };
private static LocalFlightObservation localObservation = EmptyLocalObservation();
private static string localSampleStage = "notSampled";
private static ulong localGenerationValue, localConsumedValue, localConfirmedValue, localFailedValue;
private static ulong localGeneration { get => Volatile.Read(ref localGenerationValue); set => Volatile.Write(ref localGenerationValue, value); }
private static ulong localConsumed { get => Volatile.Read(ref localConsumedValue); set => Volatile.Write(ref localConsumedValue, value); }
private static ulong localConfirmed { get => Volatile.Read(ref localConfirmedValue); set => Volatile.Write(ref localConfirmedValue, value); }
private static ulong localFailed { get => Volatile.Read(ref localFailedValue); set => Volatile.Write(ref localFailedValue, value); }
private static LocalActionRequest localAction;
private static ulong localActionGenerationValue, localActionConsumedValue, localActionAppliedValue, localActionFailedValue;
private static ulong localActionGeneration { get => Volatile.Read(ref localActionGenerationValue); set => Volatile.Write(ref localActionGenerationValue, value); }
private static ulong localActionConsumed { get => Volatile.Read(ref localActionConsumedValue); set => Volatile.Write(ref localActionConsumedValue, value); }
private static ulong localActionApplied { get => Volatile.Read(ref localActionAppliedValue); set => Volatile.Write(ref localActionAppliedValue, value); }
private static ulong localActionFailed { get => Volatile.Read(ref localActionFailedValue); set => Volatile.Write(ref localActionFailedValue, value); }
private struct DockingZeroInputReceipt { public ulong entity, tick; public nuint life, binding, inputService; public uint market; }
private static DockingZeroInputReceipt dockingZeroInputReceipt;
private static LocalFlightObservation EmptyLocalObservation() => new() { target = new() { key = -1 }, pad = 255 };
private static void ExpireLocal() { lock (localLock) { localSelection.expires = 0; localAction.expires = 0; dockingZeroUpdate.command.expires=0; } lock(normalBoostLock)if(normalBoostPulse is {} pulse)pulse.until=0; ExpireDockingFixture(); }
private static nuint LocalDocking(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F306C8));
return value != 0 && Pointer(value) == image + 0x5315D28 ? value : 0;
}
private static bool YieldForNativeDocking(in Lease control, in Snapshot frame)
{
if (!control.yieldOnDockingGrant || control.throttle != 0 || control.localMarket == 0 || control.localMarket == uint.MaxValue) return false;
var dock = LocalDocking(frame);
if (dock == 0 || Read<uint>(dock + 0x614) != 2 || Read<byte>(dock + 0x4E9) == 255 || Read<uint>(dock + 0x4C0) is not (3 or 4)) return false;
var station = Pointer(dock + 0x4E0); var computer = LocalDockingComputer(frame);
return station != 0 && Read<uint>(station + 0xC0) == 8 && Read<ulong>(station + 0xB0) == control.localMarket && computer != 0 && (byte)Call(image + 0xED6620, computer) != 0;
}
private static nuint LocalCruiseAssist(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F53170));
return value != 0 && Pointer(value) == image + 0x5164800 ? value : 0;
}
private static nuint LocalDockingComputer(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F5317C));
return value != 0 && Pointer(value) == image + 0x5165248 && Pointer(Pointer(value) + 0x30) == image + 0xED7D10 && Pointer(Pointer(value) + 0x58) == image + 0xED7E10 && Pointer(Pointer(value) + 0x88) == image + 0xEF8A60 ? value : 0;
}
private static bool ReadLocalDockingAvailability(nuint computer, nuint expectedTable, nuint expectedGetter)
{
if (computer < 0x10000 || Pointer(computer) != expectedTable || Pointer(expectedTable + 0x58) != expectedGetter) return false;
// The original docking update (0xE9A160) resolves its own module, asks
// the module power/health predicate (+0x70), and publishes +0x377 on
// this interface. Slot +0x58 reads that result. The SCA predicate at
// 0xED7E20 belongs to another class and cannot receive this interface.
return (byte)Call(expectedGetter, computer) != 0;
}
private static bool LocalDockingAvailable(nuint computer) => ReadLocalDockingAvailability(computer, image + 0x5165248, image + 0xED7E10);
private static nuint LocalCruiseArrival(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F34640));
return value != 0 && Pointer(value) == image + 0x533DBF0 ? value : 0;
}
private static nuint LocalStationUniversalPose(nuint life)
{
var pose = RegisteredComponent(Pointer(life + 0xC8) - 0x30, Read<uint>(image + 0x5F05D3C)); if (pose == 0) return 0;
var table = Pointer(pose);
if (table == image + 0x553DF60 && Pointer(table + 0x28) == image + 0xF84760 && Pointer(table + 0x60) == image + 0x3CBC040) return pose + 0xA8;
if (IsPose(pose) && Pointer(table + 0x60) == image + 0x2D9C200) return pose + 0xE8;
return 0;
}
private static nuint LocalRecord(nuint navigation, int key)
{
var count = Read<ulong>(navigation + 0x2C0); var capacity = Read<ulong>(navigation + 0x2C8); var storage = Pointer(navigation + 0x2D0);
if (storage == 0 || capacity < 8 || capacity > 16384 || (capacity & (capacity - 1)) != 0 || count > 1024 || count > capacity) return 0;
var entries = storage + (nuint)(((capacity >> 3) + 7) & ~7UL);
for (ulong i = 0; i < capacity; i++)
if (((Read<byte>(storage + (nuint)(i >> 3)) >> (int)(i & 7)) & 1) != 0)
{ var entry = entries + (nuint)i * 16; if (Read<int>(entry) == key) return Pointer(entry + 8); }
return 0;
}
private static bool ReadLocalIdentity(nuint record, int key, out LocalIdentity output)
{
output = new() { key = -1 };
if (record == 0 || Read<uint>(record + 0x188) != 1) return false;
LocalIdentity value = new() { key = key, body = Read<ulong>(record + 0x20), kind = Read<uint>(record + 0x184), market = Read<uint>(record + 0x130) };
var text = Pointer(record + 0x140); if (text == 0) return false;
var length = Read<ulong>(text); if (length == 0 || length >= 513) return false;
var used = 0;
for (ulong i = 0; i < length; i++)
{
var c = Read<byte>(text + 0x14 + (nuint)i); if (c == 0) return false;
if (c >= 32 && c != 127) value.name[used++] = c;
}
while (used != 0 && value.name[used - 1] == (byte)' ') value.name[--used] = 0;
var leading = 0; while (leading < used && value.name[leading] == (byte)' ') leading++;
if (leading != 0) { new Span<byte>(value.name + leading, used - leading).CopyTo(new Span<byte>(value.name, 513)); used -= leading; value.name[used] = 0; }
if (value.body == 0 || value.kind == 0 || used == 0) return false;
output = value; return true;
}
private static bool SameLocal(in LocalIdentity a, in LocalIdentity b)
{
if (a.key != b.key || a.body != b.body || a.kind != b.kind || a.market != b.market) return false;
fixed (byte* left = a.name) fixed (byte* right = b.name) return new ReadOnlySpan<byte>(left, 513).SequenceEqual(new ReadOnlySpan<byte>(right, 513));
}
private static bool LocalRecordMatches(nuint navigation, in LocalIdentity wanted) => ReadLocalIdentity(LocalRecord(navigation, wanted.key), wanted.key, out var actual) && SameLocal(actual, wanted);
private static LocalTargetObservation ObserveLocalTarget(in Snapshot frame)
{
var result = new LocalTargetObservation { target = new() { key = -1 } };
try
{
if (!frame.valid || GetTickCount64() - frame.tick > 200 || !BoundFsd(frame, frame.fsd)) return result;
var nav = Pointer(frame.fsd + 0x338);
if (Pointer(Pointer(nav) + 0x20) != image + 0x2EC8100 || Pointer(Pointer(nav) + 0xB8) != image + 0x2EC8D60) return result;
var key = Read<int>(nav + 0x38C);
if (!ReadLocalIdentity(LocalRecord(nav, key), key, out result.target) || (result.target.body & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL)) return result;
result.tick = frame.tick; result.entity = frame.entity; result.epoch = controllerEpoch; result.world = frame.referenceWorld;
result.life = frame.life; result.fsd = frame.fsd; result.inputRoot = frame.inputRoot;
result.valid = Read<int>(nav + 0x38C) == key && LocalRecordMatches(nav, result.target) && Read<ulong>(frame.transform + 0xE8) == frame.referenceWorld;
}
catch { result.valid = false; }
return result;
}
private static bool LocalSelectionAcknowledged(in LocalSelection wanted, in LocalTargetObservation observed, ulong confirmedGeneration, ulong now, ulong epoch, bool playerOverridden) =>
!playerOverridden && wanted.generation != 0 && confirmedGeneration == wanted.generation && observed.valid &&
observed.tick <= now && now - observed.tick <= 200 && observed.epoch == epoch && wanted.epoch == epoch &&
observed.entity == wanted.entity && observed.life == wanted.life && observed.fsd == wanted.fsd && observed.inputRoot == wanted.inputRoot &&
(observed.world & 0x007FFFFFFFFFFFFFUL) == (wanted.target.body & 0x007FFFFFFFFFFFFFUL) && SameLocal(observed.target, wanted.target);
private static bool LocalControlMatches(in Lease control, in Snapshot frame)
{
try
{
var navigation = Pointer(frame.fsd + 0x338); var record = LocalRecord(navigation, control.localKey);
return control.local && control.world == frame.referenceWorld && Read<int>(navigation + 0x38C) == control.localKey && record != 0 && Read<uint>(record + 0x188) == 1 && Read<ulong>(record + 0x20) == control.localBody && Read<uint>(record + 0x184) == control.localKind && Read<uint>(record + 0x130) == control.localMarket;
}
catch { return false; }
}
private static bool SelectLocalTargetImpl(in Snapshot frame, in LocalSelection wanted)
{
try
{
var now = GetTickCount64();
if (!Enabled() || !inGame || !frame.valid || !frame.healthValid || frame.healthEmergency || now - frame.tick > 200 || wanted.expires <= now || wanted.epoch != controllerEpoch || overridden || wanted.entity != frame.entity || wanted.life != frame.life || wanted.fsd != frame.fsd || wanted.inputRoot != frame.inputRoot || !BoundFsd(frame, frame.fsd) || !LocalSelectionFsdReady(frame.rawFsdState,fsdOwned)) return false;
var nav = Pointer(frame.fsd + 0x338);
if (Pointer(Pointer(nav) + 0x20) != image + 0x2EC8100 || Pointer(Pointer(nav) + 0x90) != image + 0x2ED6840 || Pointer(Pointer(nav) + 0xB8) != image + 0x2EC8D60 || (wanted.target.body & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL) || !LocalRecordMatches(nav, wanted.target)) return false;
if (Read<int>(nav + 0x38C) != wanted.target.key) Call(image + 0x2ED6840, nav, unchecked((ulong)wanted.target.key), 1, 0);
return Read<int>(nav + 0x38C) == wanted.target.key && LocalRecordMatches(nav, wanted.target);
}
catch { return false; }
}
private static void SelectLocalTarget(in Snapshot frame)
{
LocalSelection wanted; lock (localLock) wanted = localSelection;
if (wanted.generation == 0 || localConsumed == wanted.generation) return;
if (SelectLocalTargetImpl(frame, wanted)) localConfirmed = wanted.generation; else localFailed = wanted.generation;
localConsumed = wanted.generation;
}
private static double LocalDecodeFloat(nuint at)
{
var bits = Read<ulong>(at); return BitConverter.UInt32BitsToSingle((uint)bits ^ (uint)(bits >> 32));
}
private static bool LocalBodyPose(nuint body, nuint universal, nuint resource, out AffinePose output)
{
AffinePose value = default; var valid = BodyPose(body, universal, resource, &value); output = value; return valid;
}
private static bool LocalEncodedFlag(nuint at) { var bits = Read<ushort>(at); return (bits & 255) != (bits >> 8); }
private static bool SampleLocalFlight(in Snapshot frame, out LocalFlightObservation output, out string stage)
{
output = EmptyLocalObservation();
var next = EmptyLocalObservation();
stage = "snapshot";
try
{
if (!frame.valid || GetTickCount64() - frame.tick > 200 || !BoundFsd(frame, frame.fsd)) return false;
stage = "navigationInterface";
var nav = Pointer(frame.fsd + 0x338);
if (Pointer(Pointer(nav) + 0x20) != image + 0x2EC8100 || Pointer(Pointer(nav) + 0xB8) != image + 0x2EC8D60) return false;
stage = "selectedIdentity";
var key = Read<int>(nav + 0x38C); var record = LocalRecord(nav, key);
if (!ReadLocalIdentity(record, key, out next.target) || (next.target.body & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL)) return false;
stage = "loadedSystem";
var service = Pointer(frame.fsd + 0x6A0); var resource = LoadedSystem(service, frame.referenceWorld);
if (resource == 0 || Pointer(Pointer(service) + 0x60) != image + BodyLookup) return false;
stage = "bodyGeometry";
var bodies = Pointer(resource + 0x140); var capacity = Read<ulong>(resource + 0x148);
var shipBody = BodyMapEntry(bodies, capacity, frame.referenceWorld, 16, false); var targetBody = BodyMapEntry(bodies, capacity, next.target.body, 16, false);
if (shipBody == 0 || targetBody == 0 || !LocalBodyPose(shipBody, frame.transform + 0xE8, resource, out next.ship) || !LocalBodyPose(targetBody, record + 0x20, resource, out next.destination)) return false;
var delta = Subtract(next.destination.position, next.ship.position); next.distance = Math.Sqrt(Dot(delta, delta));
next.speed = frame.speed; next.throttle = Read<float>(frame.inputRoot + 0x70); next.angularVelocity = frame.angularVelocityLocal; next.angularVelocityValid = frame.angularVelocityValid;
stage = "distanceAndThrottle";
if (!double.IsFinite(next.distance) || next.distance < 0 || !double.IsFinite(next.throttle) || Math.Abs(next.throttle) > 1.001) return false;
stage = "dockingState";
var computer = LocalDockingComputer(frame);
if (computer != 0)
{
next.dockingComputerAvailable = LocalDockingAvailable(computer);
next.nativeDockingActive = (byte)Call(image + 0xED6620, computer) != 0;
next.nativeLaunchActive = (byte)Call(image + 0xED7D10, computer) != 0;
next.nativeLaunchRequested = Read<byte>(computer + 0x374) != 0;
var controller = Pointer(frame.inputRoot + 0x2F8);
next.dockingThrottleZero = controller != 0 && Pointer(computer + 0x240) == controller && Pointer(Pointer(controller) + 0x158) == image + ThrottleSetter
&&Read<float>(controller + 0x680)==0&&ReadOriginalDockingThrottle(computer,out var sourceThrottle)&&Math.Abs(sourceThrottle)<=.001f;
next.speedZeroInputSent = dockingZeroInputReceipt.entity == frame.entity && dockingZeroInputReceipt.life == frame.life && dockingZeroInputReceipt.inputService == Pointer(computer + 0x230) && frame.tick >= dockingZeroInputReceipt.tick && frame.tick - dockingZeroInputReceipt.tick < 30000;
next.dockingZeroInputSent = next.speedZeroInputSent && dockingZeroInputReceipt.market == next.target.market;
}
var dock = LocalDocking(frame);
if (dock != 0)
{
next.dockingState = Read<uint>(dock + 0x4C0); next.requestState = Read<uint>(dock + 0x614); next.pad = Read<byte>(dock + 0x4E9);
var station = Pointer(dock + 0x4E0); if (station != 0 && Read<uint>(station + 0xC0) == 8) next.stationLife = station;
next.dockingAssistEnabled = Read<byte>(dock + 0x5BB) != 0;
if (next.stationLife != 0 && Read<ulong>(next.stationLife + 0xB0) == next.target.market)
{
next.dockingVerified = true; next.dockingGranted = next.dockingState is 3 or 4 && next.requestState == 2 && next.pad != 255;
var stationService = RegisteredComponent(Pointer(next.stationLife + 0xC8) - 0x30, Read<uint>(image + 0x5F771D0));
if (stationService != 0)
{
var table = Pointer(stationService); var activeFunction = Pointer(table + 0x28); var allowed = Pointer(table + 0x58);
if (InImage(activeFunction) && InImage(allowed)) { next.dockingAssistAllowed = (byte)Call(allowed, stationService) != 0; next.dockingAssistEngaged = (byte)Call(activeFunction, stationService) != 0; }
}
}
}
stage = "motionLimits";
next.inSupercruise = LocalCruiseArrival(frame) != 0;
next.normalSpeedLimit = (double)Read<float>(frame.physics + 0x750) * Read<float>(frame.physics + 0x910);
var x = (double)Read<float>(frame.physics + 0x600); var y = (double)Read<float>(frame.physics + 0x604); var z = (double)Read<float>(frame.physics + 0x608);
next.accelerationLowerBound = Math.Min(x, Math.Min(y, z)) * .25;
next.cruiseSpeedLimit = next.inSupercruise && frame.speedFraction > .001 ? frame.speed / frame.speedFraction : next.normalSpeedLimit;
var arrival = LocalCruiseArrival(frame);
if (arrival != 0)
{
stage = "cruiseInterface";
var table = Pointer(arrival);
if (Pointer(table + 0x38) != image + 0x2EA4EE0 || Pointer(table + 0x118) != image + 0x2EA4EC0 || Pointer(table + 0x68) != image + 0x2EAD330) return false;
next.cruiseSpeedLimit = CallFloat(image + 0x2EA4EE0, arrival); var limits = (nuint)Call(image + 0x2EA4EC0, arrival); if (limits != arrival + 0x5A8) return false;
next.safeDropDistance = LocalDecodeFloat(limits + 0x28); next.safeDropSpeed = LocalDecodeFloat(limits + 0x48); next.safeDropAngle = LocalDecodeFloat(limits + 0x68);
var alignment = next.distance > 0 ? Math.Acos(Math.Clamp(Dot(next.ship.forward, Scale(delta, 1 / next.distance)), -1, 1)) : Math.PI;
next.safeDisengage = LocalEncodedFlag(limits + 0xE8) && LocalEncodedFlag(limits + 0x108) && LocalEncodedFlag(limits + 0x128) && double.IsFinite(next.safeDropDistance) && next.safeDropDistance > 0 && next.distance < next.safeDropDistance && double.IsFinite(next.safeDropSpeed) && next.safeDropSpeed > 0 && next.speed < next.safeDropSpeed && double.IsFinite(next.safeDropAngle) && next.safeDropAngle > 0 && next.safeDropAngle < 1.571 && alignment < next.safeDropAngle;
var helper = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F53174)); var assist = LocalCruiseAssist(frame);
if (helper != 0 && Pointer(helper) == image + 0x5164C78 && assist != 0)
{
next.cruiseApproachSeconds = Read<double>(Pointer(helper - 8) + 0x50); next.cruiseApproachThrottle = Read<float>(Pointer(assist - 8) + 0x10);
next.cruiseParametersVerified = double.IsFinite(next.cruiseApproachSeconds) && next.cruiseApproachSeconds > 0 && next.cruiseApproachSeconds <= 120 && double.IsFinite(next.cruiseApproachThrottle) && next.cruiseApproachThrottle > 0 && next.cruiseApproachThrottle <= 1;
}
}
stage = "motionLimits";
if (!double.IsFinite(next.normalSpeedLimit) || next.normalSpeedLimit <= 0 || !double.IsFinite(next.cruiseSpeedLimit) || next.cruiseSpeedLimit <= 0 || !double.IsFinite(next.accelerationLowerBound) || next.accelerationLowerBound <= 0) return false;
stage = "cruiseAssist";
var cruiseAssist = LocalCruiseAssist(frame);
if (cruiseAssist != 0)
{
next.cruiseAssistAvailable = (byte)Call(image + 0xECAF00, cruiseAssist) != 0; next.cruiseAssistEnabled = Read<byte>(cruiseAssist + 0x256) != 0; next.cruiseAssistEngaged = Read<byte>(cruiseAssist + 0x257) != 0;
next.cruiseAssistMatches = (byte)Call(image + 0xED8760, cruiseAssist, record) != 0; next.cruiseAssistObscured = (byte)Call(image + 0xED8A50, cruiseAssist, record) != 0;
}
next.tick = frame.tick; next.utc = frame.utcMilliseconds; next.sequence = frame.sequence; next.entity = frame.entity; next.world = frame.referenceWorld; next.selected = true;
next.valid = Read<int>(nav + 0x38C) == key && LocalRecordMatches(nav, next.target) && Read<ulong>(frame.transform + 0xE8) == frame.referenceWorld;
stage = next.valid ? "ready" : "identityChanged";
return next.valid;
}
catch (Exception error) { stage += ":" + error.GetType().Name; return false; }
finally { output = next; }
}
private static void PollLocalFlight(in Snapshot frame)
{
LocalFlightObservation before; lock (localLock) before = localObservation;
if (before.sequence == frame.sequence && before.tick != 0 && GetTickCount64() - before.tick < 100) return;
var selected = ObserveLocalTarget(frame);
var next = EmptyLocalObservation();
if (SampleLocalFlight(frame, out next, out var stage))
{
var seconds = ((double)next.tick - before.tick) * .001;
if (before.valid && before.entity == next.entity && before.world == next.world && SameLocal(before.target, next.target) && seconds >= .005 && seconds <= .3)
{
next.velocity = Scale(Subtract(next.ship.position, before.ship.position), 1 / seconds); next.targetVelocity = Scale(Subtract(next.destination.position, before.destination.position), 1 / seconds); next.velocityValid = Finite(next.velocity) && Finite(next.targetVelocity);
}
}
lock (localLock) { localObservation = next; localTargetObservation = selected; localSampleStage = stage; }
}
private static nuint LocalEntityFromDirectory(nuint context, nuint playerLife, ulong playerEntity, ulong wanted)
{
try
{
var count = Read<ulong>(context + 0x148); var capacity = Read<ulong>(context + 0x150); var storage = Pointer(context + 0x158);
if (storage == 0 || capacity < 8 || capacity > 65536 || (capacity & (capacity - 1)) != 0 || count > capacity) return 0;
var entries = storage + (nuint)(((capacity >> 3) + 7) & ~7UL); var own = false; nuint found = 0;
for (ulong i = 0; i < capacity; i++)
if (((Read<byte>(storage + (nuint)(i >> 3)) >> (int)(i & 7)) & 1) != 0)
{ var at = entries + (nuint)i * 16; var key = Read<ulong>(at); var life = Pointer(at + 8); if (key == playerEntity) own = life == playerLife; if (key == wanted) found = life; }
if (!own || found == 0 || Read<ulong>(context + 0x148) != count || Read<ulong>(context + 0x150) != capacity || Pointer(context + 0x158) != storage || Read<uint>(found + 0xC0) != 8 || Read<ulong>(found + 0xB0) != wanted) return 0;
return found;
}
catch { return 0; }
}
private static bool LocalDockingTargetKind(uint kind)=>XFEEDToolBox.Core.Navigation.NativeDockingTargetKinds.SupportsDocking(kind);
private static bool RequestLocalDocking(in Snapshot frame, in LocalFlightObservation observed)
{
var nav = Pointer(frame.fsd + 0x338); var dock = LocalDocking(frame); var record = LocalRecord(nav, observed.target.key);
if (dock == 0 || record == 0 || observed.inSupercruise || observed.distance > 7500 || observed.target.market == 0 || observed.target.market == uint.MaxValue || !LocalDockingTargetKind(observed.target.kind) || Pointer(Pointer(dock) + 0xF8) != image + 0x2C65C30) return false;
var context = Pointer(frame.life + 0x38); var token = (ulong)Read<uint>(record + 0x130); if (context == 0 || token == 0 || token == uint.MaxValue) return false;
var existing = LocalEntityFromDirectory(context, frame.life, frame.entity, token); if (existing == 0) return false;
nuint station = 0; Call(image + 0x62D0A0, (ulong)(nuint)(&station), (ulong)(nuint)(&existing));
try
{
if (station == 0 || Read<uint>(station + 0xC0) != 8 || Read<ulong>(station + 0xB0) != token) return false;
var stationPose = LocalStationUniversalPose(station); var resource = LoadedSystem(Pointer(frame.fsd + 0x6A0), frame.referenceWorld); if (stationPose == 0 || resource == 0) return false;
var stationWorld = Read<ulong>(stationPose); if ((stationWorld & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL)) return false;
var body = BodyMapEntry(Pointer(resource + 0x140), Read<ulong>(resource + 0x148), stationWorld, 16, false); if (body == 0 || !LocalBodyPose(body, stationPose, resource, out var pose)) return false;
var delta = Subtract(pose.position, observed.ship.position); if (Dot(delta, delta) > 7500.0 * 7500.0 || !LocalRecordMatches(nav, observed.target) || Read<int>(nav + 0x38C) != observed.target.key) return false;
EndNormalBoostPulse();
if(observed.dockingComputerAvailable)
{
var controller=Pointer(frame.inputRoot+0x2F8);
if(controller==0||Pointer(Pointer(controller)+0x158)!=image+ThrottleSetter||!SendOriginalDockingZeroInput(frame,controller,observed.target.market))return false;
}
return (byte)Call(image + 0x2C65C30, dock, (ulong)(nuint)(&station)) != 0;
}
finally { Call(image + 0x62F3A0, (ulong)(nuint)(&station)); }
}
private static bool SendOriginalDockingZeroInput(in Snapshot frame, nuint controller, uint market)
{
try
{
var computer = LocalDockingComputer(frame); var actions = computer != 0 ? Pointer(computer + 0x230) : 0;
if (computer == 0 || Pointer(computer + 0x240) != controller || actions == 0 || Pointer(actions) != image + 0x512FD90 || Pointer(Pointer(actions) + 0x40) != image + 0x8FEDC0) return false;
var scratch = stackalloc byte[47]; var temporary = AlignServiceScratch(scratch); new Span<byte>(temporary, 32).Clear();
try
{
Call(image + 0x8FEDC0, actions, (ulong)(nuint)temporary, image + 0x531A568);
var input = Pointer((nuint)temporary); if (input == 0 || Read<int>(input) <= 0 || Read<uint>(input + 0x1C) == uint.MaxValue) return false;
Write(input + 0x24, (byte)(Read<byte>(input + 0x24) | 1)); Write(input + 0x1C, unchecked(Read<uint>(input + 0x1C) + 1)); Call(image + ThrottleSetter, controller, F32(0));
dockingZeroInputReceipt = new() { entity = frame.entity, tick = frame.tick, life = frame.life, binding = input, inputService = actions, market = market }; return true;
}
finally { Call(image + 0x204010, (ulong)(nuint)temporary); }
}
catch { return false; }
}
private static bool LocalDockingZeroConfirmed(bool nativeDuty,bool inputSent,float controllerThrottle,double actualThrottle)
=>nativeDuty||inputSent&&controllerThrottle==0&&double.IsFinite(actualThrottle)&&Math.Abs(actualThrottle)<=.001;
private static bool ApplyLocalAction(in Snapshot frame, in LocalActionRequest command, in LocalSelection selection, in LocalFlightObservation observed)
{
try
{
var now = GetTickCount64();
if (GetCurrentThreadId() != owningThread || !Enabled() || !inGame || !frame.valid || !frame.healthValid || frame.hullFraction<=0 || frame.healthEmergency || overridden || command.expires <= now || command.entity != frame.entity || command.life != frame.life || command.inputRoot != frame.inputRoot || command.world != frame.referenceWorld || command.epoch != controllerEpoch || command.selection != selection.generation || localConfirmed != selection.generation || !observed.valid || !SameLocal(observed.target, selection.target) || now - observed.tick > 200 || now - frame.tick > 200 || !BoundFsd(frame, frame.fsd) || !LocalRecordMatches(Pointer(frame.fsd + 0x338), selection.target)) return false;
if (command.action == 1) return RequestLocalDocking(frame, observed);
if(command.action==13)return RequestNormalBoost(frame);
if (command.action == 12)
{
if (!observed.nativeDockingActive) return true;
if (observed.nativeLaunchActive || observed.inSupercruise) return false;
var computer = LocalDockingComputer(frame); var activity = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F44C98));
if (computer == 0 || activity == 0 || Pointer(activity) != image + 0x5165E08 || Pointer(Pointer(activity) + 0x30) != image + 0xEACAD0 || (byte)Call(image + 0xED6620, computer) == 0) return false;
Call(image + 0xEACAD0, activity); var controller = Pointer(frame.inputRoot + 0x2F8);
if (controller == 0 || Pointer(computer + 0x240) != controller || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
Call(image + ThrottleSetter, controller, F32(0)); return true;
}
if (command.action == 9)
{
var computer = LocalDockingComputer(frame); var dock = LocalDocking(frame); var activity = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F44C98));
if (computer == 0 || dock == 0 || activity == 0 || !observed.nativeLaunchActive && !observed.nativeLaunchRequested || !observed.dockingVerified || observed.inSupercruise || Pointer(activity) != image + 0x5165E08 || Pointer(Pointer(activity) + 0x30) != image + 0xEACAD0 || Pointer(Pointer(computer) + 0x80) != image + 0xEE8710) return false;
if (observed.nativeLaunchActive) Call(image + 0xEACAD0, activity);
if (Read<byte>(computer + 0x374) != 0) { nuint empty = 0; Call(image + 0xEE8710, computer, ulong.MaxValue, (ulong)(nuint)(&empty)); }
var controller = Pointer(frame.inputRoot + 0x2F8);
if (controller == 0 || Pointer(computer + 0x240) != controller || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
Call(image + ThrottleSetter, controller, F32(0)); return true;
}
if (command.action == 8)
{
var dock = LocalDocking(frame); var state = dock != 0 ? Read<uint>(dock + 0x4C0) : 0; var computer = LocalDockingComputer(frame);
if (dock == 0 || computer == 0 || !observed.dockingComputerAvailable || !LocalDockingAvailable(computer) || observed.inSupercruise || state is not (5 or 7) || observed.distance > 7500 || Pointer(Pointer(dock) + 0xF0) != image + 0x2C65CD0) return false;
var station = Pointer(dock + 0x4E0);
if (station == 0 || Read<uint>(station + 0xC0) != 8 || Read<ulong>(station + 0xB0) != observed.target.market) return false;
Call(image + 0xEF8A60, computer); return (byte)Call(image + 0x2C65CD0, dock, (ulong)(nuint)(&station)) != 0;
}
if (command.action == 7)
{
var arrival = LocalCruiseArrival(frame);
if (arrival == 0 || !observed.safeDisengage || !observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || observed.target.kind <= 3 || observed.target.kind == 39) return false;
Call(image + 0x2EAD330, arrival); return true;
}
if (command.action == 6)
{
if (!observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || frame.speed > 100000 || observed.cruiseAssistEngaged) return false;
Lease control; lock (leaseLock) control = lease;
if (control.expires <= now || !LocalControlMatches(control, frame) || control.throttle != 0) return false;
var next = new FsdLease { expires = command.expires, sequence = command.sequence, generation = NextFsdGeneration(), entity = frame.entity, fsd = frame.fsd, inputRoot = frame.inputRoot, life = frame.life, command = 4, heatCeiling = 80, local = true };
lock (leaseLock) fsdLease = next; return true;
}
if (command.action == 5)
{
if (observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || HeatHudPercent(frame) >= 90 || Read<byte>(frame.inputRoot + 0x60 + 0xA9) != 0 || Read<byte>(frame.inputRoot + 0x60 + 0xB1) != 0 || Read<byte>(frame.inputRoot + 0x60 + 0xBB) != 0) return false;
Lease control; lock (leaseLock) control = lease;
if (control.expires <= now || !LocalControlMatches(control, frame) || control.throttle < .75f) return false;
var next = new FsdLease { expires = command.expires, sequence = command.sequence, generation = NextFsdGeneration(), entity = frame.entity, fsd = frame.fsd, inputRoot = frame.inputRoot, life = frame.life, command = 1, heatCeiling = 90, supercruiseEntry = true, local = true };
lock (leaseLock) fsdLease = next; return true;
}
if (command.action == 10)
{
var dock = LocalDocking(frame); var computer = LocalDockingComputer(frame); var controller = Pointer(frame.inputRoot + 0x2F8);
if (dock == 0 || computer == 0 || !observed.dockingComputerAvailable || !LocalDockingAvailable(computer) || controller == 0 || Pointer(computer + 0x240) != controller || !observed.dockingVerified || !observed.dockingGranted || observed.inSupercruise || observed.distance > 7500 || Read<uint>(dock + 0x614) != 2 || Read<uint>(dock + 0x4C0) is not (3 or 4) || Read<byte>(dock + 0x4E9) == 255 || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
var station = Pointer(dock + 0x4E0);
if (station == 0 || Read<uint>(station + 0xC0) != 8 || Read<ulong>(station + 0xB0) != observed.target.market) return false;
var actualThrottle=Read<float>(frame.inputRoot+0x70);
if((byte)Call(image+0xED6620,computer)!=0)return true;
bool released;lock(leaseLock)released=lease.expires<=now&&fsdLease.expires<=now;
if(!released||fsdOwned)return false;
var sourceVerified=ReadOriginalDockingThrottle(computer,out var sourceThrottle);
if(!LocalDockingSourceZeroConfirmed(false,observed.dockingZeroInputSent,Read<float>(controller+0x680),actualThrottle,sourceVerified?sourceThrottle:double.NaN)
&&!SendOriginalDockingZeroInput(frame,controller,observed.target.market))return false;
if(!CommitOriginalDockingZeroAxis(frame.inputRoot,controller,actualThrottle,true,released,out _))return false;
lock(localLock)dockingZeroUpdate=new(){command=command,computer=computer,controller=controller,fsd=frame.fsd,market=observed.target.market,throttle=actualThrottle};
return true;
}
if (command.action == 11)
{
var controller = Pointer(frame.inputRoot + 0x2F8);
if (observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || controller == 0 || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
return SendOriginalDockingZeroInput(frame, controller, 0);
}
var assist = LocalCruiseAssist(frame);
if (command.action == 2)
{
if (assist == 0 || !observed.inSupercruise || !observed.cruiseAssistAvailable || (byte)Call(image + 0xECAF00, assist) == 0 || observed.cruiseAssistObscured || observed.distance < 1) return false;
if (observed.cruiseAssistEnabled && observed.cruiseAssistMatches) return true;
var direction = Scale(Subtract(observed.destination.position, observed.ship.position), 1 / observed.distance);
if (Dot(direction, observed.ship.forward) < Math.Cos(5.0 * Math.PI / 180)) return false;
var controller = Pointer(frame.inputRoot + 0x2F8);
if (controller == 0 || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter || !observed.cruiseParametersVerified) return false;
Call(image + ThrottleSetter, controller, F32((float)observed.cruiseApproachThrottle)); Call(image + 0xEEA430, assist, 1); return true;
}
if (command.action == 3)
{
if (assist == 0 || !observed.cruiseAssistMatches || !observed.cruiseAssistEnabled && !observed.cruiseAssistEngaged) return true;
Call(image + 0xEEA430, assist, 0); return true;
}
return false;
}
catch { return false; }
}
private static void PollLocalAction(in Snapshot frame)
{
PollNormalBoostPulse(frame);
LocalActionRequest command; LocalSelection selection; LocalFlightObservation observed;
lock (localLock) { command = localAction; selection = localSelection; observed = localObservation; }
if (command.generation == 0 || command.generation == localActionConsumed) return;
if (ApplyLocalAction(frame, command, selection, observed)) localActionApplied = command.generation; else localActionFailed = command.generation;
localActionConsumed = command.generation;
}
private static string LocalIdentityName(in LocalIdentity value)
{
fixed (byte* text = value.name) { var length = 0; while (length < 513 && text[length] != 0) length++; return Encoding.UTF8.GetString(new ReadOnlySpan<byte>(text, length)); }
}
private static JsonObject LocalIdentityJson(in LocalIdentity value) => Obj(("key", value.key), ("bodyAddress", value.body.ToString(CultureInfo.InvariantCulture)), ("kind", value.kind), ("marketId", value.market), ("name", LocalIdentityName(value)));
private static JsonArray LocalVector(Vector value) => Arr(value.x, value.y, value.z);
private static JsonObject LocalFlightRead(in Snapshot frame)
{
LocalFlightObservation value;DockingMapperReceipt mapper;
lock(localLock){value=localObservation;mapper=dockingInputMapperReceipt;}var now=GetTickCount64();
if (!frame.valid || !value.valid || value.entity != frame.entity || value.world != frame.referenceWorld || now - value.tick > 200) throw new InvalidOperationException("Local target telemetry unavailable or stale");
return Obj(("sequence", value.sequence), ("observedAtUnixMilliseconds", value.utc), ("entityId", value.entity.ToString(CultureInfo.InvariantCulture)), ("referenceWorldAddress", value.world.ToString(CultureInfo.InvariantCulture)),
("geometryVerified", true), ("selected", value.selected), ("target", LocalIdentityJson(value.target)), ("position", LocalVector(value.ship.position)), ("forward", LocalVector(value.ship.forward)), ("up", LocalVector(value.ship.up)), ("targetPosition", LocalVector(value.destination.position)),
("velocity", LocalVector(value.velocity)), ("targetVelocity", LocalVector(value.targetVelocity)), ("velocityVerified", value.velocityValid), ("angularVelocity", LocalVector(value.angularVelocity)), ("angularVelocityVerified", value.angularVelocityValid), ("speedZeroInputSent", value.speedZeroInputSent),
("distanceMetres", value.distance), ("speedMetresPerSecond", value.speed), ("throttle", value.throttle), ("rawDockingState", value.dockingState), ("rawRequestState", value.requestState), ("rawPad", value.pad), ("normalSpeedLimit", value.normalSpeedLimit), ("cruiseSpeedLimit", value.cruiseSpeedLimit), ("accelerationLowerBound", value.accelerationLowerBound),
("safeDisengage", value.safeDisengage), ("safeDropDistance", value.safeDropDistance), ("safeDropSpeed", value.safeDropSpeed), ("cruiseParametersVerified", value.cruiseParametersVerified), ("cruiseApproachSeconds", value.cruiseApproachSeconds), ("cruiseApproachThrottle", value.cruiseApproachThrottle),
("rawFsdState", frame.rawFsdState), ("rawFsdMode", frame.rawFsdMode), ("rawFsdStage", frame.rawFsdStage), ("inSupercruise", value.inSupercruise),
("normalBoostAvailable",TryNormalBoostBinding(frame,out _,out _)),("normalBoostInputs",normalBoostInputs),("normalBoostReleased",normalBoostReleased),
("normalBoostPressed",normalBoostPulse is not null),("normalBoostLastTick",lastNormalBoostAt),
("dockingZeroAxisCommits", dockingZeroAxisCommits), ("dockingZeroAxisRestoresSkipped", dockingZeroAxisRestoresSkipped),
("dockingZeroUpdateCalls",dockingZeroUpdateCalls),("dockingZeroUpdateMatched",dockingZeroUpdateMatched),("dockingZeroUpdateApplied",dockingZeroUpdateApplied),
("dockingInputMapperCalls",dockingInputMapperCalls),("dockingInputMapperMatched",dockingInputMapperMatched),("dockingInputMapperApplied",dockingInputMapperApplied),
("dockingInputMapper",Obj(("verified",mapper.verified),("sequence",mapper.sequence),("entityId",mapper.entity.ToString(CultureInfo.InvariantCulture)),
("observedAtUnixMilliseconds",mapper.utc),("accumulatorBefore",mapper.before),("accumulatorAfter",mapper.after),("sourceAfter",mapper.source))),
("supercruiseAssist", Obj(("available", value.cruiseAssistAvailable), ("enabled", value.cruiseAssistEnabled), ("engaged", value.cruiseAssistEngaged), ("matchesTarget", value.cruiseAssistMatches), ("obscured", value.cruiseAssistObscured))),
("docking", Obj(("verified", value.dockingVerified), ("computerAvailable", value.dockingComputerAvailable), ("clearanceGranted", value.dockingGranted), ("assistAllowed", value.dockingAssistAllowed), ("assistEnabled", value.dockingAssistEnabled), ("assistEngaged", value.dockingAssistEngaged), ("nativeDutyActive", value.nativeDockingActive), ("nativeLaunchActive", value.nativeLaunchActive), ("nativeLaunchRequested", value.nativeLaunchRequested), ("throttleZero", value.dockingThrottleZero), ("zeroThrottleInputSent", value.dockingZeroInputSent))));
}
private static JsonObject LocalFlightRequest(string op, JsonElement request, string owner, in Snapshot frame)
{
var now = GetTickCount64();
if (op == "local.suspend")
{
if (clientOwner != owner) throw new InvalidOperationException("Local control belongs to another controller");
lock (leaseLock) { if (lease.local) lease.expires = 0; if (fsdLease.local) fsdLease.expires = 0; }
lock(localLock)dockingZeroUpdate.command.expires=0;
return Obj(("suspended", true));
}
if (!Enabled() || !inGame || !frame.valid || !frame.healthValid || frame.hullFraction<=0 || frame.healthEmergency || now - frame.tick > 200) throw new InvalidOperationException("Fresh healthy player ship required for local navigation");
ulong expiry; lock (leaseLock) expiry = Math.Max(lease.expires, Math.Max(fsdLease.expires, targetLease.expires));
LocalSelection wanted; lock (localLock) wanted = localSelection;
if (expiry <= now && wanted.expires <= now && !fsdOwned && op == "local.select") clientOwner = "";
if (clientOwner.Length != 0 && clientOwner != owner) throw new InvalidOperationException("Another controller owns local navigation");
if (op == "local.select")
{
if (expiry > now || !LocalSelectionFsdReady(frame.rawFsdState,fsdOwned)) throw new InvalidOperationException("Stop flight control before selecting a local destination");
LocalIdentity identity = new() { key = request.Required("key").GetInt32(), body = ulong.Parse(request.Required("bodyAddress").GetString()!, CultureInfo.InvariantCulture), kind = request.Required("kind").GetUInt32(), market = request.Required("marketId").GetUInt32() };
var name = request.Required("name").GetString()!; var encoded = Encoding.UTF8.GetBytes(name);
if (identity.key < 0 || identity.body == 0 || identity.kind == 0 || encoded.Length == 0 || encoded.Length >= 513 || name.Contains('\0')) throw new InvalidOperationException("Invalid local target identity");
encoded.CopyTo(new Span<byte>(identity.name, 513));
if (clientOwner.Length == 0) overridden = false;
ClaimController(owner); wanted = new() { target = identity, generation = ++localGeneration, expires = now + 300, entity = frame.entity, epoch = controllerEpoch, life = frame.life, fsd = frame.fsd, inputRoot = frame.inputRoot };
lock (localLock) localSelection = wanted;
}
else if (clientOwner != owner || wanted.generation == 0 || request.Required("generation").GetUInt64() != wanted.generation) throw new InvalidOperationException("No matching local target request");
if (op == "local.action")
{
var reference = Reference(request.Required("sequence").GetUInt64(), frame, now); var utc = UtcMilliseconds(); var deadline = request.Required("expiresAtUnixMilliseconds").GetUInt64();
var action = request.Required("action").GetString() switch { "requestDocking" => 1u, "enableSupercruiseAssist" => 2u, "cancelSupercruiseAssist" => 3u, "enterSupercruise" => 5u, "leaveSupercruiseSlowly" => 6u, "safeDisengage" => 7u, "launch" => 8u, "cancelLaunch" => 9u, "zeroThrottleForDocking" => 10u, "setNormalThrottleZero" => 11u, "cancelDockingAssist" => 12u, "boostThrusters"=>13u, _ => 0u };
if (action == 0 || deadline <= utc || deadline > utc + 300 || overridden || localConfirmed != wanted.generation) throw new InvalidOperationException("Invalid or expired local action");
var next = new LocalActionRequest { generation = ++localActionGeneration, selection = wanted.generation, expires = Math.Min(now + deadline - utc, reference.tick + 300), sequence = reference.sequence, entity = frame.entity, epoch = controllerEpoch, world = frame.referenceWorld, life = frame.life, inputRoot = frame.inputRoot, action = action };
lock (localLock) localAction = next; return Obj(("queued", true), ("actionGeneration", next.generation));
}
if (op == "local.action.status")
{
var generation = request.Required("actionGeneration").GetUInt64(); LocalActionRequest command; lock (localLock) command = localAction;
if (generation != command.generation || command.selection != wanted.generation) throw new InvalidOperationException("No matching local action");
var applied = localActionApplied == generation; var failed = localActionFailed == generation;
return Obj(("applied", applied), ("failed", failed || !applied && command.expires <= now), ("pending", !applied && !failed && command.expires > now), ("actionGeneration", generation));
}
if (op == "local.axes.release") { lock (leaseLock) if (lease.local) lease.expires = 0; return Obj(("released", true)); }
if (op == "local.apply")
{
RequirePhysicalInputSources(frame,now);
var reference = Reference(request.Required("sequence").GetUInt64(), frame, now); var utc = UtcMilliseconds(); var deadline = request.Required("expiresAtUnixMilliseconds").GetUInt64();
if (deadline <= utc || deadline > utc + 300 || overridden || localConfirmed != wanted.generation) throw new InvalidOperationException("Local control frame expired or selection changed");
var values = request.Required("axes"); if (values.GetArrayLength() != 6) throw new InvalidOperationException("Invalid local control axes");
Float6 axes = default; var invalidAxes = false;
for (var index = 0; index < 6; index++) { axes[index] = values[index].GetSingle(); invalidAxes |= !float.IsFinite(axes[index]) || Math.Abs(axes[index]) > 1; }
var throttle = request.TryGetProperty("throttle", out var throttleValue) ? throttleValue.GetSingle() : -1;
if (!float.IsFinite(throttle) || throttle != -1 && (throttle < 0 || throttle > 1) || invalidAxes) throw new InvalidOperationException("Invalid local control axes");
var next = new Lease { expires = Math.Min(now + deadline - utc, reference.tick + 300), sequence = reference.sequence, inputRoot = frame.inputRoot, axes = axes, throttle = throttle, epoch = controllerEpoch, local = true, localKey = wanted.target.key, localKind = wanted.target.kind, localBody = wanted.target.body, localMarket = wanted.target.market, world = frame.referenceWorld, yieldOnDockingGrant = request.Bool("yieldOnDockingGrant") };
if (!LocalControlMatches(next, frame)) throw new InvalidOperationException("Local target changed before applying controls");
lock (leaseLock)
{
lease = next;
if (fsdLease.local && fsdLease.entity == frame.entity && fsdLease.life == frame.life && fsdLease.command == 1 && fsdLease.expires != 0 && frame.rawFsdState == 3) { fsdLease.expires = next.expires; fsdLease.sequence = reference.sequence; }
}
return Obj(("accepted", true), ("generation", wanted.generation), ("sequence", reference.sequence));
}
LocalFlightObservation observed; LocalTargetObservation selected; string stage;
lock (localLock) { observed = localObservation; selected = localTargetObservation; stage = localSampleStage; }
// The original navigation selection is observable before a remote body's
// full flight geometry is ready. Confirm that selection independently;
// local.read and all flight actions still require complete telemetry.
var confirmed = wanted.entity == frame.entity && wanted.life == frame.life && wanted.fsd == frame.fsd && wanted.inputRoot == frame.inputRoot &&
LocalSelectionAcknowledged(wanted, selected, localConfirmed, now, controllerEpoch, overridden);
var telemetryReady = observed.valid && observed.entity == frame.entity && observed.world == frame.referenceWorld && SameLocal(observed.target, wanted.target) && observed.tick <= now && now - observed.tick <= 200;
return Obj(("generation", wanted.generation), ("selected", confirmed), ("pending", !confirmed && localFailed != wanted.generation && wanted.expires > now),
("target", LocalIdentityJson(selected.target)), ("flightInputsSent", 0), ("telemetryReady", telemetryReady), ("sampleStage", stage),
("sampleLimits", Obj(("normalSpeed", double.IsFinite(observed.normalSpeedLimit) ? observed.normalSpeedLimit : 0),
("cruiseSpeed", double.IsFinite(observed.cruiseSpeedLimit) ? observed.cruiseSpeedLimit : 0),
("acceleration", double.IsFinite(observed.accelerationLowerBound) ? observed.accelerationLowerBound : 0))));
}
}
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using static XFEEDToolBox.Plugin.JsonWire;
namespace XFEEDToolBox.Plugin;
internal static unsafe partial class Navigation
{
private struct LocalIdentity { public int key; public uint kind, market; public ulong body; public fixed byte name[513]; }
private struct LocalSelection { public LocalIdentity target; public ulong generation, expires, entity, epoch; public nuint life, fsd, inputRoot; }
private struct LocalTargetObservation
{
public LocalIdentity target;
public ulong tick, entity, epoch, world;
public nuint life, fsd, inputRoot;
public bool valid;
}
private struct LocalFlightObservation
{
public ulong tick, utc, sequence, entity, world;
public LocalIdentity target;
public AffinePose ship, destination;
public Vector velocity, targetVelocity, angularVelocity;
public double distance, speed, throttle, normalSpeedLimit, cruiseSpeedLimit, accelerationLowerBound;
public double safeDropDistance, safeDropSpeed, safeDropAngle, cruiseApproachSeconds, cruiseApproachThrottle;
public bool safeDisengage, cruiseParametersVerified, valid, velocityValid, selected, angularVelocityValid;
public uint dockingState, requestState, pad;
public nuint stationLife;
public bool inSupercruise, cruiseAssistAvailable, cruiseAssistEnabled, cruiseAssistEngaged, cruiseAssistMatches, cruiseAssistObscured;
public bool dockingVerified, dockingGranted, dockingAssistAllowed, dockingAssistEnabled, dockingAssistEngaged;
public bool dockingComputerAvailable, nativeDockingActive, nativeLaunchActive, nativeLaunchRequested, dockingThrottleZero, dockingZeroInputSent, speedZeroInputSent;
}
private struct LocalActionRequest { public ulong generation, selection, expires, sequence, entity, epoch, world; public nuint life, inputRoot; public uint action; }
private static readonly object localLock = new();
private static LocalSelection localSelection = new() { target = new() { key = -1 } };
private static LocalTargetObservation localTargetObservation = new() { target = new() { key = -1 } };
private static LocalFlightObservation localObservation = EmptyLocalObservation();
private static string localSampleStage = "notSampled";
private static ulong localGenerationValue, localConsumedValue, localConfirmedValue, localFailedValue;
private static ulong localGeneration { get => Volatile.Read(ref localGenerationValue); set => Volatile.Write(ref localGenerationValue, value); }
private static ulong localConsumed { get => Volatile.Read(ref localConsumedValue); set => Volatile.Write(ref localConsumedValue, value); }
private static ulong localConfirmed { get => Volatile.Read(ref localConfirmedValue); set => Volatile.Write(ref localConfirmedValue, value); }
private static ulong localFailed { get => Volatile.Read(ref localFailedValue); set => Volatile.Write(ref localFailedValue, value); }
private static LocalActionRequest localAction;
private static ulong localActionGenerationValue, localActionConsumedValue, localActionAppliedValue, localActionFailedValue;
private static ulong localActionGeneration { get => Volatile.Read(ref localActionGenerationValue); set => Volatile.Write(ref localActionGenerationValue, value); }
private static ulong localActionConsumed { get => Volatile.Read(ref localActionConsumedValue); set => Volatile.Write(ref localActionConsumedValue, value); }
private static ulong localActionApplied { get => Volatile.Read(ref localActionAppliedValue); set => Volatile.Write(ref localActionAppliedValue, value); }
private static ulong localActionFailed { get => Volatile.Read(ref localActionFailedValue); set => Volatile.Write(ref localActionFailedValue, value); }
private struct DockingZeroInputReceipt { public ulong entity, tick; public nuint life, binding, inputService; public uint market; }
private static DockingZeroInputReceipt dockingZeroInputReceipt;
private static LocalFlightObservation EmptyLocalObservation() => new() { target = new() { key = -1 }, pad = 255 };
private static void ExpireLocal() { lock (localLock) { localSelection.expires = 0; localAction.expires = 0; dockingZeroUpdate.command.expires=0; } lock(normalBoostLock)if(normalBoostPulse is {} pulse)pulse.until=0; ExpireDockingFixture(); }
private static nuint LocalDocking(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F306C8));
return value != 0 && Pointer(value) == image + 0x5315D28 ? value : 0;
}
private static bool YieldForNativeDocking(in Lease control, in Snapshot frame)
{
if (!control.yieldOnDockingGrant || control.throttle != 0 || control.localMarket == 0 || control.localMarket == uint.MaxValue) return false;
var dock = LocalDocking(frame);
if (dock == 0 || Read<uint>(dock + 0x614) != 2 || Read<byte>(dock + 0x4E9) == 255 || Read<uint>(dock + 0x4C0) is not (3 or 4)) return false;
var station = Pointer(dock + 0x4E0); var computer = LocalDockingComputer(frame);
return station != 0 && Read<uint>(station + 0xC0) == 8 && Read<ulong>(station + 0xB0) == control.localMarket && computer != 0 && (byte)Call(image + 0xED6620, computer) != 0;
}
private static nuint LocalCruiseAssist(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F53170));
return value != 0 && Pointer(value) == image + 0x5164800 ? value : 0;
}
private static nuint LocalDockingComputer(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F5317C));
return value != 0 && Pointer(value) == image + 0x5165248 && Pointer(Pointer(value) + 0x30) == image + 0xED7D10 && Pointer(Pointer(value) + 0x58) == image + 0xED7E10 && Pointer(Pointer(value) + 0x88) == image + 0xEF8A60 ? value : 0;
}
private static bool ReadLocalDockingAvailability(nuint computer, nuint expectedTable, nuint expectedGetter)
{
if (computer < 0x10000 || Pointer(computer) != expectedTable || Pointer(expectedTable + 0x58) != expectedGetter) return false;
// The original docking update (0xE9A160) resolves its own module, asks
// the module power/health predicate (+0x70), and publishes +0x377 on
// this interface. Slot +0x58 reads that result. The SCA predicate at
// 0xED7E20 belongs to another class and cannot receive this interface.
return (byte)Call(expectedGetter, computer) != 0;
}
private static bool LocalDockingAvailable(nuint computer) => ReadLocalDockingAvailability(computer, image + 0x5165248, image + 0xED7E10);
private static nuint LocalCruiseArrival(in Snapshot frame)
{
var value = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F34640));
return value != 0 && Pointer(value) == image + 0x533DBF0 ? value : 0;
}
private static nuint LocalStationUniversalPose(nuint life)
{
var pose = RegisteredComponent(Pointer(life + 0xC8) - 0x30, Read<uint>(image + 0x5F05D3C)); if (pose == 0) return 0;
var table = Pointer(pose);
if (table == image + 0x553DF60 && Pointer(table + 0x28) == image + 0xF84760 && Pointer(table + 0x60) == image + 0x3CBC040) return pose + 0xA8;
if (IsPose(pose) && Pointer(table + 0x60) == image + 0x2D9C200) return pose + 0xE8;
return 0;
}
private static nuint LocalRecord(nuint navigation, int key)
{
var count = Read<ulong>(navigation + 0x2C0); var capacity = Read<ulong>(navigation + 0x2C8); var storage = Pointer(navigation + 0x2D0);
if (storage == 0 || capacity < 8 || capacity > 16384 || (capacity & (capacity - 1)) != 0 || count > 1024 || count > capacity) return 0;
var entries = storage + (nuint)(((capacity >> 3) + 7) & ~7UL);
for (ulong i = 0; i < capacity; i++)
if (((Read<byte>(storage + (nuint)(i >> 3)) >> (int)(i & 7)) & 1) != 0)
{ var entry = entries + (nuint)i * 16; if (Read<int>(entry) == key) return Pointer(entry + 8); }
return 0;
}
private static bool ReadLocalIdentity(nuint record, int key, out LocalIdentity output)
{
output = new() { key = -1 };
if (record == 0 || Read<uint>(record + 0x188) != 1) return false;
LocalIdentity value = new() { key = key, body = Read<ulong>(record + 0x20), kind = Read<uint>(record + 0x184), market = Read<uint>(record + 0x130) };
var text = Pointer(record + 0x140); if (text == 0) return false;
var length = Read<ulong>(text); if (length == 0 || length >= 513) return false;
var used = 0;
for (ulong i = 0; i < length; i++)
{
var c = Read<byte>(text + 0x14 + (nuint)i); if (c == 0) return false;
if (c >= 32 && c != 127) value.name[used++] = c;
}
while (used != 0 && value.name[used - 1] == (byte)' ') value.name[--used] = 0;
var leading = 0; while (leading < used && value.name[leading] == (byte)' ') leading++;
if (leading != 0) { new Span<byte>(value.name + leading, used - leading).CopyTo(new Span<byte>(value.name, 513)); used -= leading; value.name[used] = 0; }
if (value.body == 0 || value.kind == 0 || used == 0) return false;
output = value; return true;
}
private static bool SameLocal(in LocalIdentity a, in LocalIdentity b)
{
if (a.key != b.key || a.body != b.body || a.kind != b.kind || a.market != b.market) return false;
fixed (byte* left = a.name) fixed (byte* right = b.name) return new ReadOnlySpan<byte>(left, 513).SequenceEqual(new ReadOnlySpan<byte>(right, 513));
}
private static bool LocalRecordMatches(nuint navigation, in LocalIdentity wanted) => ReadLocalIdentity(LocalRecord(navigation, wanted.key), wanted.key, out var actual) && SameLocal(actual, wanted);
private static LocalTargetObservation ObserveLocalTarget(in Snapshot frame)
{
var result = new LocalTargetObservation { target = new() { key = -1 } };
try
{
if (!frame.valid || GetTickCount64() - frame.tick > 200 || !BoundFsd(frame, frame.fsd)) return result;
var nav = Pointer(frame.fsd + 0x338);
if (Pointer(Pointer(nav) + 0x20) != image + 0x2EC8100 || Pointer(Pointer(nav) + 0xB8) != image + 0x2EC8D60) return result;
var key = Read<int>(nav + 0x38C);
if (!ReadLocalIdentity(LocalRecord(nav, key), key, out result.target) || (result.target.body & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL)) return result;
result.tick = frame.tick; result.entity = frame.entity; result.epoch = controllerEpoch; result.world = frame.referenceWorld;
result.life = frame.life; result.fsd = frame.fsd; result.inputRoot = frame.inputRoot;
result.valid = Read<int>(nav + 0x38C) == key && LocalRecordMatches(nav, result.target) && Read<ulong>(frame.transform + 0xE8) == frame.referenceWorld;
}
catch { result.valid = false; }
return result;
}
private static bool LocalSelectionAcknowledged(in LocalSelection wanted, in LocalTargetObservation observed, ulong confirmedGeneration, ulong now, ulong epoch, bool playerOverridden) =>
!playerOverridden && wanted.generation != 0 && confirmedGeneration == wanted.generation && observed.valid &&
observed.tick <= now && now - observed.tick <= 200 && observed.epoch == epoch && wanted.epoch == epoch &&
observed.entity == wanted.entity && observed.life == wanted.life && observed.fsd == wanted.fsd && observed.inputRoot == wanted.inputRoot &&
(observed.world & 0x007FFFFFFFFFFFFFUL) == (wanted.target.body & 0x007FFFFFFFFFFFFFUL) && SameLocal(observed.target, wanted.target);
private static bool LocalControlMatches(in Lease control, in Snapshot frame)
{
try
{
var navigation = Pointer(frame.fsd + 0x338); var record = LocalRecord(navigation, control.localKey);
return control.local && control.world == frame.referenceWorld && Read<int>(navigation + 0x38C) == control.localKey && record != 0 && Read<uint>(record + 0x188) == 1 && Read<ulong>(record + 0x20) == control.localBody && Read<uint>(record + 0x184) == control.localKind && Read<uint>(record + 0x130) == control.localMarket;
}
catch { return false; }
}
private static bool SelectLocalTargetImpl(in Snapshot frame, in LocalSelection wanted)
{
try
{
var now = GetTickCount64();
if (!Enabled() || !inGame || !frame.valid || !frame.healthValid || frame.healthEmergency || now - frame.tick > 200 || wanted.expires <= now || wanted.epoch != controllerEpoch || overridden || wanted.entity != frame.entity || wanted.life != frame.life || wanted.fsd != frame.fsd || wanted.inputRoot != frame.inputRoot || !BoundFsd(frame, frame.fsd) || !LocalSelectionFsdReady(frame.rawFsdState,fsdOwned)) return false;
var nav = Pointer(frame.fsd + 0x338);
if (Pointer(Pointer(nav) + 0x20) != image + 0x2EC8100 || Pointer(Pointer(nav) + 0x90) != image + 0x2ED6840 || Pointer(Pointer(nav) + 0xB8) != image + 0x2EC8D60 || (wanted.target.body & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL) || !LocalRecordMatches(nav, wanted.target)) return false;
if (Read<int>(nav + 0x38C) != wanted.target.key) Call(image + 0x2ED6840, nav, unchecked((ulong)wanted.target.key), 1, 0);
return Read<int>(nav + 0x38C) == wanted.target.key && LocalRecordMatches(nav, wanted.target);
}
catch { return false; }
}
private static void SelectLocalTarget(in Snapshot frame)
{
LocalSelection wanted; lock (localLock) wanted = localSelection;
if (wanted.generation == 0 || localConsumed == wanted.generation) return;
if (SelectLocalTargetImpl(frame, wanted)) localConfirmed = wanted.generation; else localFailed = wanted.generation;
localConsumed = wanted.generation;
}
private static double LocalDecodeFloat(nuint at)
{
var bits = Read<ulong>(at); return BitConverter.UInt32BitsToSingle((uint)bits ^ (uint)(bits >> 32));
}
private static bool LocalBodyPose(nuint body, nuint universal, nuint resource, out AffinePose output)
{
AffinePose value = default; var valid = BodyPose(body, universal, resource, &value); output = value; return valid;
}
private static bool LocalEncodedFlag(nuint at) { var bits = Read<ushort>(at); return (bits & 255) != (bits >> 8); }
private static bool SampleLocalFlight(in Snapshot frame, out LocalFlightObservation output, out string stage)
{
output = EmptyLocalObservation();
var next = EmptyLocalObservation();
stage = "snapshot";
try
{
if (!frame.valid || GetTickCount64() - frame.tick > 200 || !BoundFsd(frame, frame.fsd)) return false;
stage = "navigationInterface";
var nav = Pointer(frame.fsd + 0x338);
if (Pointer(Pointer(nav) + 0x20) != image + 0x2EC8100 || Pointer(Pointer(nav) + 0xB8) != image + 0x2EC8D60) return false;
stage = "selectedIdentity";
var key = Read<int>(nav + 0x38C); var record = LocalRecord(nav, key);
if (!ReadLocalIdentity(record, key, out next.target) || (next.target.body & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL)) return false;
stage = "loadedSystem";
var service = Pointer(frame.fsd + 0x6A0); var resource = LoadedSystem(service, frame.referenceWorld);
if (resource == 0 || Pointer(Pointer(service) + 0x60) != image + BodyLookup) return false;
stage = "bodyGeometry";
var bodies = Pointer(resource + 0x140); var capacity = Read<ulong>(resource + 0x148);
var shipBody = BodyMapEntry(bodies, capacity, frame.referenceWorld, 16, false); var targetBody = BodyMapEntry(bodies, capacity, next.target.body, 16, false);
if (shipBody == 0 || targetBody == 0 || !LocalBodyPose(shipBody, frame.transform + 0xE8, resource, out next.ship) || !LocalBodyPose(targetBody, record + 0x20, resource, out next.destination)) return false;
var delta = Subtract(next.destination.position, next.ship.position); next.distance = Math.Sqrt(Dot(delta, delta));
next.speed = frame.speed; next.throttle = Read<float>(frame.inputRoot + 0x70); next.angularVelocity = frame.angularVelocityLocal; next.angularVelocityValid = frame.angularVelocityValid;
stage = "distanceAndThrottle";
if (!double.IsFinite(next.distance) || next.distance < 0 || !double.IsFinite(next.throttle) || Math.Abs(next.throttle) > 1.001) return false;
stage = "dockingState";
var computer = LocalDockingComputer(frame);
if (computer != 0)
{
next.dockingComputerAvailable = LocalDockingAvailable(computer);
next.nativeDockingActive = (byte)Call(image + 0xED6620, computer) != 0;
next.nativeLaunchActive = (byte)Call(image + 0xED7D10, computer) != 0;
next.nativeLaunchRequested = Read<byte>(computer + 0x374) != 0;
var controller = Pointer(frame.inputRoot + 0x2F8);
next.dockingThrottleZero = controller != 0 && Pointer(computer + 0x240) == controller && Pointer(Pointer(controller) + 0x158) == image + ThrottleSetter
&&Read<float>(controller + 0x680)==0&&ReadOriginalDockingThrottle(computer,out var sourceThrottle)&&Math.Abs(sourceThrottle)<=.001f;
next.speedZeroInputSent = dockingZeroInputReceipt.entity == frame.entity && dockingZeroInputReceipt.life == frame.life && dockingZeroInputReceipt.inputService == Pointer(computer + 0x230) && frame.tick >= dockingZeroInputReceipt.tick && frame.tick - dockingZeroInputReceipt.tick < 30000;
next.dockingZeroInputSent = next.speedZeroInputSent && dockingZeroInputReceipt.market == next.target.market;
}
var dock = LocalDocking(frame);
if (dock != 0)
{
next.dockingState = Read<uint>(dock + 0x4C0); next.requestState = Read<uint>(dock + 0x614); next.pad = Read<byte>(dock + 0x4E9);
var station = Pointer(dock + 0x4E0); if (station != 0 && Read<uint>(station + 0xC0) == 8) next.stationLife = station;
next.dockingAssistEnabled = Read<byte>(dock + 0x5BB) != 0;
if (next.stationLife != 0 && Read<ulong>(next.stationLife + 0xB0) == next.target.market)
{
next.dockingVerified = true; next.dockingGranted = next.dockingState is 3 or 4 && next.requestState == 2 && next.pad != 255;
var stationService = RegisteredComponent(Pointer(next.stationLife + 0xC8) - 0x30, Read<uint>(image + 0x5F771D0));
if (stationService != 0)
{
var table = Pointer(stationService); var activeFunction = Pointer(table + 0x28); var allowed = Pointer(table + 0x58);
if (InImage(activeFunction) && InImage(allowed)) { next.dockingAssistAllowed = (byte)Call(allowed, stationService) != 0; next.dockingAssistEngaged = (byte)Call(activeFunction, stationService) != 0; }
}
}
}
stage = "motionLimits";
next.inSupercruise = LocalCruiseArrival(frame) != 0;
next.normalSpeedLimit = (double)Read<float>(frame.physics + 0x750) * Read<float>(frame.physics + 0x910);
var x = (double)Read<float>(frame.physics + 0x600); var y = (double)Read<float>(frame.physics + 0x604); var z = (double)Read<float>(frame.physics + 0x608);
next.accelerationLowerBound = Math.Min(x, Math.Min(y, z)) * .25;
next.cruiseSpeedLimit = next.inSupercruise && frame.speedFraction > .001 ? frame.speed / frame.speedFraction : next.normalSpeedLimit;
var arrival = LocalCruiseArrival(frame);
if (arrival != 0)
{
stage = "cruiseInterface";
var table = Pointer(arrival);
if (Pointer(table + 0x38) != image + 0x2EA4EE0 || Pointer(table + 0x118) != image + 0x2EA4EC0 || Pointer(table + 0x68) != image + 0x2EAD330) return false;
next.cruiseSpeedLimit = CallFloat(image + 0x2EA4EE0, arrival); var limits = (nuint)Call(image + 0x2EA4EC0, arrival); if (limits != arrival + 0x5A8) return false;
next.safeDropDistance = LocalDecodeFloat(limits + 0x28); next.safeDropSpeed = LocalDecodeFloat(limits + 0x48); next.safeDropAngle = LocalDecodeFloat(limits + 0x68);
var alignment = next.distance > 0 ? Math.Acos(Math.Clamp(Dot(next.ship.forward, Scale(delta, 1 / next.distance)), -1, 1)) : Math.PI;
next.safeDisengage = LocalEncodedFlag(limits + 0xE8) && LocalEncodedFlag(limits + 0x108) && LocalEncodedFlag(limits + 0x128) && double.IsFinite(next.safeDropDistance) && next.safeDropDistance > 0 && next.distance < next.safeDropDistance && double.IsFinite(next.safeDropSpeed) && next.safeDropSpeed > 0 && next.speed < next.safeDropSpeed && double.IsFinite(next.safeDropAngle) && next.safeDropAngle > 0 && next.safeDropAngle < 1.571 && alignment < next.safeDropAngle;
var helper = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F53174)); var assist = LocalCruiseAssist(frame);
if (helper != 0 && Pointer(helper) == image + 0x5164C78 && assist != 0)
{
next.cruiseApproachSeconds = Read<double>(Pointer(helper - 8) + 0x50); next.cruiseApproachThrottle = Read<float>(Pointer(assist - 8) + 0x10);
next.cruiseParametersVerified = double.IsFinite(next.cruiseApproachSeconds) && next.cruiseApproachSeconds > 0 && next.cruiseApproachSeconds <= 120 && double.IsFinite(next.cruiseApproachThrottle) && next.cruiseApproachThrottle > 0 && next.cruiseApproachThrottle <= 1;
}
}
stage = "motionLimits";
if (!double.IsFinite(next.normalSpeedLimit) || next.normalSpeedLimit <= 0 || !double.IsFinite(next.cruiseSpeedLimit) || next.cruiseSpeedLimit <= 0 || !double.IsFinite(next.accelerationLowerBound) || next.accelerationLowerBound <= 0) return false;
stage = "cruiseAssist";
var cruiseAssist = LocalCruiseAssist(frame);
if (cruiseAssist != 0)
{
next.cruiseAssistAvailable = (byte)Call(image + 0xECAF00, cruiseAssist) != 0; next.cruiseAssistEnabled = Read<byte>(cruiseAssist + 0x256) != 0; next.cruiseAssistEngaged = Read<byte>(cruiseAssist + 0x257) != 0;
next.cruiseAssistMatches = (byte)Call(image + 0xED8760, cruiseAssist, record) != 0; next.cruiseAssistObscured = (byte)Call(image + 0xED8A50, cruiseAssist, record) != 0;
}
next.tick = frame.tick; next.utc = frame.utcMilliseconds; next.sequence = frame.sequence; next.entity = frame.entity; next.world = frame.referenceWorld; next.selected = true;
next.valid = Read<int>(nav + 0x38C) == key && LocalRecordMatches(nav, next.target) && Read<ulong>(frame.transform + 0xE8) == frame.referenceWorld;
stage = next.valid ? "ready" : "identityChanged";
return next.valid;
}
catch (Exception error) { stage += ":" + error.GetType().Name; return false; }
finally { output = next; }
}
private static void PollLocalFlight(in Snapshot frame)
{
LocalFlightObservation before; lock (localLock) before = localObservation;
if (before.sequence == frame.sequence && before.tick != 0 && GetTickCount64() - before.tick < 100) return;
var selected = ObserveLocalTarget(frame);
var next = EmptyLocalObservation();
if (SampleLocalFlight(frame, out next, out var stage))
{
var seconds = ((double)next.tick - before.tick) * .001;
if (before.valid && before.entity == next.entity && before.world == next.world && SameLocal(before.target, next.target) && seconds >= .005 && seconds <= .3)
{
next.velocity = Scale(Subtract(next.ship.position, before.ship.position), 1 / seconds); next.targetVelocity = Scale(Subtract(next.destination.position, before.destination.position), 1 / seconds); next.velocityValid = Finite(next.velocity) && Finite(next.targetVelocity);
}
}
lock (localLock) { localObservation = next; localTargetObservation = selected; localSampleStage = stage; }
}
private static nuint LocalEntityFromDirectory(nuint context, nuint playerLife, ulong playerEntity, ulong wanted)
{
try
{
var count = Read<ulong>(context + 0x148); var capacity = Read<ulong>(context + 0x150); var storage = Pointer(context + 0x158);
if (storage == 0 || capacity < 8 || capacity > 65536 || (capacity & (capacity - 1)) != 0 || count > capacity) return 0;
var entries = storage + (nuint)(((capacity >> 3) + 7) & ~7UL); var own = false; nuint found = 0;
for (ulong i = 0; i < capacity; i++)
if (((Read<byte>(storage + (nuint)(i >> 3)) >> (int)(i & 7)) & 1) != 0)
{ var at = entries + (nuint)i * 16; var key = Read<ulong>(at); var life = Pointer(at + 8); if (key == playerEntity) own = life == playerLife; if (key == wanted) found = life; }
if (!own || found == 0 || Read<ulong>(context + 0x148) != count || Read<ulong>(context + 0x150) != capacity || Pointer(context + 0x158) != storage || Read<uint>(found + 0xC0) != 8 || Read<ulong>(found + 0xB0) != wanted) return 0;
return found;
}
catch { return 0; }
}
private static bool LocalDockingTargetKind(uint kind)=>XFEEDToolBox.Core.Navigation.NativeDockingTargetKinds.SupportsDocking(kind);
private static bool RequestLocalDocking(in Snapshot frame, in LocalFlightObservation observed)
{
var nav = Pointer(frame.fsd + 0x338); var dock = LocalDocking(frame); var record = LocalRecord(nav, observed.target.key);
if (dock == 0 || record == 0 || observed.inSupercruise || observed.distance > 7500 || observed.target.market == 0 || observed.target.market == uint.MaxValue || !LocalDockingTargetKind(observed.target.kind) || Pointer(Pointer(dock) + 0xF8) != image + 0x2C65C30) return false;
var context = Pointer(frame.life + 0x38); var token = (ulong)Read<uint>(record + 0x130); if (context == 0 || token == 0 || token == uint.MaxValue) return false;
var existing = LocalEntityFromDirectory(context, frame.life, frame.entity, token); if (existing == 0) return false;
nuint station = 0; Call(image + 0x62D0A0, (ulong)(nuint)(&station), (ulong)(nuint)(&existing));
try
{
if (station == 0 || Read<uint>(station + 0xC0) != 8 || Read<ulong>(station + 0xB0) != token) return false;
var stationPose = LocalStationUniversalPose(station); var resource = LoadedSystem(Pointer(frame.fsd + 0x6A0), frame.referenceWorld); if (stationPose == 0 || resource == 0) return false;
var stationWorld = Read<ulong>(stationPose); if ((stationWorld & 0x007FFFFFFFFFFFFFUL) != (frame.referenceWorld & 0x007FFFFFFFFFFFFFUL)) return false;
var body = BodyMapEntry(Pointer(resource + 0x140), Read<ulong>(resource + 0x148), stationWorld, 16, false); if (body == 0 || !LocalBodyPose(body, stationPose, resource, out var pose)) return false;
var delta = Subtract(pose.position, observed.ship.position); if (Dot(delta, delta) > 7500.0 * 7500.0 || !LocalRecordMatches(nav, observed.target) || Read<int>(nav + 0x38C) != observed.target.key) return false;
EndNormalBoostPulse();
if(observed.dockingComputerAvailable)
{
var controller=Pointer(frame.inputRoot+0x2F8);
if(controller==0||Pointer(Pointer(controller)+0x158)!=image+ThrottleSetter||!SendOriginalDockingZeroInput(frame,controller,observed.target.market))return false;
}
return (byte)Call(image + 0x2C65C30, dock, (ulong)(nuint)(&station)) != 0;
}
finally { Call(image + 0x62F3A0, (ulong)(nuint)(&station)); }
}
private static bool SendOriginalDockingZeroInput(in Snapshot frame, nuint controller, uint market)
{
try
{
var computer = LocalDockingComputer(frame); var actions = computer != 0 ? Pointer(computer + 0x230) : 0;
if (computer == 0 || Pointer(computer + 0x240) != controller || actions == 0 || Pointer(actions) != image + 0x512FD90 || Pointer(Pointer(actions) + 0x40) != image + 0x8FEDC0) return false;
var scratch = stackalloc byte[47]; var temporary = AlignServiceScratch(scratch); new Span<byte>(temporary, 32).Clear();
try
{
Call(image + 0x8FEDC0, actions, (ulong)(nuint)temporary, image + 0x531A568);
var input = Pointer((nuint)temporary); if (input == 0 || Read<int>(input) <= 0 || Read<uint>(input + 0x1C) == uint.MaxValue) return false;
Write(input + 0x24, (byte)(Read<byte>(input + 0x24) | 1)); Write(input + 0x1C, unchecked(Read<uint>(input + 0x1C) + 1)); Call(image + ThrottleSetter, controller, F32(0));
dockingZeroInputReceipt = new() { entity = frame.entity, tick = frame.tick, life = frame.life, binding = input, inputService = actions, market = market }; return true;
}
finally { Call(image + 0x204010, (ulong)(nuint)temporary); }
}
catch { return false; }
}
private static bool LocalDockingZeroConfirmed(bool nativeDuty,bool inputSent,float controllerThrottle,double actualThrottle)
=>nativeDuty||inputSent&&controllerThrottle==0&&double.IsFinite(actualThrottle)&&Math.Abs(actualThrottle)<=.001;
private static bool ApplyLocalAction(in Snapshot frame, in LocalActionRequest command, in LocalSelection selection, in LocalFlightObservation observed)
{
try
{
var now = GetTickCount64();
if (GetCurrentThreadId() != owningThread || !Enabled() || !inGame || !frame.valid || !frame.healthValid || frame.hullFraction<=0 || frame.healthEmergency || overridden || command.expires <= now || command.entity != frame.entity || command.life != frame.life || command.inputRoot != frame.inputRoot || command.world != frame.referenceWorld || command.epoch != controllerEpoch || command.selection != selection.generation || localConfirmed != selection.generation || !observed.valid || !SameLocal(observed.target, selection.target) || now - observed.tick > 200 || now - frame.tick > 200 || !BoundFsd(frame, frame.fsd) || !LocalRecordMatches(Pointer(frame.fsd + 0x338), selection.target)) return false;
if (command.action == 1) return RequestLocalDocking(frame, observed);
if(command.action==13)return RequestNormalBoost(frame);
if (command.action == 12)
{
if (!observed.nativeDockingActive) return true;
if (observed.nativeLaunchActive || observed.inSupercruise) return false;
var computer = LocalDockingComputer(frame); var activity = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F44C98));
if (computer == 0 || activity == 0 || Pointer(activity) != image + 0x5165E08 || Pointer(Pointer(activity) + 0x30) != image + 0xEACAD0 || (byte)Call(image + 0xED6620, computer) == 0) return false;
Call(image + 0xEACAD0, activity); var controller = Pointer(frame.inputRoot + 0x2F8);
if (controller == 0 || Pointer(computer + 0x240) != controller || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
Call(image + ThrottleSetter, controller, F32(0)); return true;
}
if (command.action == 9)
{
var computer = LocalDockingComputer(frame); var dock = LocalDocking(frame); var activity = RegisteredComponent(Pointer(frame.life + 0xC8) - 0x30, Read<uint>(image + 0x5F44C98));
if (computer == 0 || dock == 0 || activity == 0 || !observed.nativeLaunchActive && !observed.nativeLaunchRequested || !observed.dockingVerified || observed.inSupercruise || Pointer(activity) != image + 0x5165E08 || Pointer(Pointer(activity) + 0x30) != image + 0xEACAD0 || Pointer(Pointer(computer) + 0x80) != image + 0xEE8710) return false;
if (observed.nativeLaunchActive) Call(image + 0xEACAD0, activity);
if (Read<byte>(computer + 0x374) != 0) { nuint empty = 0; Call(image + 0xEE8710, computer, ulong.MaxValue, (ulong)(nuint)(&empty)); }
var controller = Pointer(frame.inputRoot + 0x2F8);
if (controller == 0 || Pointer(computer + 0x240) != controller || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
Call(image + ThrottleSetter, controller, F32(0)); return true;
}
if (command.action == 8)
{
var dock = LocalDocking(frame); var state = dock != 0 ? Read<uint>(dock + 0x4C0) : 0; var computer = LocalDockingComputer(frame);
if (dock == 0 || computer == 0 || !observed.dockingComputerAvailable || !LocalDockingAvailable(computer) || observed.inSupercruise || state is not (5 or 7) || observed.distance > 7500 || Pointer(Pointer(dock) + 0xF0) != image + 0x2C65CD0) return false;
var station = Pointer(dock + 0x4E0);
if (station == 0 || Read<uint>(station + 0xC0) != 8 || Read<ulong>(station + 0xB0) != observed.target.market) return false;
Call(image + 0xEF8A60, computer); return (byte)Call(image + 0x2C65CD0, dock, (ulong)(nuint)(&station)) != 0;
}
if (command.action == 7)
{
var arrival = LocalCruiseArrival(frame);
if (arrival == 0 || !observed.safeDisengage || !observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || observed.target.kind <= 3 || observed.target.kind == 39) return false;
Call(image + 0x2EAD330, arrival); return true;
}
if (command.action == 6)
{
if (!observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || frame.speed > 100000 || observed.cruiseAssistEngaged) return false;
Lease control; lock (leaseLock) control = lease;
if (control.expires <= now || !LocalControlMatches(control, frame) || control.throttle != 0) return false;
var next = new FsdLease { expires = command.expires, sequence = command.sequence, generation = NextFsdGeneration(), entity = frame.entity, fsd = frame.fsd, inputRoot = frame.inputRoot, life = frame.life, command = 4, heatCeiling = 80, local = true };
lock (leaseLock) fsdLease = next; return true;
}
if (command.action == 5)
{
if (observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || HeatHudPercent(frame) >= 90 || Read<byte>(frame.inputRoot + 0x60 + 0xA9) != 0 || Read<byte>(frame.inputRoot + 0x60 + 0xB1) != 0 || Read<byte>(frame.inputRoot + 0x60 + 0xBB) != 0) return false;
Lease control; lock (leaseLock) control = lease;
if (control.expires <= now || !LocalControlMatches(control, frame) || control.throttle < .75f) return false;
var next = new FsdLease { expires = command.expires, sequence = command.sequence, generation = NextFsdGeneration(), entity = frame.entity, fsd = frame.fsd, inputRoot = frame.inputRoot, life = frame.life, command = 1, heatCeiling = 90, supercruiseEntry = true, local = true };
lock (leaseLock) fsdLease = next; return true;
}
if (command.action == 10)
{
var dock = LocalDocking(frame); var computer = LocalDockingComputer(frame); var controller = Pointer(frame.inputRoot + 0x2F8);
if (dock == 0 || computer == 0 || !observed.dockingComputerAvailable || !LocalDockingAvailable(computer) || controller == 0 || Pointer(computer + 0x240) != controller || !observed.dockingVerified || !observed.dockingGranted || observed.inSupercruise || observed.distance > 7500 || Read<uint>(dock + 0x614) != 2 || Read<uint>(dock + 0x4C0) is not (3 or 4) || Read<byte>(dock + 0x4E9) == 255 || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
var station = Pointer(dock + 0x4E0);
if (station == 0 || Read<uint>(station + 0xC0) != 8 || Read<ulong>(station + 0xB0) != observed.target.market) return false;
var actualThrottle=Read<float>(frame.inputRoot+0x70);
if((byte)Call(image+0xED6620,computer)!=0)return true;
bool released;lock(leaseLock)released=lease.expires<=now&&fsdLease.expires<=now;
if(!released||fsdOwned)return false;
var sourceVerified=ReadOriginalDockingThrottle(computer,out var sourceThrottle);
if(!LocalDockingSourceZeroConfirmed(false,observed.dockingZeroInputSent,Read<float>(controller+0x680),actualThrottle,sourceVerified?sourceThrottle:double.NaN)
&&!SendOriginalDockingZeroInput(frame,controller,observed.target.market))return false;
if(!CommitOriginalDockingZeroAxis(frame.inputRoot,controller,actualThrottle,true,released,out _))return false;
lock(localLock)dockingZeroUpdate=new(){command=command,computer=computer,controller=controller,fsd=frame.fsd,market=observed.target.market,throttle=actualThrottle};
return true;
}
if (command.action == 11)
{
var controller = Pointer(frame.inputRoot + 0x2F8);
if (observed.inSupercruise || frame.rawFsdState != 2 || fsdOwned || controller == 0 || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter) return false;
return SendOriginalDockingZeroInput(frame, controller, 0);
}
var assist = LocalCruiseAssist(frame);
if (command.action == 2)
{
if (assist == 0 || !observed.inSupercruise || !observed.cruiseAssistAvailable || (byte)Call(image + 0xECAF00, assist) == 0 || observed.cruiseAssistObscured || observed.distance < 1) return false;
if (observed.cruiseAssistEnabled && observed.cruiseAssistMatches) return true;
var direction = Scale(Subtract(observed.destination.position, observed.ship.position), 1 / observed.distance);
if (Dot(direction, observed.ship.forward) < Math.Cos(5.0 * Math.PI / 180)) return false;
var controller = Pointer(frame.inputRoot + 0x2F8);
if (controller == 0 || Pointer(Pointer(controller) + 0x158) != image + ThrottleSetter || !observed.cruiseParametersVerified) return false;
Call(image + ThrottleSetter, controller, F32((float)observed.cruiseApproachThrottle)); Call(image + 0xEEA430, assist, 1); return true;
}
if (command.action == 3)
{
if (assist == 0 || !observed.cruiseAssistMatches || !observed.cruiseAssistEnabled && !observed.cruiseAssistEngaged) return true;
Call(image + 0xEEA430, assist, 0); return true;
}
return false;
}
catch { return false; }
}
private static void PollLocalAction(in Snapshot frame)
{
PollNormalBoostPulse(frame);
LocalActionRequest command; LocalSelection selection; LocalFlightObservation observed;
lock (localLock) { command = localAction; selection = localSelection; observed = localObservation; }
if (command.generation == 0 || command.generation == localActionConsumed) return;
if (ApplyLocalAction(frame, command, selection, observed)) localActionApplied = command.generation; else localActionFailed = command.generation;
localActionConsumed = command.generation;
}
private static string LocalIdentityName(in LocalIdentity value)
{
fixed (byte* text = value.name) { var length = 0; while (length < 513 && text[length] != 0) length++; return Encoding.UTF8.GetString(new ReadOnlySpan<byte>(text, length)); }
}
private static JsonObject LocalIdentityJson(in LocalIdentity value) => Obj(("key", value.key), ("bodyAddress", value.body.ToString(CultureInfo.InvariantCulture)), ("kind", value.kind), ("marketId", value.market), ("name", LocalIdentityName(value)));
private static JsonArray LocalVector(Vector value) => Arr(value.x, value.y, value.z);
private static JsonObject LocalFlightRead(in Snapshot frame)
{
LocalFlightObservation value;DockingMapperReceipt mapper;
lock(localLock){value=localObservation;mapper=dockingInputMapperReceipt;}var now=GetTickCount64();
if (!frame.valid || !value.valid || value.entity != frame.entity || value.world != frame.referenceWorld || now - value.tick > 200) throw new InvalidOperationException("Local target telemetry unavailable or stale");
return Obj(("sequence", value.sequence), ("observedAtUnixMilliseconds", value.utc), ("entityId", value.entity.ToString(CultureInfo.InvariantCulture)), ("referenceWorldAddress", value.world.ToString(CultureInfo.InvariantCulture)),
("geometryVerified", true), ("selected", value.selected), ("target", LocalIdentityJson(value.target)), ("position", LocalVector(value.ship.position)), ("forward", LocalVector(value.ship.forward)), ("up", LocalVector(value.ship.up)), ("targetPosition", LocalVector(value.destination.position)),
("velocity", LocalVector(value.velocity)), ("targetVelocity", LocalVector(value.targetVelocity)), ("velocityVerified", value.velocityValid), ("angularVelocity", LocalVector(value.angularVelocity)), ("angularVelocityVerified", value.angularVelocityValid), ("speedZeroInputSent", value.speedZeroInputSent),
("distanceMetres", value.distance), ("speedMetresPerSecond", value.speed), ("throttle", value.throttle), ("rawDockingState", value.dockingState), ("rawRequestState", value.requestState), ("rawPad", value.pad), ("normalSpeedLimit", value.normalSpeedLimit), ("cruiseSpeedLimit", value.cruiseSpeedLimit), ("accelerationLowerBound", value.accelerationLowerBound),
("safeDisengage", value.safeDisengage), ("safeDropDistance", value.safeDropDistance), ("safeDropSpeed", value.safeDropSpeed), ("cruiseParametersVerified", value.cruiseParametersVerified), ("cruiseApproachSeconds", value.cruiseApproachSeconds), ("cruiseApproachThrottle", value.cruiseApproachThrottle),
("rawFsdState", frame.rawFsdState), ("rawFsdMode", frame.rawFsdMode), ("rawFsdStage", frame.rawFsdStage), ("inSupercruise", value.inSupercruise),
("normalBoostAvailable",TryNormalBoostBinding(frame,out _,out _)),("normalBoostInputs",normalBoostInputs),("normalBoostReleased",normalBoostReleased),
("normalBoostPressed",normalBoostPulse is not null),("normalBoostLastTick",lastNormalBoostAt),
("dockingZeroAxisCommits", dockingZeroAxisCommits), ("dockingZeroAxisRestoresSkipped", dockingZeroAxisRestoresSkipped),
("dockingZeroUpdateCalls",dockingZeroUpdateCalls),("dockingZeroUpdateMatched",dockingZeroUpdateMatched),("dockingZeroUpdateApplied",dockingZeroUpdateApplied),
("dockingInputMapperCalls",dockingInputMapperCalls),("dockingInputMapperMatched",dockingInputMapperMatched),("dockingInputMapperApplied",dockingInputMapperApplied),
("dockingInputMapper",Obj(("verified",mapper.verified),("sequence",mapper.sequence),("entityId",mapper.entity.ToString(CultureInfo.InvariantCulture)),
("observedAtUnixMilliseconds",mapper.utc),("accumulatorBefore",mapper.before),("accumulatorAfter",mapper.after),("sourceAfter",mapper.source))),
("supercruiseAssist", Obj(("available", value.cruiseAssistAvailable), ("enabled", value.cruiseAssistEnabled), ("engaged", value.cruiseAssistEngaged), ("matchesTarget", value.cruiseAssistMatches), ("obscured", value.cruiseAssistObscured))),
("docking", Obj(("verified", value.dockingVerified), ("computerAvailable", value.dockingComputerAvailable), ("clearanceGranted", value.dockingGranted), ("assistAllowed", value.dockingAssistAllowed), ("assistEnabled", value.dockingAssistEnabled), ("assistEngaged", value.dockingAssistEngaged), ("nativeDutyActive", value.nativeDockingActive), ("nativeLaunchActive", value.nativeLaunchActive), ("nativeLaunchRequested", value.nativeLaunchRequested), ("throttleZero", value.dockingThrottleZero), ("zeroThrottleInputSent", value.dockingZeroInputSent))));
}
private static JsonObject LocalFlightRequest(string op, JsonElement request, string owner, in Snapshot frame)
{
var now = GetTickCount64();
if (op == "local.suspend")
{
if (clientOwner != owner) throw new InvalidOperationException("Local control belongs to another controller");
lock (leaseLock) { if (lease.local) lease.expires = 0; if (fsdLease.local) fsdLease.expires = 0; }
lock(localLock)dockingZeroUpdate.command.expires=0;
return Obj(("suspended", true));
}
if (!Enabled() || !inGame || !frame.valid || !frame.healthValid || frame.hullFraction<=0 || frame.healthEmergency || now - frame.tick > 200) throw new InvalidOperationException("Fresh healthy player ship required for local navigation");
ulong expiry; lock (leaseLock) expiry = Math.Max(lease.expires, Math.Max(fsdLease.expires, targetLease.expires));
LocalSelection wanted; lock (localLock) wanted = localSelection;
if (expiry <= now && wanted.expires <= now && !fsdOwned && op == "local.select") clientOwner = "";
if (clientOwner.Length != 0 && clientOwner != owner) throw new InvalidOperationException("Another controller owns local navigation");
if (op == "local.select")
{
if (expiry > now || !LocalSelectionFsdReady(frame.rawFsdState,fsdOwned)) throw new InvalidOperationException("Stop flight control before selecting a local destination");
LocalIdentity identity = new() { key = request.Required("key").GetInt32(), body = ulong.Parse(request.Required("bodyAddress").GetString()!, CultureInfo.InvariantCulture), kind = request.Required("kind").GetUInt32(), market = request.Required("marketId").GetUInt32() };
var name = request.Required("name").GetString()!; var encoded = Encoding.UTF8.GetBytes(name);
if (identity.key < 0 || identity.body == 0 || identity.kind == 0 || encoded.Length == 0 || encoded.Length >= 513 || name.Contains('\0')) throw new InvalidOperationException("Invalid local target identity");
encoded.CopyTo(new Span<byte>(identity.name, 513));
if (clientOwner.Length == 0) overridden = false;
ClaimController(owner); wanted = new() { target = identity, generation = ++localGeneration, expires = now + 300, entity = frame.entity, epoch = controllerEpoch, life = frame.life, fsd = frame.fsd, inputRoot = frame.inputRoot };
lock (localLock) localSelection = wanted;
}
else if (clientOwner != owner || wanted.generation == 0 || request.Required("generation").GetUInt64() != wanted.generation) throw new InvalidOperationException("No matching local target request");
if (op == "local.action")
{
var reference = Reference(request.Required("sequence").GetUInt64(), frame, now); var utc = UtcMilliseconds(); var deadline = request.Required("expiresAtUnixMilliseconds").GetUInt64();
var action = request.Required("action").GetString() switch { "requestDocking" => 1u, "enableSupercruiseAssist" => 2u, "cancelSupercruiseAssist" => 3u, "enterSupercruise" => 5u, "leaveSupercruiseSlowly" => 6u, "safeDisengage" => 7u, "launch" => 8u, "cancelLaunch" => 9u, "zeroThrottleForDocking" => 10u, "setNormalThrottleZero" => 11u, "cancelDockingAssist" => 12u, "boostThrusters"=>13u, _ => 0u };
if (action == 0 || deadline <= utc || deadline > utc + 300 || overridden || localConfirmed != wanted.generation) throw new InvalidOperationException("Invalid or expired local action");
var next = new LocalActionRequest { generation = ++localActionGeneration, selection = wanted.generation, expires = Math.Min(now + deadline - utc, reference.tick + 300), sequence = reference.sequence, entity = frame.entity, epoch = controllerEpoch, world = frame.referenceWorld, life = frame.life, inputRoot = frame.inputRoot, action = action };
lock (localLock) localAction = next; return Obj(("queued", true), ("actionGeneration", next.generation));
}
if (op == "local.action.status")
{
var generation = request.Required("actionGeneration").GetUInt64(); LocalActionRequest command; lock (localLock) command = localAction;
if (generation != command.generation || command.selection != wanted.generation) throw new InvalidOperationException("No matching local action");
var applied = localActionApplied == generation; var failed = localActionFailed == generation;
return Obj(("applied", applied), ("failed", failed || !applied && command.expires <= now), ("pending", !applied && !failed && command.expires > now), ("actionGeneration", generation));
}
if (op == "local.axes.release") { lock (leaseLock) if (lease.local) lease.expires = 0; return Obj(("released", true)); }
if (op == "local.apply")
{
RequirePhysicalInputSources(frame,now);
var reference = Reference(request.Required("sequence").GetUInt64(), frame, now); var utc = UtcMilliseconds(); var deadline = request.Required("expiresAtUnixMilliseconds").GetUInt64();
if (deadline <= utc || deadline > utc + 300 || overridden || localConfirmed != wanted.generation) throw new InvalidOperationException("Local control frame expired or selection changed");
var values = request.Required("axes"); if (values.GetArrayLength() != 6) throw new InvalidOperationException("Invalid local control axes");
Float6 axes = default; var invalidAxes = false;
for (var index = 0; index < 6; index++) { axes[index] = values[index].GetSingle(); invalidAxes |= !float.IsFinite(axes[index]) || Math.Abs(axes[index]) > 1; }
var throttle = request.TryGetProperty("throttle", out var throttleValue) ? throttleValue.GetSingle() : -1;
if (!float.IsFinite(throttle) || throttle != -1 && (throttle < 0 || throttle > 1) || invalidAxes) throw new InvalidOperationException("Invalid local control axes");
var next = new Lease { expires = Math.Min(now + deadline - utc, reference.tick + 300), sequence = reference.sequence, inputRoot = frame.inputRoot, axes = axes, throttle = throttle, epoch = controllerEpoch, local = true, localKey = wanted.target.key, localKind = wanted.target.kind, localBody = wanted.target.body, localMarket = wanted.target.market, world = frame.referenceWorld, yieldOnDockingGrant = request.Bool("yieldOnDockingGrant") };
if (!LocalControlMatches(next, frame)) throw new InvalidOperationException("Local target changed before applying controls");
lock (leaseLock)
{
lease = next;
if (fsdLease.local && fsdLease.entity == frame.entity && fsdLease.life == frame.life && fsdLease.command == 1 && fsdLease.expires != 0 && frame.rawFsdState == 3) { fsdLease.expires = next.expires; fsdLease.sequence = reference.sequence; }
}
return Obj(("accepted", true), ("generation", wanted.generation), ("sequence", reference.sequence));
}
LocalFlightObservation observed; LocalTargetObservation selected; string stage;
lock (localLock) { observed = localObservation; selected = localTargetObservation; stage = localSampleStage; }
// The original navigation selection is observable before a remote body's
// full flight geometry is ready. Confirm that selection independently;
// local.read and all flight actions still require complete telemetry.
var confirmed = wanted.entity == frame.entity && wanted.life == frame.life && wanted.fsd == frame.fsd && wanted.inputRoot == frame.inputRoot &&
LocalSelectionAcknowledged(wanted, selected, localConfirmed, now, controllerEpoch, overridden);
var telemetryReady = observed.valid && observed.entity == frame.entity && observed.world == frame.referenceWorld && SameLocal(observed.target, wanted.target) && observed.tick <= now && now - observed.tick <= 200;
return Obj(("generation", wanted.generation), ("selected", confirmed), ("pending", !confirmed && localFailed != wanted.generation && wanted.expires > now),
("target", LocalIdentityJson(selected.target)), ("flightInputsSent", 0), ("telemetryReady", telemetryReady), ("sampleStage", stage),
("sampleLimits", Obj(("normalSpeed", double.IsFinite(observed.normalSpeedLimit) ? observed.normalSpeedLimit : 0),
("cruiseSpeed", double.IsFinite(observed.cruiseSpeedLimit) ? observed.cruiseSpeedLimit : 0),
("acceleration", double.IsFinite(observed.accelerationLowerBound) ? observed.accelerationLowerBound : 0))));
}
}