using System.Text.Json; using XFEEDToolBox.Core.Plugins; namespace XFEEDToolBox.Core.Navigation; public sealed partial class NativeSystemFlightControlDriver { private NavigationPipeClient? dockingResetPipe; private string? dockingResetRequestId; private DateTimeOffset dockingResetDeadline; private bool requestOriginalDockingStart; private bool emptyDockingDiagnostic=true; public JsonElement? LastOriginalDockingResetReceipt{get;private set;} /// Explicit empty-ship validation retains its narrower ship/cargo limits. public Task EnableOriginalDockingResetTestAsync(CancellationToken token=default)=>EnableOriginalDockingResetTestAsync(false,token); public async Task EnableOriginalDockingResetTestAsync(bool startDockingAfterZero,CancellationToken token=default) =>await ConfigureOriginalDockingAsync(startDockingAfterZero,true,token); public async Task ConfigureOriginalDockingControlAsync(string packageDirectory,CancellationToken token=default) { var native=new NavigationPipeClient(processId,OriginalDockingService); try{await native.CallAsync(new{op="describe"},token);} catch(Exception error)when(!token.IsCancellationRequested&&error is IOException or OperationCanceledException) { var main=await pipe.CallAsync(new{op="describe"},token); if(main.GetProperty("processId").GetInt32()!=processId||main.GetProperty("gameSha256").GetString()!=gameHash) throw new InvalidDataException("Original docking package belongs to another game."); await RuntimeInteractionModule.LoadAsync(processId,gameHash,main.GetProperty("owningThread").GetInt32(),packageDirectory,token); } await ConfigureOriginalDockingAsync(true,false,token); } private async Task ConfigureOriginalDockingAsync(bool startDockingAfterZero,bool diagnostic,CancellationToken token) { if(dockingResetRequestId is not null)throw new InvalidOperationException("An original docking test request is already immutable."); const string service=OriginalDockingService; var candidate=new NavigationPipeClient(processId,service);var hello=await candidate.CallAsync(new{op="describe"},token); if(hello.GetProperty("processId").GetInt32()!=processId||hello.GetProperty("gameSha256").GetString()!=gameHash ||hello.GetProperty("service").GetString()!=service||!Flag(hello,"nativeDockingReset")||!Flag(hello,"enabled") ||hello.GetProperty("owningThread").GetInt32()<=0||hello.GetProperty("state").GetString()!="InGame"||hello.GetProperty("nextState").GetString()!="InGame" ||DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()-hello.GetProperty("observedAtUnixMilliseconds").GetInt64() is <0 or >200 ||startDockingAfterZero&&!Flag(hello,"nativeDockingStart")) throw new NotSupportedException("Verified original docking reset test extension is not loaded."); dockingResetPipe=candidate;requestOriginalDockingStart=startDockingAfterZero;emptyDockingDiagnostic=diagnostic; } private void AdoptOriginalResetReceipt(JsonElement value) { if(value.GetProperty("requestId").GetString()!=dockingResetRequestId)throw new InvalidDataException("Original reset receipt identity differs."); if(value.TryGetProperty("nativeGeneration",out var selectedGeneration)&&selectedGeneration.ValueKind==JsonValueKind.Number&&selectedGeneration.GetInt64()>0) generation=selectedGeneration.GetInt64(); LastOriginalDockingResetReceipt=value.Clone(); } private async Task OriginalDockingResetAsync(SystemFlightFrame frame,DateTimeOffset deadline,CancellationToken token) { var service=dockingResetPipe??throw new InvalidOperationException("Original docking reset is not configured."); if(frame.Mode!=LocalFlightMode.Normal||frame.PlayerOverride||frame.ExternalThreat||selected is null||frame.Clearance?.Allows(selected,DateTimeOffset.UtcNow)!=true ||frame.TargetDistance>7500||player().CurrentShipId is null ||emptyDockingDiagnostic&&(player().CargoCount!=0||player().CurrentShipId!=5||player().CargoCapacity!=192)) throw new InvalidOperationException("Original reset requires the same selected main ship, current ATC and the configured validation scope."); if(dockingResetRequestId is not null) { if(LastOriginalDockingResetReceipt is {} prior&&Flag(prior,"applied") &&LastTelemetry is {} current&&NativeSystemFlightObservation.DockingInputZeroConfirmed(current))return; throw new InvalidOperationException("This docking grant already used its one original reset attempt; it will not be replayed."); } if(ensureFlightProtection is not null)await ensureFlightProtection(token); await pipe.CallAsync(new{op="local.axes.release",owner,generation},token); var remaining=(int)Math.Floor((deadline-DateTimeOffset.UtcNow).TotalMilliseconds); if(remaining is <1 or >250)throw new FlightTelemetryUnavailableException("Original reset intent frame is expired."); dockingResetRequestId=Guid.NewGuid().ToString("N");dockingResetDeadline=deadline; try { var receipt=await service.CallAsync(new{op="docking.reset.arm",requestId=dockingResetRequestId,owner,generation, sequence=frame.Sequence,marketId=selected.MarketId,ttlMilliseconds=remaining,expiresAtUnixMilliseconds=deadline.ToUnixTimeMilliseconds(),startDockingAfterZero=requestOriginalDockingStart},token); AdoptOriginalResetReceipt(receipt); } catch(Exception original)when(original is IOException or InvalidOperationException or OperationCanceledException) { // Resolve an uncertain acknowledgement by the same immutable ID; // arm is never sent again, even if a native reselect already happened. JsonElement reconciled; try { using var reconciliation=new CancellationTokenSource(TimeSpan.FromMilliseconds(500)); reconciled=await service.CallAsync(new{op="docking.reset.status",requestId=dockingResetRequestId,owner},reconciliation.Token); } catch(Exception status)when(!emptyDockingDiagnostic&&!token.IsCancellationRequested &&(NavigationRecovery.CanRetry(status,CancellationToken.None)||status is InvalidOperationException&&status.Message=="No matching original reset request")) {throw new DockingResetObservationPendingException(dockingResetRequestId,selected.MarketId!.Value,frame.ObservedAt,new AggregateException(original,status));} catch(Exception status){throw new InvalidOperationException("Original reset outcome is unknown; the arm request was not replayed.",new AggregateException(original,status));} AdoptOriginalResetReceipt(reconciled); } while(true) { token.ThrowIfCancellationRequested();var receipt=LastOriginalDockingResetReceipt!.Value; if(Flag(receipt,"failed")||Flag(receipt,"expired")) { var error=new InvalidOperationException("Original reset was rejected: "+(receipt.TryGetProperty("issue",out var issue)?issue.GetString():"no detail")); if(!emptyDockingDiagnostic&&DockingValidationProof.RejectedBeforeResetDispatch(receipt)) throw new DockingResetObservationPendingException(dockingResetRequestId,selected.MarketId!.Value,frame.ObservedAt,error); throw error; } if(Flag(receipt,"applied")) { if(!DockingValidationProof.OriginalResetZero(receipt,frame.ShipEntity,selected.MarketId!.Value,frame.ObservedAt,DateTimeOffset.UtcNow)) throw new InvalidDataException("Original reset did not verify the selected main ship's actual accumulator and source zero."); if(!requestOriginalDockingStart)return; if(!Flag(receipt,"dockingStartRequested"))throw new InvalidDataException("Explicit original docking start was not accepted by the pinned test extension."); if(receipt.TryGetProperty("dockingStartFailure",out var startFailure)&&startFailure.ValueKind==JsonValueKind.String&&!string.IsNullOrEmpty(startFailure.GetString())) throw new InvalidOperationException("Original docking start was rejected: "+startFailure.GetString()); if(Flag(receipt,"originalDockingStartCalled"))return; // A status read can overlap the one original native call. // Reconcile that same request until its original deadline; // neither an in-progress call nor an unknown reply is retried. } if(DateTimeOffset.UtcNow>deadline.AddMilliseconds(350))throw new InvalidOperationException("Original reset receipt remains unresolved; no request will be replayed."); await Task.Delay(10,token); AdoptOriginalResetReceipt(await service.CallAsync(new{op="docking.reset.status",requestId=dockingResetRequestId,owner},token)); } } private async Task CancelOriginalDockingResetAsync() { if(dockingResetPipe is null||dockingResetRequestId is null)return; try{AdoptOriginalResetReceipt(await dockingResetPipe.CallAsync(new{op="docking.reset.cancel",requestId=dockingResetRequestId,owner}));} catch { // The extension enforces this original absolute deadline. Waiting // it out does not extend the input or create a replacement request. var remaining=dockingResetDeadline-DateTimeOffset.UtcNow;if(remaining>TimeSpan.Zero)await Task.Delay(remaining); } } }