using System;
using System.Linq;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class DockRecoveryTests
{
// Recorded positions from the stalled 11:06 save. Keep them in the test
// rather than depending on an untracked local save under obj/.
static readonly Vector3D SavedForward=new Vector3D(-.7583930492401123,.43053719401359558,.4893641471862793);
static readonly Vector3D SavedUp=new Vector3D(.62588357925415039,.27143645286560059,.73115783929824829);
static readonly Vector3D FourPort=new Vector3D(-67812.319486450288,-123701.75128268334,-162123.89634542784);
static readonly Vector3D FivePort=new Vector3D(-67799.8018148652,-123696.32255362603,-162109.27318864188);
static readonly Vector3D FourPosition=new Vector3D(-67923.280464637908,-123859.14376708004,-161997.76596521458);
static readonly Vector3D FourStart=new Vector3D(-67920.109540823731,-123850.31650989386,-162001.60530758521);
static readonly Vector3D FivePosition=new Vector3D(-67846.562285724911,-123647.61979939672,-162059.99176219723);
static readonly Vector3D OnePosition=new Vector3D(-67811.91664713982,-123698.84802260133,-162118.48642270884);
static readonly Vector3D TwoPosition=new Vector3D(-67845.112023729715,-123659.2111615825,-162077.86188009998);
static readonly Vector3D TwoWaitingPoint=new Vector3D(-67849.776998099245,-123665.0629307388,-162050.74675762336);
static void SeedLane(FleetStateTests.Rig rig,int miner,long dock,Vector3D position,Vector3D start,Vector3D forward,Vector3D up,string kind="DOCK_REQUEST")
{
rig.RegisterAll();
var saved=rig.Save();
saved.Set("fleet","LaneCount",1);
saved.Set("fleet","LaneOwner",rig.Miners[miner].Id);
saved.Set("fleet","LaneKind",kind);
saved.Set("fleet","LaneDock",dock);
saved.Set("fleet","LaneToken","obsolete-before-restart");
P.Data.PutVector(saved,"fleet","LanePosition",position);
P.Data.PutVector(saved,"fleet","LaneStart",start);
P.Data.PutVector(saved,"fleet","LaneForward",forward);
P.Data.PutVector(saved,"fleet","LaneUp",up);
saved.Set("fleet","LaneRadius",Math.Max(3,rig.Miners[miner].Radius));
saved.Set("fleet","LaneClearance",60);
rig.Fleet.Load(saved);
// Exercise the same record storage restoration used by live reloads.
rig.Restore(rig.Checkpoint());
}
static FleetStateTests.Rig BlockedReturn(P.FlightState state=P.FlightState.Holding,bool outside=true,string kind="DOCK_REQUEST",bool occupied=false)
{
var rig=new FleetStateTests.Rig(2,false);
rig.Miners[0].State=state;
rig.Miners[0].Position=new Vector3D(0,0,outside?-100:-15);
rig.Miners[1].Position=new Vector3D(0,0,-30);
rig.Miners[1].State=P.FlightState.Holding;
if(occupied)
{
// A different miner is physically connected to the original berth.
// A future approach line alone no longer prevents an inbound grant.
rig.SetConnected(0,true);rig.Miners[0].Connected=false;rig.Miners[1].Connected=true;
rig.Miners[1].ConnectorId=rig.Miners[0].ConnectorId;rig.Miners[0].ConnectorId=9999;
}
SeedLane(rig,0,100,Vector3D.Zero,rig.Miners[0].Position,Vector3D.Forward,Vector3D.Up,kind);
rig.RegisterAll();rig.Tick(30);rig.Sent.Clear();
return rig;
}
static void AssertOriginalLaneRetained(FleetStateTests.Rig rig,bool renewed=false)
{
var saved=rig.Save();
Assert.Equal(1,P.Data.ReadLong(saved,"fleet","LaneCount"));
Assert.Equal(rig.Miners[0].Id,saved.Get("fleet","LaneOwner").ToString());
Assert.Equal(100,P.Data.ReadLong(saved,"fleet","LaneDock"));
if(renewed)
{
Assert.NotEmpty(saved.Get("fleet","LaneToken").ToString());
var grant=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,grant.ConnectorId);Assert.Equal(saved.Get("fleet","LaneToken").ToString(),grant.Token);
}
else
{
Assert.Equal("",saved.Get("fleet","LaneToken").ToString());
Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT");
}
}
[Fact]
public void SavedFourCanUseItsFreeBerthBesideTheAlreadyDockedOne()
{
var rig=new FleetStateTests.Rig(3,false);
rig.SetPort(0,FourPort,SavedForward);
rig.SetPort(1,OnePosition,SavedForward);
rig.SetPort(2,FivePort,SavedForward);
var four=rig.Miners[0];four.Id="深空工业4号";four.Position=FourPosition;four.State=P.FlightState.Returning;four.Radius=4.1124754356677382;
var one=rig.Miners[1];one.Id="深空工业1号";one.Position=OnePosition;one.Radius=4.631461208568588;rig.SetConnected(1,true);
rig.Miners[2].Position=FivePort+SavedUp*1000;
SeedLane(rig,0,100,FourPort,FourStart,SavedForward,SavedUp);
rig.RegisterAll();rig.Tick(30);
Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT");
rig.Receive("DOCK_REQUEST",four);rig.Tick(12);
var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==four.Id).Body);
Assert.Equal(100,granted.ConnectorId);
Assert.NotEqual("obsolete-before-restart",granted.Token);
Assert.NotEmpty(granted.Token);
Assert.Equal("",granted.Problem);
Assert.True(one.Connected);
}
[Fact]
public void SavedFiveGetsItsBerthWithoutHoldingTwoForTheFutureApproachLine()
{
var rig=new FleetStateTests.Rig(1,false);
rig.SetPort(0,FivePort,SavedForward);
var five=rig.Miners[0];five.Id="深空工业5号";five.Position=FivePosition;five.State=P.FlightState.Holding;five.Radius=4.11247548414806;
five.HasRoute=true;five.RouteTarget=new Vector3D(-67846.136002352228,-123647.842939467,-162060.25410989788);
var two=new P.Telemetry {Id="深空工业2号",Position=TwoPosition,State=P.FlightState.Holding,Radius=4.1124763592894293,
HasRoute=true,RouteTarget=TwoWaitingPoint,Battery=1,Hydrogen=1,ThrustMargin=2.2698880858019943};
rig.Miners.Add(two);
SeedLane(rig,0,100,FivePort,FivePosition,SavedForward,SavedUp);
rig.RegisterAll();rig.Tick(30);
var initial=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==two.Id);
Assert.False(P.Data.Flag(initial.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(initial.Body,"traffic","Avoid"));
rig.Sent.Clear();
rig.Receive("DOCK_REQUEST",five);rig.Receive("TELEMETRY",two);rig.Tick(12);
var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==five.Id).Body);
Assert.Equal(100,granted.ConnectorId);Assert.NotEmpty(granted.Token);Assert.Equal("",granted.Problem);
var traffic=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==two.Id);
Assert.False(P.Data.Flag(traffic.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(traffic.Body,"traffic","Avoid"));
var waiting=P.DockFrame.FromIni(rig.Sent.Last(p=>p.Kind=="DOCKPOSE"&&p.Target==five.Id).Body);
Assert.Equal(granted.Token,waiting.Token);Assert.Equal("",waiting.Problem);
Assert.Equal(1,P.Data.ReadLong(rig.Save(),"fleet","LaneCount"));
Assert.Equal(TwoPosition,two.Position);
}
[Theory]
[InlineData(P.FlightState.Returning)]
[InlineData(P.FlightState.Holding)]
public void FreshReturningAndHoldingRequestsReuseAFreeBerthDespiteDistantShipsOnTheApproach(P.FlightState phase)
{
var rig=BlockedReturn(phase);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,granted.ConnectorId);
Assert.NotEmpty(granted.Token);
}
[Theory]
[InlineData(P.FlightState.DockAlign)]
[InlineData(P.FlightState.DockApproach)]
[InlineData(P.FlightState.Departing)]
[InlineData(P.FlightState.DockRetreat)]
public void ActiveOrRetreatingDockingPhaseRenewsItsOriginalFreeBerth(P.FlightState phase)
{
var rig=BlockedReturn(phase);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
AssertOriginalLaneRetained(rig,true);
}
[Fact]
public void NearPortHoldingMinerRenewsItsOriginalFreeBerth()
{
var rig=BlockedReturn(P.FlightState.Holding,false);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
AssertOriginalLaneRetained(rig,true);
}
[Theory]
[InlineData(-.1,false)]
[InlineData(0,false)]
[InlineData(.1,true)]
public void ReassignmentUsesTheAssignedConnectorEntryDistanceAndMinerMargin(double extra,bool canReassign)
{
var rig=BlockedReturn(occupied:true);
var entry=P.Data.ReadNumber(rig.Save(),"fleet","LaneApproachDistance",0);Assert.InRange(entry,30,40);
var distance=entry+rig.Miners[0].Radius+2+extra;
rig.Miners[0].Position=new Vector3D(0,0,-distance);
rig.Miners[1].Position=Vector3D.Zero;
rig.Receive("TELEMETRY",rig.Miners[1]);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
if(!canReassign)AssertOriginalLaneRetained(rig);
else Assert.Equal(101,P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body).ConnectorId);
}
[Fact]
public void LegacyLaneGeometryLoadsAsAConnectorAssignmentAndIsNotWrittenBack()
{
var rig=new FleetStateTests.Rig(1,false);
rig.Miners[0].State=P.FlightState.Holding;rig.Miners[0].Position=Vector3D.Forward*100;
SeedLane(rig,0,100,Vector3D.Zero,Vector3D.Forward*100,Vector3D.Forward,Vector3D.Up);
var saved=rig.Save();
Assert.Equal(100,P.Data.ReadLong(saved,"fleet","LaneDock"));Assert.Equal(rig.Miners[0].Id,P.Data.Text(saved,"fleet","LaneOwner"));
Assert.Equal(Vector3D.Zero,P.Data.ReadVector(saved,"fleet","LanePosition",Vector3D.One));
Assert.Equal(30,P.Data.ReadNumber(saved,"fleet","LaneApproachDistance",0));
Assert.Equal(60,P.Data.ReadNumber(saved,"fleet","LaneClearance",0));
foreach(var key in new[]{"LaneStart","LaneForward","LaneUp","LaneRadius"})Assert.False(saved.ContainsKey("fleet",key));
Assert.Empty(P.Data.Text(saved,"fleet","LaneToken"));
rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
var grant=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,grant.ConnectorId);Assert.Equal(30,grant.ApproachDistance);Assert.NotEmpty(grant.Token);
}
[Fact]
public void RegistrationAloneDoesNotAuthorizeReplacingRestoredOccupancy()
{
var rig=BlockedReturn();
rig.Receive("TELEMETRY",rig.Miners[0]);rig.Tick(12);
AssertOriginalLaneRetained(rig);
}
[Fact]
public void StaleTelemetryCannotReleaseAPreviouslyConsentedReservation()
{
var rig=BlockedReturn(P.FlightState.Holding,false,occupied:true);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);
rig.Miners[0].Position=new Vector3D(0,0,-100);
rig.Receive("TELEMETRY",rig.Miners[0]);
rig.Program.Now+=4;rig.Tick(12);
AssertOriginalLaneRetained(rig);
}
[Fact]
public void LostMinerReservationRemainsProtectedAfterReload()
{
var rig=BlockedReturn();
rig.Program.Now+=60;rig.Receive("TELEMETRY",rig.Miners[1]);rig.Tick(12);
AssertOriginalLaneRetained(rig);
}
[Fact]
public void ValidCurrentGrantIsNotReassignedByAnotherRequest()
{
var rig=new FleetStateTests.Rig(2,false);
rig.Miners[0].Position=new Vector3D(0,0,-100);rig.Miners[0].State=P.FlightState.Returning;
rig.Miners[1].Position=new Vector3D(1000,0,-30);
rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
var original=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,original.ConnectorId);
rig.Miners[1].Position=new Vector3D(0,0,-30);rig.Receive("TELEMETRY",rig.Miners[1]);rig.Sent.Clear();
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
Assert.All(rig.Sent.Where(p=>p.Kind=="DOCK_GRANT"),p=>
{
var repeated=P.DockFrame.FromIni(p.Body);
Assert.Equal(original.Token,repeated.Token);
Assert.Equal(original.ConnectorId,repeated.ConnectorId);
});
Assert.Equal(original.Token,rig.Save().Get("fleet","LaneToken").ToString());
}
[Fact]
public void OccupiedDockExplainsWhyAnInboundRequestIsWaiting()
{
var rig=new FleetStateTests.Rig(1,true);
var waiting=rig.Miners[0];waiting.Connected=false;waiting.ConnectorId=9999;
waiting.Position=new Vector3D(0,0,-100);waiting.State=P.FlightState.Holding;
rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",waiting);rig.Tick(12);
Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT");
var pose=P.DockFrame.FromIni(rig.Sent.Last(p=>p.Kind=="DOCKPOSE"&&p.Target==waiting.Id).Body);
Assert.Equal("",pose.Token);
Assert.Equal(P.L.FleetNoFreeDock,pose.Problem);
}
[Fact]
public void AnUnstableDockWindowDoesNotDelayAnInboundConnectorAssignment()
{
var rig=new FleetStateTests.Rig(1,false,"[Dock]\nStableSeconds=30\n");
var waiting=rig.Miners[0];waiting.Position=new Vector3D(0,0,-100);waiting.State=P.FlightState.Holding;
rig.RegisterAll();rig.Receive("DOCK_REQUEST",waiting);
var pose=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==waiting.Id).Body);
Assert.NotEmpty(pose.Token);
Assert.Equal("",pose.Problem);
Assert.Equal(0,rig.Program.Now);
}
}
}
using System;
using System.Linq;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class DockRecoveryTests
{
// Recorded positions from the stalled 11:06 save. Keep them in the test
// rather than depending on an untracked local save under obj/.
static readonly Vector3D SavedForward=new Vector3D(-.7583930492401123,.43053719401359558,.4893641471862793);
static readonly Vector3D SavedUp=new Vector3D(.62588357925415039,.27143645286560059,.73115783929824829);
static readonly Vector3D FourPort=new Vector3D(-67812.319486450288,-123701.75128268334,-162123.89634542784);
static readonly Vector3D FivePort=new Vector3D(-67799.8018148652,-123696.32255362603,-162109.27318864188);
static readonly Vector3D FourPosition=new Vector3D(-67923.280464637908,-123859.14376708004,-161997.76596521458);
static readonly Vector3D FourStart=new Vector3D(-67920.109540823731,-123850.31650989386,-162001.60530758521);
static readonly Vector3D FivePosition=new Vector3D(-67846.562285724911,-123647.61979939672,-162059.99176219723);
static readonly Vector3D OnePosition=new Vector3D(-67811.91664713982,-123698.84802260133,-162118.48642270884);
static readonly Vector3D TwoPosition=new Vector3D(-67845.112023729715,-123659.2111615825,-162077.86188009998);
static readonly Vector3D TwoWaitingPoint=new Vector3D(-67849.776998099245,-123665.0629307388,-162050.74675762336);
static void SeedLane(FleetStateTests.Rig rig,int miner,long dock,Vector3D position,Vector3D start,Vector3D forward,Vector3D up,string kind="DOCK_REQUEST")
{
rig.RegisterAll();
var saved=rig.Save();
saved.Set("fleet","LaneCount",1);
saved.Set("fleet","LaneOwner",rig.Miners[miner].Id);
saved.Set("fleet","LaneKind",kind);
saved.Set("fleet","LaneDock",dock);
saved.Set("fleet","LaneToken","obsolete-before-restart");
P.Data.PutVector(saved,"fleet","LanePosition",position);
P.Data.PutVector(saved,"fleet","LaneStart",start);
P.Data.PutVector(saved,"fleet","LaneForward",forward);
P.Data.PutVector(saved,"fleet","LaneUp",up);
saved.Set("fleet","LaneRadius",Math.Max(3,rig.Miners[miner].Radius));
saved.Set("fleet","LaneClearance",60);
rig.Fleet.Load(saved);
// Exercise the same record storage restoration used by live reloads.
rig.Restore(rig.Checkpoint());
}
static FleetStateTests.Rig BlockedReturn(P.FlightState state=P.FlightState.Holding,bool outside=true,string kind="DOCK_REQUEST",bool occupied=false)
{
var rig=new FleetStateTests.Rig(2,false);
rig.Miners[0].State=state;
rig.Miners[0].Position=new Vector3D(0,0,outside?-100:-15);
rig.Miners[1].Position=new Vector3D(0,0,-30);
rig.Miners[1].State=P.FlightState.Holding;
if(occupied)
{
// A different miner is physically connected to the original berth.
// A future approach line alone no longer prevents an inbound grant.
rig.SetConnected(0,true);rig.Miners[0].Connected=false;rig.Miners[1].Connected=true;
rig.Miners[1].ConnectorId=rig.Miners[0].ConnectorId;rig.Miners[0].ConnectorId=9999;
}
SeedLane(rig,0,100,Vector3D.Zero,rig.Miners[0].Position,Vector3D.Forward,Vector3D.Up,kind);
rig.RegisterAll();rig.Tick(30);rig.Sent.Clear();
return rig;
}
static void AssertOriginalLaneRetained(FleetStateTests.Rig rig,bool renewed=false)
{
var saved=rig.Save();
Assert.Equal(1,P.Data.ReadLong(saved,"fleet","LaneCount"));
Assert.Equal(rig.Miners[0].Id,saved.Get("fleet","LaneOwner").ToString());
Assert.Equal(100,P.Data.ReadLong(saved,"fleet","LaneDock"));
if(renewed)
{
Assert.NotEmpty(saved.Get("fleet","LaneToken").ToString());
var grant=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,grant.ConnectorId);Assert.Equal(saved.Get("fleet","LaneToken").ToString(),grant.Token);
}
else
{
Assert.Equal("",saved.Get("fleet","LaneToken").ToString());
Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT");
}
}
[Fact]
public void SavedFourCanUseItsFreeBerthBesideTheAlreadyDockedOne()
{
var rig=new FleetStateTests.Rig(3,false);
rig.SetPort(0,FourPort,SavedForward);
rig.SetPort(1,OnePosition,SavedForward);
rig.SetPort(2,FivePort,SavedForward);
var four=rig.Miners[0];four.Id="深空工业4号";four.Position=FourPosition;four.State=P.FlightState.Returning;four.Radius=4.1124754356677382;
var one=rig.Miners[1];one.Id="深空工业1号";one.Position=OnePosition;one.Radius=4.631461208568588;rig.SetConnected(1,true);
rig.Miners[2].Position=FivePort+SavedUp*1000;
SeedLane(rig,0,100,FourPort,FourStart,SavedForward,SavedUp);
rig.RegisterAll();rig.Tick(30);
Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT");
rig.Receive("DOCK_REQUEST",four);rig.Tick(12);
var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==four.Id).Body);
Assert.Equal(100,granted.ConnectorId);
Assert.NotEqual("obsolete-before-restart",granted.Token);
Assert.NotEmpty(granted.Token);
Assert.Equal("",granted.Problem);
Assert.True(one.Connected);
}
[Fact]
public void SavedFiveGetsItsBerthWithoutHoldingTwoForTheFutureApproachLine()
{
var rig=new FleetStateTests.Rig(1,false);
rig.SetPort(0,FivePort,SavedForward);
var five=rig.Miners[0];five.Id="深空工业5号";five.Position=FivePosition;five.State=P.FlightState.Holding;five.Radius=4.11247548414806;
five.HasRoute=true;five.RouteTarget=new Vector3D(-67846.136002352228,-123647.842939467,-162060.25410989788);
var two=new P.Telemetry {Id="深空工业2号",Position=TwoPosition,State=P.FlightState.Holding,Radius=4.1124763592894293,
HasRoute=true,RouteTarget=TwoWaitingPoint,Battery=1,Hydrogen=1,ThrustMargin=2.2698880858019943};
rig.Miners.Add(two);
SeedLane(rig,0,100,FivePort,FivePosition,SavedForward,SavedUp);
rig.RegisterAll();rig.Tick(30);
var initial=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==two.Id);
Assert.False(P.Data.Flag(initial.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(initial.Body,"traffic","Avoid"));
rig.Sent.Clear();
rig.Receive("DOCK_REQUEST",five);rig.Receive("TELEMETRY",two);rig.Tick(12);
var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==five.Id).Body);
Assert.Equal(100,granted.ConnectorId);Assert.NotEmpty(granted.Token);Assert.Equal("",granted.Problem);
var traffic=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==two.Id);
Assert.False(P.Data.Flag(traffic.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(traffic.Body,"traffic","Avoid"));
var waiting=P.DockFrame.FromIni(rig.Sent.Last(p=>p.Kind=="DOCKPOSE"&&p.Target==five.Id).Body);
Assert.Equal(granted.Token,waiting.Token);Assert.Equal("",waiting.Problem);
Assert.Equal(1,P.Data.ReadLong(rig.Save(),"fleet","LaneCount"));
Assert.Equal(TwoPosition,two.Position);
}
[Theory]
[InlineData(P.FlightState.Returning)]
[InlineData(P.FlightState.Holding)]
public void FreshReturningAndHoldingRequestsReuseAFreeBerthDespiteDistantShipsOnTheApproach(P.FlightState phase)
{
var rig=BlockedReturn(phase);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,granted.ConnectorId);
Assert.NotEmpty(granted.Token);
}
[Theory]
[InlineData(P.FlightState.DockAlign)]
[InlineData(P.FlightState.DockApproach)]
[InlineData(P.FlightState.Departing)]
[InlineData(P.FlightState.DockRetreat)]
public void ActiveOrRetreatingDockingPhaseRenewsItsOriginalFreeBerth(P.FlightState phase)
{
var rig=BlockedReturn(phase);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
AssertOriginalLaneRetained(rig,true);
}
[Fact]
public void NearPortHoldingMinerRenewsItsOriginalFreeBerth()
{
var rig=BlockedReturn(P.FlightState.Holding,false);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
AssertOriginalLaneRetained(rig,true);
}
[Theory]
[InlineData(-.1,false)]
[InlineData(0,false)]
[InlineData(.1,true)]
public void ReassignmentUsesTheAssignedConnectorEntryDistanceAndMinerMargin(double extra,bool canReassign)
{
var rig=BlockedReturn(occupied:true);
var entry=P.Data.ReadNumber(rig.Save(),"fleet","LaneApproachDistance",0);Assert.InRange(entry,30,40);
var distance=entry+rig.Miners[0].Radius+2+extra;
rig.Miners[0].Position=new Vector3D(0,0,-distance);
rig.Miners[1].Position=Vector3D.Zero;
rig.Receive("TELEMETRY",rig.Miners[1]);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
if(!canReassign)AssertOriginalLaneRetained(rig);
else Assert.Equal(101,P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body).ConnectorId);
}
[Fact]
public void LegacyLaneGeometryLoadsAsAConnectorAssignmentAndIsNotWrittenBack()
{
var rig=new FleetStateTests.Rig(1,false);
rig.Miners[0].State=P.FlightState.Holding;rig.Miners[0].Position=Vector3D.Forward*100;
SeedLane(rig,0,100,Vector3D.Zero,Vector3D.Forward*100,Vector3D.Forward,Vector3D.Up);
var saved=rig.Save();
Assert.Equal(100,P.Data.ReadLong(saved,"fleet","LaneDock"));Assert.Equal(rig.Miners[0].Id,P.Data.Text(saved,"fleet","LaneOwner"));
Assert.Equal(Vector3D.Zero,P.Data.ReadVector(saved,"fleet","LanePosition",Vector3D.One));
Assert.Equal(30,P.Data.ReadNumber(saved,"fleet","LaneApproachDistance",0));
Assert.Equal(60,P.Data.ReadNumber(saved,"fleet","LaneClearance",0));
foreach(var key in new[]{"LaneStart","LaneForward","LaneUp","LaneRadius"})Assert.False(saved.ContainsKey("fleet",key));
Assert.Empty(P.Data.Text(saved,"fleet","LaneToken"));
rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
var grant=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,grant.ConnectorId);Assert.Equal(30,grant.ApproachDistance);Assert.NotEmpty(grant.Token);
}
[Fact]
public void RegistrationAloneDoesNotAuthorizeReplacingRestoredOccupancy()
{
var rig=BlockedReturn();
rig.Receive("TELEMETRY",rig.Miners[0]);rig.Tick(12);
AssertOriginalLaneRetained(rig);
}
[Fact]
public void StaleTelemetryCannotReleaseAPreviouslyConsentedReservation()
{
var rig=BlockedReturn(P.FlightState.Holding,false,occupied:true);
rig.Receive("DOCK_REQUEST",rig.Miners[0]);
rig.Miners[0].Position=new Vector3D(0,0,-100);
rig.Receive("TELEMETRY",rig.Miners[0]);
rig.Program.Now+=4;rig.Tick(12);
AssertOriginalLaneRetained(rig);
}
[Fact]
public void LostMinerReservationRemainsProtectedAfterReload()
{
var rig=BlockedReturn();
rig.Program.Now+=60;rig.Receive("TELEMETRY",rig.Miners[1]);rig.Tick(12);
AssertOriginalLaneRetained(rig);
}
[Fact]
public void ValidCurrentGrantIsNotReassignedByAnotherRequest()
{
var rig=new FleetStateTests.Rig(2,false);
rig.Miners[0].Position=new Vector3D(0,0,-100);rig.Miners[0].State=P.FlightState.Returning;
rig.Miners[1].Position=new Vector3D(1000,0,-30);
rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
var original=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body);
Assert.Equal(100,original.ConnectorId);
rig.Miners[1].Position=new Vector3D(0,0,-30);rig.Receive("TELEMETRY",rig.Miners[1]);rig.Sent.Clear();
rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12);
Assert.All(rig.Sent.Where(p=>p.Kind=="DOCK_GRANT"),p=>
{
var repeated=P.DockFrame.FromIni(p.Body);
Assert.Equal(original.Token,repeated.Token);
Assert.Equal(original.ConnectorId,repeated.ConnectorId);
});
Assert.Equal(original.Token,rig.Save().Get("fleet","LaneToken").ToString());
}
[Fact]
public void OccupiedDockExplainsWhyAnInboundRequestIsWaiting()
{
var rig=new FleetStateTests.Rig(1,true);
var waiting=rig.Miners[0];waiting.Connected=false;waiting.ConnectorId=9999;
waiting.Position=new Vector3D(0,0,-100);waiting.State=P.FlightState.Holding;
rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",waiting);rig.Tick(12);
Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT");
var pose=P.DockFrame.FromIni(rig.Sent.Last(p=>p.Kind=="DOCKPOSE"&&p.Target==waiting.Id).Body);
Assert.Equal("",pose.Token);
Assert.Equal(P.L.FleetNoFreeDock,pose.Problem);
}
[Fact]
public void AnUnstableDockWindowDoesNotDelayAnInboundConnectorAssignment()
{
var rig=new FleetStateTests.Rig(1,false,"[Dock]\nStableSeconds=30\n");
var waiting=rig.Miners[0];waiting.Position=new Vector3D(0,0,-100);waiting.State=P.FlightState.Holding;
rig.RegisterAll();rig.Receive("DOCK_REQUEST",waiting);
var pose=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==waiting.Id).Body);
Assert.NotEmpty(pose.Token);
Assert.Equal("",pose.Problem);
Assert.Equal(0,rig.Program.Now);
}
}
}