using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class AssistAvailabilityTests
{
private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-23T00:00:00Z");
private static readonly SystemNavigationTarget Target=new(1,101,"Station",SystemTargetKind.Station,77);
private static SystemFlightFrame Frame=>new(){Sequence=1,ObservedAt=Now,GameSha256=new('a',64),LoadoutFingerprint="ship",ShipEntity=1,SystemAddress=1,ReferenceWorld=101,
InGame=true,GeometryVerified=true,Mode=LocalFlightMode.Supercruise,SelectedTarget=Target,Forward=new(0,0,1),Up=new(0,1,0),TargetPosition=new(0,0,6e8),TargetDistance=6e8,
MaximumNormalSpeed=300,MaximumCruiseSpeed=1e10,CruiseParametersVerified=true,CruiseApproachSeconds=6,CruiseApproachThrottle=.75,
BrakingAcceleration=10,TranslationAcceleration=5,LegalApproachSpeed=100,AssistAlignmentDegrees=5,Assists=new(false,true,true)};
[Test] public void InstalledHardwareIsFilteredByLiveModuleAvailability()
{
var fitted=new NavigationAssists(true,true,true);
Check.Equal(new NavigationAssists(false,false,true),fitted.Available(false,true));
Check.Equal(new NavigationAssists(true,true,false),fitted.Available(true,false));
Check.Equal(new NavigationAssists(false,false,false),fitted.Available(false,false));
Check.Equal(new NavigationAssists(false,false,false),new NavigationAssists(false,false,false).Available(true,true));
}
[Test] public void StaleDutyFlagsCannotOverrideAnUnavailableModule()
{
Check.Equal(NativeAssistState.Unavailable,NativeSystemFlightObservation.CruiseAssist(false,true,true,true,true));
Check.Equal(NativeAssistState.Unavailable,NativeSystemFlightObservation.DockingAssist(false,true,true));
Check.Equal(NativeAssistState.Ready,NativeSystemFlightObservation.DockingAssist(true,false,false));
}
[Test] public void LostCruiseAssistImmediatelyResumesSoftwareApproachWithoutAnotherEnableRequest()
{
foreach(var failure in new[]{NativeAssistState.Unavailable,NativeAssistState.Disabled,NativeAssistState.Failed})
{
var policy=new SystemFlightNavigator();
var native=policy.Decide(Frame with{SupercruiseAssist=NativeAssistState.Engaged},Target,Now);
Check.That(native.Throttle is null&&native.Direction is null);
var next=Frame with{SupercruiseAssist=failure,ObservedAt=Now.AddSeconds(1)};
var fallback=policy.Decide(next,Target,next.ObservedAt);
Check.Equal(.75,fallback.Throttle);Check.That(fallback.Direction is not null);
Check.That(fallback.Actions.HasFlag(SystemNavigationAction.CancelOwnedAssists));
for(var second=2;second<=25;second++)
{
next=next with{ObservedAt=Now.AddSeconds(second)};
var decision=policy.Decide(next,Target,next.ObservedAt);
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist));
Check.Equal(.75,decision.Throttle);
}
}
}
[Test] public void LostDockingComputerUsesGrantedPadPathAndNeverRepeatsNativeHandoff()
{
foreach(var failure in new[]{NativeAssistState.Unavailable,NativeAssistState.Disabled,NativeAssistState.Failed})
{
var policy=new SystemFlightNavigator();var grant=new DockingClearance(77,DockingPermission.Granted,3,Now,Now.AddMinutes(5));
var frame=Frame with{Mode=LocalFlightMode.Normal,TargetDistance=7000,Clearance=grant,DockingAssist=NativeAssistState.Engaged};
Check.Equal(SystemNavigationPhase.NativeLanding,policy.Decide(frame,Target,Now).Phase);
for(var second=1;second<=35;second++)
{
var at=Now.AddSeconds(second);
frame=frame with{ObservedAt=at,DockingAssist=failure,DockingPath=new(77,3,1,[new(new(0,0,20),Vector3d.Zero,new(0,0,1),new(0,1,0),10,1,true,true)],at,15)};
var decision=policy.Decide(frame,Target,at);
Check.Equal(SystemNavigationPhase.ApplicationLanding,decision.Phase);
Check.That(decision.Translation is {Length:>0});
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.EnableDockingAssist));
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff));
}
}
}
[Test] public void UnavailableDockingHardwareStillRequiresAtcBeforeAnyLanding()
{
var frame=Frame with{Mode=LocalFlightMode.Normal,TargetDistance=7500,DockingAssist=NativeAssistState.Unavailable};
var request=new SystemFlightNavigator().Decide(frame,Target,Now);
Check.Equal(SystemNavigationPhase.RequestingDocking,request.Phase);Check.Equal(0d,request.Throttle);
Check.That(request.Actions.HasFlag(SystemNavigationAction.RequestDocking));Check.That(request.Translation is null);
}
[Test] public void LostDockingComputerBrakesWhileWaitingForTheRealPadPath()
{
var policy=new SystemFlightNavigator();
var frame=Frame with{Mode=LocalFlightMode.Normal,TargetDistance=7000,DockingAssist=NativeAssistState.Engaged,
Clearance=new(77,DockingPermission.Granted,3,Now,Now.AddMinutes(5)),Throttle=.8,Speed=60};
Check.Equal(SystemNavigationPhase.NativeLanding,policy.Decide(frame,Target,Now).Phase);
foreach(var state in new[]{NativeAssistState.Unavailable,NativeAssistState.Ready,NativeAssistState.Ready})
{
frame=frame with{ObservedAt=frame.ObservedAt.AddMilliseconds(80),DockingAssist=state};
var decision=policy.Decide(frame,Target,frame.ObservedAt);
Check.Equal(SystemNavigationPhase.Waiting,decision.Phase);Check.Equal(0d,decision.Throttle);
Check.Equal(Vector3d.Zero,decision.Translation);Check.That(decision.Actions.HasFlag(SystemNavigationAction.CancelOwnedAssists));
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.EnableDockingAssist));
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.ReleaseFlightAxes));
}
}
[Test] public void FailedHardwareDoesNotHideOriginalDutiesWhichStillNeedCancellation()
{
using var active=System.Text.Json.JsonDocument.Parse("{\"available\":false,\"enabled\":false,\"engaged\":true,\"matchesTarget\":true}");
using var inactive=System.Text.Json.JsonDocument.Parse("{\"available\":false,\"enabled\":false,\"engaged\":false,\"matchesTarget\":true}");
using var landing=System.Text.Json.JsonDocument.Parse("{\"computerAvailable\":false,\"nativeDutyActive\":true,\"nativeLaunchActive\":false}");
using var launch=System.Text.Json.JsonDocument.Parse("{\"nativeDutyActive\":true,\"nativeLaunchActive\":true}");
using var complete=System.Text.Json.JsonDocument.Parse("{\"nativeDutyActive\":false,\"nativeLaunchActive\":false}");
Check.That(NativeSystemFlightObservation.CruiseNeedsCancellation(active.RootElement));
Check.That(!NativeSystemFlightObservation.CruiseNeedsCancellation(inactive.RootElement));
Check.That(NativeSystemFlightObservation.DockingNeedsCancellation(landing.RootElement));
Check.That(!NativeSystemFlightObservation.DockingNeedsCancellation(launch.RootElement));
Check.That(!NativeSystemFlightObservation.DockingNeedsCancellation(complete.RootElement));
}
[Test] public void AssistPowerSwitchesDoNotAbortFallbackAsAChangedLoadout()
{
var ship=new XFEEDToolBox.Core.Models.ShipRecord{ShipId=7,Type="anaconda",UnladenMass=400,Modules=[
new(){Slot="Slot01",Item="int_supercruiseassist",Enabled=true,Priority=0,Health=1},
new(){Slot="Slot02",Item="int_dockingcomputer_advanced",Enabled=true,Priority=0,Health=1},
new(){Slot="MainEngines",Item="int_engine_size7_class5",Enabled=true,Priority=0,Health=1}]};
var original=NativeSystemFlightObservation.LoadoutFingerprint(ship);
var unavailable=ship with{Modules=ship.Modules.Select(m=>m.Item=="int_engine_size7_class5"?m:m with{Enabled=false,Priority=4,Health=0}).ToArray()};
Check.Equal(original,NativeSystemFlightObservation.LoadoutFingerprint(unavailable));
var replaced=ship with{Modules=ship.Modules.Select(m=>m.Slot=="Slot01"?m with{Item="int_cargorack_size1_class1"}:m).ToArray()};
Check.That(original!=NativeSystemFlightObservation.LoadoutFingerprint(replaced));
var enginesOff=ship with{Modules=ship.Modules.Select(m=>m.Slot=="MainEngines"?m with{Enabled=false}:m).ToArray()};
Check.That(original!=NativeSystemFlightObservation.LoadoutFingerprint(enginesOff));
}
}
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class AssistAvailabilityTests
{
private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-23T00:00:00Z");
private static readonly SystemNavigationTarget Target=new(1,101,"Station",SystemTargetKind.Station,77);
private static SystemFlightFrame Frame=>new(){Sequence=1,ObservedAt=Now,GameSha256=new('a',64),LoadoutFingerprint="ship",ShipEntity=1,SystemAddress=1,ReferenceWorld=101,
InGame=true,GeometryVerified=true,Mode=LocalFlightMode.Supercruise,SelectedTarget=Target,Forward=new(0,0,1),Up=new(0,1,0),TargetPosition=new(0,0,6e8),TargetDistance=6e8,
MaximumNormalSpeed=300,MaximumCruiseSpeed=1e10,CruiseParametersVerified=true,CruiseApproachSeconds=6,CruiseApproachThrottle=.75,
BrakingAcceleration=10,TranslationAcceleration=5,LegalApproachSpeed=100,AssistAlignmentDegrees=5,Assists=new(false,true,true)};
[Test] public void InstalledHardwareIsFilteredByLiveModuleAvailability()
{
var fitted=new NavigationAssists(true,true,true);
Check.Equal(new NavigationAssists(false,false,true),fitted.Available(false,true));
Check.Equal(new NavigationAssists(true,true,false),fitted.Available(true,false));
Check.Equal(new NavigationAssists(false,false,false),fitted.Available(false,false));
Check.Equal(new NavigationAssists(false,false,false),new NavigationAssists(false,false,false).Available(true,true));
}
[Test] public void StaleDutyFlagsCannotOverrideAnUnavailableModule()
{
Check.Equal(NativeAssistState.Unavailable,NativeSystemFlightObservation.CruiseAssist(false,true,true,true,true));
Check.Equal(NativeAssistState.Unavailable,NativeSystemFlightObservation.DockingAssist(false,true,true));
Check.Equal(NativeAssistState.Ready,NativeSystemFlightObservation.DockingAssist(true,false,false));
}
[Test] public void LostCruiseAssistImmediatelyResumesSoftwareApproachWithoutAnotherEnableRequest()
{
foreach(var failure in new[]{NativeAssistState.Unavailable,NativeAssistState.Disabled,NativeAssistState.Failed})
{
var policy=new SystemFlightNavigator();
var native=policy.Decide(Frame with{SupercruiseAssist=NativeAssistState.Engaged},Target,Now);
Check.That(native.Throttle is null&&native.Direction is null);
var next=Frame with{SupercruiseAssist=failure,ObservedAt=Now.AddSeconds(1)};
var fallback=policy.Decide(next,Target,next.ObservedAt);
Check.Equal(.75,fallback.Throttle);Check.That(fallback.Direction is not null);
Check.That(fallback.Actions.HasFlag(SystemNavigationAction.CancelOwnedAssists));
for(var second=2;second<=25;second++)
{
next=next with{ObservedAt=Now.AddSeconds(second)};
var decision=policy.Decide(next,Target,next.ObservedAt);
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.EnableSupercruiseAssist));
Check.Equal(.75,decision.Throttle);
}
}
}
[Test] public void LostDockingComputerUsesGrantedPadPathAndNeverRepeatsNativeHandoff()
{
foreach(var failure in new[]{NativeAssistState.Unavailable,NativeAssistState.Disabled,NativeAssistState.Failed})
{
var policy=new SystemFlightNavigator();var grant=new DockingClearance(77,DockingPermission.Granted,3,Now,Now.AddMinutes(5));
var frame=Frame with{Mode=LocalFlightMode.Normal,TargetDistance=7000,Clearance=grant,DockingAssist=NativeAssistState.Engaged};
Check.Equal(SystemNavigationPhase.NativeLanding,policy.Decide(frame,Target,Now).Phase);
for(var second=1;second<=35;second++)
{
var at=Now.AddSeconds(second);
frame=frame with{ObservedAt=at,DockingAssist=failure,DockingPath=new(77,3,1,[new(new(0,0,20),Vector3d.Zero,new(0,0,1),new(0,1,0),10,1,true,true)],at,15)};
var decision=policy.Decide(frame,Target,at);
Check.Equal(SystemNavigationPhase.ApplicationLanding,decision.Phase);
Check.That(decision.Translation is {Length:>0});
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.EnableDockingAssist));
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.ExitForFailedHandoff));
}
}
}
[Test] public void UnavailableDockingHardwareStillRequiresAtcBeforeAnyLanding()
{
var frame=Frame with{Mode=LocalFlightMode.Normal,TargetDistance=7500,DockingAssist=NativeAssistState.Unavailable};
var request=new SystemFlightNavigator().Decide(frame,Target,Now);
Check.Equal(SystemNavigationPhase.RequestingDocking,request.Phase);Check.Equal(0d,request.Throttle);
Check.That(request.Actions.HasFlag(SystemNavigationAction.RequestDocking));Check.That(request.Translation is null);
}
[Test] public void LostDockingComputerBrakesWhileWaitingForTheRealPadPath()
{
var policy=new SystemFlightNavigator();
var frame=Frame with{Mode=LocalFlightMode.Normal,TargetDistance=7000,DockingAssist=NativeAssistState.Engaged,
Clearance=new(77,DockingPermission.Granted,3,Now,Now.AddMinutes(5)),Throttle=.8,Speed=60};
Check.Equal(SystemNavigationPhase.NativeLanding,policy.Decide(frame,Target,Now).Phase);
foreach(var state in new[]{NativeAssistState.Unavailable,NativeAssistState.Ready,NativeAssistState.Ready})
{
frame=frame with{ObservedAt=frame.ObservedAt.AddMilliseconds(80),DockingAssist=state};
var decision=policy.Decide(frame,Target,frame.ObservedAt);
Check.Equal(SystemNavigationPhase.Waiting,decision.Phase);Check.Equal(0d,decision.Throttle);
Check.Equal(Vector3d.Zero,decision.Translation);Check.That(decision.Actions.HasFlag(SystemNavigationAction.CancelOwnedAssists));
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.EnableDockingAssist));
Check.That(!decision.Actions.HasFlag(SystemNavigationAction.ReleaseFlightAxes));
}
}
[Test] public void FailedHardwareDoesNotHideOriginalDutiesWhichStillNeedCancellation()
{
using var active=System.Text.Json.JsonDocument.Parse("{\"available\":false,\"enabled\":false,\"engaged\":true,\"matchesTarget\":true}");
using var inactive=System.Text.Json.JsonDocument.Parse("{\"available\":false,\"enabled\":false,\"engaged\":false,\"matchesTarget\":true}");
using var landing=System.Text.Json.JsonDocument.Parse("{\"computerAvailable\":false,\"nativeDutyActive\":true,\"nativeLaunchActive\":false}");
using var launch=System.Text.Json.JsonDocument.Parse("{\"nativeDutyActive\":true,\"nativeLaunchActive\":true}");
using var complete=System.Text.Json.JsonDocument.Parse("{\"nativeDutyActive\":false,\"nativeLaunchActive\":false}");
Check.That(NativeSystemFlightObservation.CruiseNeedsCancellation(active.RootElement));
Check.That(!NativeSystemFlightObservation.CruiseNeedsCancellation(inactive.RootElement));
Check.That(NativeSystemFlightObservation.DockingNeedsCancellation(landing.RootElement));
Check.That(!NativeSystemFlightObservation.DockingNeedsCancellation(launch.RootElement));
Check.That(!NativeSystemFlightObservation.DockingNeedsCancellation(complete.RootElement));
}
[Test] public void AssistPowerSwitchesDoNotAbortFallbackAsAChangedLoadout()
{
var ship=new XFEEDToolBox.Core.Models.ShipRecord{ShipId=7,Type="anaconda",UnladenMass=400,Modules=[
new(){Slot="Slot01",Item="int_supercruiseassist",Enabled=true,Priority=0,Health=1},
new(){Slot="Slot02",Item="int_dockingcomputer_advanced",Enabled=true,Priority=0,Health=1},
new(){Slot="MainEngines",Item="int_engine_size7_class5",Enabled=true,Priority=0,Health=1}]};
var original=NativeSystemFlightObservation.LoadoutFingerprint(ship);
var unavailable=ship with{Modules=ship.Modules.Select(m=>m.Item=="int_engine_size7_class5"?m:m with{Enabled=false,Priority=4,Health=0}).ToArray()};
Check.Equal(original,NativeSystemFlightObservation.LoadoutFingerprint(unavailable));
var replaced=ship with{Modules=ship.Modules.Select(m=>m.Slot=="Slot01"?m with{Item="int_cargorack_size1_class1"}:m).ToArray()};
Check.That(original!=NativeSystemFlightObservation.LoadoutFingerprint(replaced));
var enginesOff=ship with{Modules=ship.Modules.Select(m=>m.Slot=="MainEngines"?m with{Enabled=false}:m).ToArray()};
Check.That(original!=NativeSystemFlightObservation.LoadoutFingerprint(enginesOff));
}
}