using System.Text.Json;
using System.Text.Json.Nodes;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class StationDepartureContinuityTests
{
private static readonly DateTimeOffset Now=DateTimeOffset.UtcNow;
private static PlayerState Player=>new(){InGame=true,Docked=false,CommanderId="F1",CurrentShipId=7,LoadGameAt=Now.AddMinutes(-5),SystemName="System 1",
Status=new(1L<<16,0,100,1,0,null,null,null,null,[]),StatusAt=Now,StatusReadAt=Now,StatusWrittenAt=Now,
Navigation=new(){CurrentSystemAddress=1,UndockedAt=Now.AddSeconds(-45),UndockedMarketId=10,UndockedStationName="Port",UndockedSystemAddress=1}};
private static JsonObject Main()=>JsonSerializer.SerializeToNode(new{sequence=1,observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),entityId="100",referenceWorldAddress="1",
inGame=true,localPlayerShipVerified=true,playerOverride=false,rawFsdState=2,shipHealth=new{verified=true,entityId="100",observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),hullFraction=.702915,emergency=false,damageObserved=false}})!.AsObject();
private static JsonObject Local(bool launch=true)=>JsonSerializer.SerializeToNode(new{sequence=1,observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),entityId="100",referenceWorldAddress="1",
geometryVerified=true,inSupercruise=false,rawDockingState=9,target=new{marketId=10},docking=new{verified=true,computerAvailable=true,nativeLaunchActive=launch,nativeLaunchRequested=false,
nativeDutyActive=launch,clearanceGranted=false}})!.AsObject();
private static bool Needs(PlayerState player,JsonObject main,JsonObject local,long? destination=null)
=>StationDepartureContinuity.NeedsContinuation(player,JsonSerializer.SerializeToElement(main),JsonSerializer.SerializeToElement(local),destination,Now);
[Test]public void Live12UndockedInsideTheStationWithOriginalLaunchActiveCannotStartAnotherFlightPhase()
{
Check.That(Needs(Player,Main(),Local()));
Check.Equal(10L,StationDepartureContinuity.KnownOrigin(Player,Now)!.MarketId);
Check.That(Needs(Player,Main(),Local(),10),"Returning to the origin does not permit fighting an active original launch.");
}
[Test]public void InactiveLaunchStillMassLockedNeedsExitEvidenceExceptForNormalReturnToTheConfirmedOrigin()
{
Check.That(Needs(Player,Main(),Local(false)));Check.That(Needs(Player,Main(),Local(false),20));
Check.That(!Needs(Player,Main(),Local(false),10));
var unknown=Player with{Navigation=Player.Navigation with{UndockedMarketId=null,UndockedStationName=""}};
Check.That(Needs(unknown,Main(),Local(false),10));Check.That(StationDepartureContinuity.KnownOrigin(unknown,Now) is null);
}
[Test]public void NormalFreeFlightAndActualSupercruiseOrHyperspaceDoNotNeedAnInventedOrigin()
{
var free=Player with{Status=Player.Status! with{Flags=0},Navigation=Player.Navigation with{UndockedAt=null,UndockedMarketId=null,UndockedStationName=""}};
Check.That(!Needs(free,Main(),Local(false)));
var cruise=free with{Status=free.Status! with{Flags=16}};
Check.That(!StationDepartureContinuity.NeedsContinuation(cruise,JsonSerializer.SerializeToElement(Main()),default,null,Now));
var jumping=Main();jumping["rawFsdState"]=4;
Check.That(!StationDepartureContinuity.NeedsContinuation(free,JsonSerializer.SerializeToElement(jumping),default,null,Now));
}
[Test]public void AnOriginalLandingAtTheRequestedLocalStationRemainsObservationOnly()
{
var landing=Local(false);landing["rawDockingState"]=3;landing["docking"]!["nativeDutyActive"]=true;landing["docking"]!["clearanceGranted"]=true;
Check.That(!Needs(Player,Main(),landing,10));Check.That(Needs(Player,Main(),landing,20));
}
[Test]public void ManualTakeoverFreshDamageAndUnverifiedOrForeignObservationsCannotClearTheDepartureGate()
{
var manual=Main();manual["playerOverride"]=true;Check.Throws<InvalidDataException>(()=>Needs(Player,manual,Local()));
foreach(var key in new[]{"emergency","damageObserved"}){var damaged=Main();damaged["shipHealth"]![key]=true;Check.Throws<FlightHealthPauseException>(()=>Needs(Player,damaged,Local()));}
var dead=Main();dead["shipHealth"]!["hullFraction"]=0;Check.Throws<FlightHealthPauseException>(()=>Needs(Player,dead,Local()));
var stale=Local();stale["observedAtUnixMilliseconds"]=Now.AddSeconds(-1).ToUnixTimeMilliseconds();Check.Throws<FlightTelemetryUnavailableException>(()=>Needs(Player,Main(),stale));
var foreign=Local();foreign["entityId"]="200";Check.Throws<FlightTelemetryUnavailableException>(()=>Needs(Player,Main(),foreign));
var unknown=Local();unknown["docking"]!.AsObject().Remove("nativeLaunchActive");Check.That(Needs(Player,Main(),unknown));
}
[Test]public void TheExistingUndockReceiptIsUsableButNeverAcrossAnotherSessionSystemOrCompletedCruise()
{
Check.That(StationDepartureContinuity.KnownOrigin(Player,Now) is not null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{LoadGameAt=Now},Now) is null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{Navigation=Player.Navigation with{CurrentSystemAddress=2}},Now) is null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{Navigation=Player.Navigation with{SupercruiseEnteredAt=Now.AddSeconds(-1)}},Now) is null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{Navigation=Player.Navigation with{LastJumpAt=Now.AddSeconds(-1)}},Now) is null);
}
[Test]public void JournalProjectionRetainsTheActualUndockedMarketNameAndSystemForANewHost()
{
var projector=new NavigationProjector();projector.Apply(JsonSerializer.SerializeToElement(new{@event="Location",timestamp=Now.AddSeconds(-10),SystemAddress=1}));
projector.Apply(JsonSerializer.SerializeToElement(new{@event="Undocked",timestamp=Now,StationName="Port",MarketID=10}));
Check.Equal(10L,projector.State.UndockedMarketId!.Value);Check.Equal("Port",projector.State.UndockedStationName);Check.Equal(1L,projector.State.UndockedSystemAddress!.Value);
}
[Test]public void ComputerZeroDefaultFlagsCannotReleaseAnOriginObligationOrBecomeInactiveCancellationProof()
{
var local=Local(false);local["docking"]!["computerAvailable"]=false;
Check.That(!StationDepartureContinuity.NativeDutySourceVerified(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(!StationDepartureContinuity.NativeLaunchInactive(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(Needs(Player,Main(),local));Check.That(Needs(Player,Main(),local,10));
local["docking"]!["throttleZero"]=true;
Check.That(StationDepartureContinuity.NativeDutySourceVerified(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(StationDepartureContinuity.NativeLaunchInactive(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(!Needs(Player,Main(),local,10));
local["docking"]!.AsObject().Remove("nativeDutyActive");
Check.That(!StationDepartureContinuity.NativeLaunchInactive(JsonSerializer.SerializeToElement(local["docking"])));Check.That(Needs(Player,Main(),local,10));
}
[Test]public void OnlyACompleteCurrentLoadoutWithNoInstalledComputerCanEstablishFreeFlightWithoutAComputerSource()
{
var free=Player with{Status=Player.Status! with{Flags=0},Navigation=Player.Navigation with{UndockedAt=null,UndockedMarketId=null,UndockedStationName=""},
Ships=[new(){ShipId=7,Type="type9",ConfigurationAt=Now,Modules=[new(){Slot="FrameShiftDrive",Item="int_hyperdrive_size7_class5",Enabled=true}]}]};
var unknown=Local(false);unknown["docking"]!["computerAvailable"]=false;
Check.That(StationDepartureContinuity.VerifiedNoInstalledDockingComputer(free,Now));Check.That(!Needs(free,Main(),unknown));
foreach(var computer in new[]{"int_dockingcomputer_standard","int_dockingcomputer_advanced"})
{
var disabled=free with{Ships=[free.Ships[0] with{Modules=[..free.Ships[0].Modules,new(){Slot="Slot01",Item=computer,Enabled=false,Health=0}]}]};
Check.That(!StationDepartureContinuity.VerifiedNoInstalledDockingComputer(disabled,Now));Check.Throws<InvalidDataException>(()=>Needs(disabled,Main(),unknown));
}
foreach(var ship in new[]{free.Ships[0] with{ConfigurationChanged=true},free.Ships[0] with{ConfigurationAt=null},free.Ships[0] with{Modules=[]}})
Check.Throws<InvalidDataException>(()=>Needs(free with{Ships=[ship]},Main(),unknown));
}
[Test]public void ObservationSelectionIgnoresSelectedSignalsAndRequiresARealCompletePhysicalBody()
{
SystemLocation Body(int key,ulong world,SystemLocationCategory kind,bool selected=false,string? identity=null)=>new(key,world,"Body "+key,"",1,kind,null,null,null,selected,identity??new string('a',64));
var signal=Body(1,0,SystemLocationCategory.Signal,true);var planet=Body(2,22,SystemLocationCategory.Planet,true);var star=Body(3,33,SystemLocationCategory.Star);
var catalogue=new CurrentSystemSnapshot(1,"System 1",100,1,Now,[signal,planet,star]);
Check.Equal(22UL,StationDepartureContinuity.ObservationTarget(catalogue,22).BodyAddress);
Check.Equal(33UL,StationDepartureContinuity.ObservationTarget(catalogue,99).BodyAddress);
Check.Equal(22UL,StationDepartureContinuity.ObservationTarget(catalogue with{Locations=[signal,star with{Identity="partial"},planet]},99).BodyAddress);
Check.Throws<InvalidDataException>(()=>StationDepartureContinuity.ObservationTarget(catalogue with{Locations=[signal]},99));
}
[Test]public void ARealSameSessionSupercruiseEntryAndExitDischargeTheLaunchEvenWhenTheArrivalIsStillMassLocked()
{
var travelled=Player with{Navigation=Player.Navigation with{SupercruiseEnteredAt=Now.AddSeconds(-30),SupercruiseExitedAt=Now.AddSeconds(-5),LastJumpAt=Now.AddSeconds(-15)}};
Check.That(!Needs(travelled,Main(),Local(false)));Check.That(!Needs(travelled,Main(),Local(false),20));
foreach(var navigation in new[]
{
travelled.Navigation with{SupercruiseEnteredAt=null},travelled.Navigation with{SupercruiseExitedAt=null},
travelled.Navigation with{SupercruiseEnteredAt=Now.AddSeconds(-4)},travelled.Navigation with{SupercruiseEnteredAt=Now.AddMinutes(-6)},
travelled.Navigation with{UndockedAt=Now.AddSeconds(-20)},travelled.Navigation with{UndockedAt=Now.AddSeconds(-2)},
travelled.Navigation with{LastJumpAt=Now.AddSeconds(-2)},travelled.Navigation with{SupercruiseExitedAt=Now.AddSeconds(6)}
})Check.That(Needs(travelled with{Navigation=navigation},Main(),Local(false)),"Incomplete, prior-session, reversed or superseded transit must not discharge departure.");
var unknown=Local(false);unknown["docking"]!["computerAvailable"]=false;
Check.Throws<InvalidDataException>(()=>Needs(travelled,Main(),unknown));
Check.That(Needs(travelled,Main(),Local()));
var duty=Local(false);duty["docking"]!["nativeDutyActive"]=true;Check.That(Needs(travelled,Main(),duty));
}
[Test]public void VerifiedAbsenceAndUnlockedFreeFlightAreIndependentOfAStillRetainedManualUndockOrigin()
{
var manual=Player with{Status=Player.Status! with{Flags=0},Ships=[new(){ShipId=7,Type="type9",ConfigurationAt=Now,
Modules=[new(){Slot="FrameShiftDrive",Item="int_hyperdrive_size7_class5",Enabled=true}]}]};
Check.That(StationDepartureContinuity.KnownOrigin(manual,Now) is not null);
var unknown=Local(false);unknown["docking"]!["computerAvailable"]=false;
Check.That(!Needs(manual,Main(),unknown));
Check.That(Needs(manual with{Status=manual.Status! with{Flags=1L<<16}},Main(),unknown));
foreach(var name in new[]{"int_dockingcomputer_standard","int_dockingcomputer_advanced"})
{
var disabled=manual with{Ships=[manual.Ships[0] with{Modules=[..manual.Ships[0].Modules,new(){Slot="Slot01",Item=name,Enabled=false,Health=0}]}]};
Check.That(Needs(disabled,Main(),unknown));
}
Check.That(Needs(manual with{Ships=[manual.Ships[0] with{ConfigurationChanged=true}]},Main(),unknown));
}
}
using System.Text.Json;
using System.Text.Json.Nodes;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class StationDepartureContinuityTests
{
private static readonly DateTimeOffset Now=DateTimeOffset.UtcNow;
private static PlayerState Player=>new(){InGame=true,Docked=false,CommanderId="F1",CurrentShipId=7,LoadGameAt=Now.AddMinutes(-5),SystemName="System 1",
Status=new(1L<<16,0,100,1,0,null,null,null,null,[]),StatusAt=Now,StatusReadAt=Now,StatusWrittenAt=Now,
Navigation=new(){CurrentSystemAddress=1,UndockedAt=Now.AddSeconds(-45),UndockedMarketId=10,UndockedStationName="Port",UndockedSystemAddress=1}};
private static JsonObject Main()=>JsonSerializer.SerializeToNode(new{sequence=1,observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),entityId="100",referenceWorldAddress="1",
inGame=true,localPlayerShipVerified=true,playerOverride=false,rawFsdState=2,shipHealth=new{verified=true,entityId="100",observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),hullFraction=.702915,emergency=false,damageObserved=false}})!.AsObject();
private static JsonObject Local(bool launch=true)=>JsonSerializer.SerializeToNode(new{sequence=1,observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),entityId="100",referenceWorldAddress="1",
geometryVerified=true,inSupercruise=false,rawDockingState=9,target=new{marketId=10},docking=new{verified=true,computerAvailable=true,nativeLaunchActive=launch,nativeLaunchRequested=false,
nativeDutyActive=launch,clearanceGranted=false}})!.AsObject();
private static bool Needs(PlayerState player,JsonObject main,JsonObject local,long? destination=null)
=>StationDepartureContinuity.NeedsContinuation(player,JsonSerializer.SerializeToElement(main),JsonSerializer.SerializeToElement(local),destination,Now);
[Test]public void Live12UndockedInsideTheStationWithOriginalLaunchActiveCannotStartAnotherFlightPhase()
{
Check.That(Needs(Player,Main(),Local()));
Check.Equal(10L,StationDepartureContinuity.KnownOrigin(Player,Now)!.MarketId);
Check.That(Needs(Player,Main(),Local(),10),"Returning to the origin does not permit fighting an active original launch.");
}
[Test]public void InactiveLaunchStillMassLockedNeedsExitEvidenceExceptForNormalReturnToTheConfirmedOrigin()
{
Check.That(Needs(Player,Main(),Local(false)));Check.That(Needs(Player,Main(),Local(false),20));
Check.That(!Needs(Player,Main(),Local(false),10));
var unknown=Player with{Navigation=Player.Navigation with{UndockedMarketId=null,UndockedStationName=""}};
Check.That(Needs(unknown,Main(),Local(false),10));Check.That(StationDepartureContinuity.KnownOrigin(unknown,Now) is null);
}
[Test]public void NormalFreeFlightAndActualSupercruiseOrHyperspaceDoNotNeedAnInventedOrigin()
{
var free=Player with{Status=Player.Status! with{Flags=0},Navigation=Player.Navigation with{UndockedAt=null,UndockedMarketId=null,UndockedStationName=""}};
Check.That(!Needs(free,Main(),Local(false)));
var cruise=free with{Status=free.Status! with{Flags=16}};
Check.That(!StationDepartureContinuity.NeedsContinuation(cruise,JsonSerializer.SerializeToElement(Main()),default,null,Now));
var jumping=Main();jumping["rawFsdState"]=4;
Check.That(!StationDepartureContinuity.NeedsContinuation(free,JsonSerializer.SerializeToElement(jumping),default,null,Now));
}
[Test]public void AnOriginalLandingAtTheRequestedLocalStationRemainsObservationOnly()
{
var landing=Local(false);landing["rawDockingState"]=3;landing["docking"]!["nativeDutyActive"]=true;landing["docking"]!["clearanceGranted"]=true;
Check.That(!Needs(Player,Main(),landing,10));Check.That(Needs(Player,Main(),landing,20));
}
[Test]public void ManualTakeoverFreshDamageAndUnverifiedOrForeignObservationsCannotClearTheDepartureGate()
{
var manual=Main();manual["playerOverride"]=true;Check.Throws<InvalidDataException>(()=>Needs(Player,manual,Local()));
foreach(var key in new[]{"emergency","damageObserved"}){var damaged=Main();damaged["shipHealth"]![key]=true;Check.Throws<FlightHealthPauseException>(()=>Needs(Player,damaged,Local()));}
var dead=Main();dead["shipHealth"]!["hullFraction"]=0;Check.Throws<FlightHealthPauseException>(()=>Needs(Player,dead,Local()));
var stale=Local();stale["observedAtUnixMilliseconds"]=Now.AddSeconds(-1).ToUnixTimeMilliseconds();Check.Throws<FlightTelemetryUnavailableException>(()=>Needs(Player,Main(),stale));
var foreign=Local();foreign["entityId"]="200";Check.Throws<FlightTelemetryUnavailableException>(()=>Needs(Player,Main(),foreign));
var unknown=Local();unknown["docking"]!.AsObject().Remove("nativeLaunchActive");Check.That(Needs(Player,Main(),unknown));
}
[Test]public void TheExistingUndockReceiptIsUsableButNeverAcrossAnotherSessionSystemOrCompletedCruise()
{
Check.That(StationDepartureContinuity.KnownOrigin(Player,Now) is not null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{LoadGameAt=Now},Now) is null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{Navigation=Player.Navigation with{CurrentSystemAddress=2}},Now) is null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{Navigation=Player.Navigation with{SupercruiseEnteredAt=Now.AddSeconds(-1)}},Now) is null);
Check.That(StationDepartureContinuity.KnownOrigin(Player with{Navigation=Player.Navigation with{LastJumpAt=Now.AddSeconds(-1)}},Now) is null);
}
[Test]public void JournalProjectionRetainsTheActualUndockedMarketNameAndSystemForANewHost()
{
var projector=new NavigationProjector();projector.Apply(JsonSerializer.SerializeToElement(new{@event="Location",timestamp=Now.AddSeconds(-10),SystemAddress=1}));
projector.Apply(JsonSerializer.SerializeToElement(new{@event="Undocked",timestamp=Now,StationName="Port",MarketID=10}));
Check.Equal(10L,projector.State.UndockedMarketId!.Value);Check.Equal("Port",projector.State.UndockedStationName);Check.Equal(1L,projector.State.UndockedSystemAddress!.Value);
}
[Test]public void ComputerZeroDefaultFlagsCannotReleaseAnOriginObligationOrBecomeInactiveCancellationProof()
{
var local=Local(false);local["docking"]!["computerAvailable"]=false;
Check.That(!StationDepartureContinuity.NativeDutySourceVerified(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(!StationDepartureContinuity.NativeLaunchInactive(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(Needs(Player,Main(),local));Check.That(Needs(Player,Main(),local,10));
local["docking"]!["throttleZero"]=true;
Check.That(StationDepartureContinuity.NativeDutySourceVerified(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(StationDepartureContinuity.NativeLaunchInactive(JsonSerializer.SerializeToElement(local["docking"])));
Check.That(!Needs(Player,Main(),local,10));
local["docking"]!.AsObject().Remove("nativeDutyActive");
Check.That(!StationDepartureContinuity.NativeLaunchInactive(JsonSerializer.SerializeToElement(local["docking"])));Check.That(Needs(Player,Main(),local,10));
}
[Test]public void OnlyACompleteCurrentLoadoutWithNoInstalledComputerCanEstablishFreeFlightWithoutAComputerSource()
{
var free=Player with{Status=Player.Status! with{Flags=0},Navigation=Player.Navigation with{UndockedAt=null,UndockedMarketId=null,UndockedStationName=""},
Ships=[new(){ShipId=7,Type="type9",ConfigurationAt=Now,Modules=[new(){Slot="FrameShiftDrive",Item="int_hyperdrive_size7_class5",Enabled=true}]}]};
var unknown=Local(false);unknown["docking"]!["computerAvailable"]=false;
Check.That(StationDepartureContinuity.VerifiedNoInstalledDockingComputer(free,Now));Check.That(!Needs(free,Main(),unknown));
foreach(var computer in new[]{"int_dockingcomputer_standard","int_dockingcomputer_advanced"})
{
var disabled=free with{Ships=[free.Ships[0] with{Modules=[..free.Ships[0].Modules,new(){Slot="Slot01",Item=computer,Enabled=false,Health=0}]}]};
Check.That(!StationDepartureContinuity.VerifiedNoInstalledDockingComputer(disabled,Now));Check.Throws<InvalidDataException>(()=>Needs(disabled,Main(),unknown));
}
foreach(var ship in new[]{free.Ships[0] with{ConfigurationChanged=true},free.Ships[0] with{ConfigurationAt=null},free.Ships[0] with{Modules=[]}})
Check.Throws<InvalidDataException>(()=>Needs(free with{Ships=[ship]},Main(),unknown));
}
[Test]public void ObservationSelectionIgnoresSelectedSignalsAndRequiresARealCompletePhysicalBody()
{
SystemLocation Body(int key,ulong world,SystemLocationCategory kind,bool selected=false,string? identity=null)=>new(key,world,"Body "+key,"",1,kind,null,null,null,selected,identity??new string('a',64));
var signal=Body(1,0,SystemLocationCategory.Signal,true);var planet=Body(2,22,SystemLocationCategory.Planet,true);var star=Body(3,33,SystemLocationCategory.Star);
var catalogue=new CurrentSystemSnapshot(1,"System 1",100,1,Now,[signal,planet,star]);
Check.Equal(22UL,StationDepartureContinuity.ObservationTarget(catalogue,22).BodyAddress);
Check.Equal(33UL,StationDepartureContinuity.ObservationTarget(catalogue,99).BodyAddress);
Check.Equal(22UL,StationDepartureContinuity.ObservationTarget(catalogue with{Locations=[signal,star with{Identity="partial"},planet]},99).BodyAddress);
Check.Throws<InvalidDataException>(()=>StationDepartureContinuity.ObservationTarget(catalogue with{Locations=[signal]},99));
}
[Test]public void ARealSameSessionSupercruiseEntryAndExitDischargeTheLaunchEvenWhenTheArrivalIsStillMassLocked()
{
var travelled=Player with{Navigation=Player.Navigation with{SupercruiseEnteredAt=Now.AddSeconds(-30),SupercruiseExitedAt=Now.AddSeconds(-5),LastJumpAt=Now.AddSeconds(-15)}};
Check.That(!Needs(travelled,Main(),Local(false)));Check.That(!Needs(travelled,Main(),Local(false),20));
foreach(var navigation in new[]
{
travelled.Navigation with{SupercruiseEnteredAt=null},travelled.Navigation with{SupercruiseExitedAt=null},
travelled.Navigation with{SupercruiseEnteredAt=Now.AddSeconds(-4)},travelled.Navigation with{SupercruiseEnteredAt=Now.AddMinutes(-6)},
travelled.Navigation with{UndockedAt=Now.AddSeconds(-20)},travelled.Navigation with{UndockedAt=Now.AddSeconds(-2)},
travelled.Navigation with{LastJumpAt=Now.AddSeconds(-2)},travelled.Navigation with{SupercruiseExitedAt=Now.AddSeconds(6)}
})Check.That(Needs(travelled with{Navigation=navigation},Main(),Local(false)),"Incomplete, prior-session, reversed or superseded transit must not discharge departure.");
var unknown=Local(false);unknown["docking"]!["computerAvailable"]=false;
Check.Throws<InvalidDataException>(()=>Needs(travelled,Main(),unknown));
Check.That(Needs(travelled,Main(),Local()));
var duty=Local(false);duty["docking"]!["nativeDutyActive"]=true;Check.That(Needs(travelled,Main(),duty));
}
[Test]public void VerifiedAbsenceAndUnlockedFreeFlightAreIndependentOfAStillRetainedManualUndockOrigin()
{
var manual=Player with{Status=Player.Status! with{Flags=0},Ships=[new(){ShipId=7,Type="type9",ConfigurationAt=Now,
Modules=[new(){Slot="FrameShiftDrive",Item="int_hyperdrive_size7_class5",Enabled=true}]}]};
Check.That(StationDepartureContinuity.KnownOrigin(manual,Now) is not null);
var unknown=Local(false);unknown["docking"]!["computerAvailable"]=false;
Check.That(!Needs(manual,Main(),unknown));
Check.That(Needs(manual with{Status=manual.Status! with{Flags=1L<<16}},Main(),unknown));
foreach(var name in new[]{"int_dockingcomputer_standard","int_dockingcomputer_advanced"})
{
var disabled=manual with{Ships=[manual.Ships[0] with{Modules=[..manual.Ships[0].Modules,new(){Slot="Slot01",Item=name,Enabled=false,Health=0}]}]};
Check.That(Needs(disabled,Main(),unknown));
}
Check.That(Needs(manual with{Ships=[manual.Ships[0] with{ConfigurationChanged=true}]},Main(),unknown));
}
}