using System.Text.Json.Serialization;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Navigation;
public sealed record FlightStopSnapshot(string CommanderId,long? ShipId,DateTimeOffset? LoadGameAt,string GameMode,bool InGame,bool Docked,int CargoCount,
long? CurrentSystemAddress,long? SelectedSystemAddress,int RouteCount,long[] RoutePrefix,long? RouteDestination,bool JumpInProgress,
long? JumpTargetSystemAddress,string JumpTargetSystem,DateTimeOffset? JumpStartedAt,DateTimeOffset? LastJumpAt)
{
public static FlightStopSnapshot Capture(PlayerState state)=>new(state.CommanderId,state.CurrentShipId,state.LoadGameAt,state.GameMode,state.InGame,state.Docked,state.CargoCount,
state.Navigation.CurrentSystemAddress,state.Navigation.TargetSystemAddress,state.Navigation.Route.Count,state.Navigation.Route.Take(16).Select(point=>point.SystemAddress).ToArray(),
state.Navigation.Route.LastOrDefault()?.SystemAddress,state.Navigation.JumpInProgress,state.Navigation.JumpTargetSystemAddress,state.Navigation.JumpTargetSystem,
state.Navigation.JumpStartedAt,state.Navigation.LastJumpAt);
}
/// <summary>The first cause is immutable; explicit manual cancellation can add a veto without erasing that evidence.</summary>
public sealed record FlightAutomationStopRecord(string RunId,string Source,DateTimeOffset At,FlightAutomationEntry? Entry,FlightAutomationStage Stage,
bool ParentTokenCancelled,string Reason,string? CauseType,FlightStopSnapshot? Snapshot)
{
[JsonIgnore]public Exception? Cause{get;init;}
public bool ManualVeto{get;init;}
public DateTimeOffset? ManualVetoAt{get;init;}
}
/// <summary>Read-only proof that this runner already released inputs for this exact, bounded Journal jump.</summary>
public sealed record InputFreeJumpSnapshot(string CommanderId,long ShipId,DateTimeOffset LoadGameAt,string GameMode,long OriginSystemAddress,
long TargetSystemAddress,string TargetSystem,DateTimeOffset JumpStartedAt,DateTimeOffset WaitingSince,bool InputsReleased)
{
public static readonly TimeSpan MaximumWait=TimeSpan.FromSeconds(75);
public DateTimeOffset Deadline=>WaitingSince+MaximumWait;
public bool Matches(PlayerState current,DateTimeOffset now)=>InputsReleased&&now>=WaitingSince&&now<=Deadline
&¤t.InGame&&!current.Docked&¤t.CommanderId==CommanderId&¤t.CurrentShipId==ShipId&¤t.LoadGameAt==LoadGameAt&¤t.GameMode==GameMode
&&!NavigationSafety.UnhealthyHull(current)&&!NavigationSafety.HasExternalThreat(current)
&¤t.Navigation.CurrentSystemAddress==OriginSystemAddress&¤t.Navigation.JumpInProgress
&¤t.Navigation.JumpTargetSystemAddress==TargetSystemAddress&&string.Equals(current.Navigation.JumpTargetSystem,TargetSystem,StringComparison.OrdinalIgnoreCase)
&¤t.Navigation.JumpStartedAt==JumpStartedAt;
public static InputFreeJumpSnapshot? AfterSuccessfulRelease(PlayerState initial,PlayerState current,NavWaypoint actualTarget,
DateTimeOffset runJournalStartedAt,DateTimeOffset waitingSince)
{
if(string.IsNullOrWhiteSpace(initial.CommanderId)||initial.CurrentShipId is not >=0||initial.LoadGameAt is not {} loaded
||current.Navigation.CurrentSystemAddress is not >0||current.Navigation.JumpStartedAt is not {} started||started<runJournalStartedAt||started<loaded
||current.Navigation.JumpTargetSystemAddress!=actualTarget.SystemAddress||!string.Equals(current.Navigation.JumpTargetSystem,actualTarget.Name,StringComparison.OrdinalIgnoreCase))return null;
var snapshot=new InputFreeJumpSnapshot(initial.CommanderId,initial.CurrentShipId.Value,loaded,initial.GameMode,current.Navigation.CurrentSystemAddress.Value,
actualTarget.SystemAddress,actualTarget.Name,started,waitingSince,true);
return snapshot.Matches(current,waitingSince)?snapshot:null;
}
}
using System.Text.Json.Serialization;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Navigation;
public sealed record FlightStopSnapshot(string CommanderId,long? ShipId,DateTimeOffset? LoadGameAt,string GameMode,bool InGame,bool Docked,int CargoCount,
long? CurrentSystemAddress,long? SelectedSystemAddress,int RouteCount,long[] RoutePrefix,long? RouteDestination,bool JumpInProgress,
long? JumpTargetSystemAddress,string JumpTargetSystem,DateTimeOffset? JumpStartedAt,DateTimeOffset? LastJumpAt)
{
public static FlightStopSnapshot Capture(PlayerState state)=>new(state.CommanderId,state.CurrentShipId,state.LoadGameAt,state.GameMode,state.InGame,state.Docked,state.CargoCount,
state.Navigation.CurrentSystemAddress,state.Navigation.TargetSystemAddress,state.Navigation.Route.Count,state.Navigation.Route.Take(16).Select(point=>point.SystemAddress).ToArray(),
state.Navigation.Route.LastOrDefault()?.SystemAddress,state.Navigation.JumpInProgress,state.Navigation.JumpTargetSystemAddress,state.Navigation.JumpTargetSystem,
state.Navigation.JumpStartedAt,state.Navigation.LastJumpAt);
}
/// <summary>The first cause is immutable; explicit manual cancellation can add a veto without erasing that evidence.</summary>
public sealed record FlightAutomationStopRecord(string RunId,string Source,DateTimeOffset At,FlightAutomationEntry? Entry,FlightAutomationStage Stage,
bool ParentTokenCancelled,string Reason,string? CauseType,FlightStopSnapshot? Snapshot)
{
[JsonIgnore]public Exception? Cause{get;init;}
public bool ManualVeto{get;init;}
public DateTimeOffset? ManualVetoAt{get;init;}
}
/// <summary>Read-only proof that this runner already released inputs for this exact, bounded Journal jump.</summary>
public sealed record InputFreeJumpSnapshot(string CommanderId,long ShipId,DateTimeOffset LoadGameAt,string GameMode,long OriginSystemAddress,
long TargetSystemAddress,string TargetSystem,DateTimeOffset JumpStartedAt,DateTimeOffset WaitingSince,bool InputsReleased)
{
public static readonly TimeSpan MaximumWait=TimeSpan.FromSeconds(75);
public DateTimeOffset Deadline=>WaitingSince+MaximumWait;
public bool Matches(PlayerState current,DateTimeOffset now)=>InputsReleased&&now>=WaitingSince&&now<=Deadline
&¤t.InGame&&!current.Docked&¤t.CommanderId==CommanderId&¤t.CurrentShipId==ShipId&¤t.LoadGameAt==LoadGameAt&¤t.GameMode==GameMode
&&!NavigationSafety.UnhealthyHull(current)&&!NavigationSafety.HasExternalThreat(current)
&¤t.Navigation.CurrentSystemAddress==OriginSystemAddress&¤t.Navigation.JumpInProgress
&¤t.Navigation.JumpTargetSystemAddress==TargetSystemAddress&&string.Equals(current.Navigation.JumpTargetSystem,TargetSystem,StringComparison.OrdinalIgnoreCase)
&¤t.Navigation.JumpStartedAt==JumpStartedAt;
public static InputFreeJumpSnapshot? AfterSuccessfulRelease(PlayerState initial,PlayerState current,NavWaypoint actualTarget,
DateTimeOffset runJournalStartedAt,DateTimeOffset waitingSince)
{
if(string.IsNullOrWhiteSpace(initial.CommanderId)||initial.CurrentShipId is not >=0||initial.LoadGameAt is not {} loaded
||current.Navigation.CurrentSystemAddress is not >0||current.Navigation.JumpStartedAt is not {} started||started<runJournalStartedAt||started<loaded
||current.Navigation.JumpTargetSystemAddress!=actualTarget.SystemAddress||!string.Equals(current.Navigation.JumpTargetSystem,actualTarget.Name,StringComparison.OrdinalIgnoreCase))return null;
var snapshot=new InputFreeJumpSnapshot(initial.CommanderId,initial.CurrentShipId.Value,loaded,initial.GameMode,current.Navigation.CurrentSystemAddress.Value,
actualTarget.SystemAddress,actualTarget.Name,started,waitingSince,true);
return snapshot.Matches(current,waitingSince)?snapshot:null;
}
}