using System;
using System.Linq;
using System.Reflection;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class FleetTrafficTests
{
static FleetStateTests.Rig Arrivals(int count)
{
var r=new FleetStateTests.Rig(count,false);
foreach(var t in r.Miners){t.State=P.FlightState.Holding;t.Position+=Vector3D.Forward*100;}
r.RegisterAll();r.Tick(30);return r;
}
static void FreshTicks(FleetStateTests.Rig r,int count)
{for(int n=0;n<count;n++){if(n%5==0)foreach(var t in r.Miners)r.Receive("TELEMETRY",t);r.Tick(1);}}
static FleetStateTests.Rig Departures(int count,string config="")
{
var r=new FleetStateTests.Rig(count,true,config);r.RegisterAll();r.Tick(30);r.Fleet.Command("start");
for(int n=0;n<count;n++)
{
r.Fleet.Command("mark 6 6 20 GPS:entry:"+(n*100)+":0:-1000: GPS:inward:"+(n*100)+":0:-1001:");
var job=r.Fleet.GetJobs().Last();r.Planner.SetOwner(job,r.Miners[n].Id);r.Miners[n].JobId=job.Id;
}
return r;
}
static void Schedule(FleetStateTests.Rig r)
{typeof(P.FleetController).GetMethod("ScheduleLane",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(r.Fleet,new object[0]);}
[Theory]
[InlineData(4)]
[InlineData(16)]
public void DifferentConnectorsReceiveUniqueStableEntryDistancesInOneDepartureSchedule(int count)
{
var r=Departures(count);foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grants=r.Sent.Where(p=>p.Kind=="DEPART_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(count,grants.Count);Assert.Equal(count,grants.Select(g=>g.ConnectorId).Distinct().Count());
Assert.Equal(Enumerable.Range(0,count).Select(i=>30d+i*10),grants.Select(g=>g.ApproachDistance).OrderBy(d=>d));
Assert.Equal(count,grants.Select(g=>g.Token).Distinct().Count());Assert.Equal(count,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void DepartureBatchDefersRemainingRequestsWhenTheFrameBudgetRunsOut()
{
var r=Departures(4);
var runtime=new Stub<IMyGridProgramRuntimeInfo>().Set("MaxInstructionCount",100000)
.Method("get_CurrentInstructionCount",a=>r.Sent.Any(p=>p.Kind=="DEPART_GRANT")?66000:0);
TestRig.SetBase(r.Program,"Runtime",runtime.Value);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);
Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT");
runtime.Set("CurrentInstructionCount",0);Schedule(r);Assert.Equal(4,r.Sent.Count(p=>p.Kind=="DEPART_GRANT"));
}
[Fact]
public void NearbyDifferentConnectorsCanBothBeAllocatedForDeparture()
{
var r=Departures(2);r.SetPort(1,Vector3D.Right*6,Vector3D.Forward);
r.Miners[0].Position=Vector3D.Left*3;r.Miners[1].Position=Vector3D.Right*9;FreshTicks(r,30);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
Assert.Equal(2,r.Sent.Count(p=>p.Kind=="DEPART_GRANT"));
}
[Fact]
public void DivergingConnectorsRetainTheirDirectionsAndCanDepartTogether()
{
var r=Departures(2);r.SetPort(0,Vector3D.Left*20,Vector3D.Left);r.SetPort(1,Vector3D.Right*20,Vector3D.Right);
r.Miners[0].Position=Vector3D.Left*20;r.Miners[1].Position=Vector3D.Right*20;FreshTicks(r,30);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grants=r.Sent.Where(p=>p.Kind=="DEPART_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(2,grants.Count);Assert.Equal(Vector3D.Left,grants.Single(g=>g.ConnectorId==100).Forward);Assert.Equal(Vector3D.Right,grants.Single(g=>g.ConnectorId==101).Forward);
}
[Fact]
public void CrossingFutureDepartureSegmentsDoNotBlockDifferentConnectorAssignments()
{
var r=Departures(2);var position=new Vector3D(30,0,-30);r.SetPort(1,position,Vector3D.Left);r.Miners[1].Position=position;FreshTicks(r,30);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grants=r.Sent.Where(p=>p.Kind=="DEPART_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(2,grants.Count);Assert.Equal(2,grants.Select(g=>g.ConnectorId).Distinct().Count());
Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void ADockedShipOnTheForwardLineDoesNotBlockAnotherConnectorDeparture()
{
var r=Departures(2);r.SetPort(1,Vector3D.Forward*30,Vector3D.Forward);r.Miners[1].Position=Vector3D.Forward*30;FreshTicks(r,30);
r.Receive("DEPART_REQUEST",r.Miners[0]);Schedule(r);Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT" && p.Target==r.Miners[0].Id);
r.Receive("DEPART_REQUEST",r.Miners[1]);Schedule(r);Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT" && p.Target==r.Miners[1].Id);
Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void ADepartingMinerKeepsItsConnectorExclusiveUntilItConfirmsDepartureComplete()
{
var r=Departures(1);var owner=r.Miners[0];r.Receive("DEPART_REQUEST",owner);Schedule(r);
var departure=P.DockFrame.FromIni(Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT").Body);
r.SetConnected(0,false);owner.State=P.FlightState.Departing;owner.Position=Vector3D.Forward*100;r.Receive("TELEMETRY",owner);
var waiting=new P.Telemetry {Id="returning-miner",State=P.FlightState.Holding,Position=Vector3D.Forward*80,Radius=3,Battery=1,Hydrogen=1};
r.Miners.Add(waiting);r.Receive("HELLO",waiting);r.Receive("DOCK_REQUEST",waiting);FreshTicks(r,12);
Assert.DoesNotContain(r.Sent,p=>p.Kind=="DOCK_GRANT");Assert.Equal(owner.Id,P.Data.Text(r.Save(),"fleet","LaneOwner"));
owner.State=P.FlightState.Transit;r.Receive("TELEMETRY",owner);FreshTicks(r,12);
var arrival=P.DockFrame.FromIni(Assert.Single(r.Sent,p=>p.Kind=="DOCK_GRANT" && p.Target==waiting.Id).Body);
Assert.Equal(departure.ConnectorId,arrival.ConnectorId);Assert.NotEqual(departure.Token,arrival.Token);
Assert.Equal(1,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void AssignedEntryAndDepartureDistancesSurviveWirePosesAndCompactStorageReload()
{
var r=Departures(2,"[Dock]\nLaneClearance=300\n");foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grant=P.DockFrame.FromIni(r.Sent.Single(p=>p.Kind=="DEPART_GRANT" && p.Target==r.Miners[1].Id).Body);
Assert.InRange(grant.ApproachDistance,30,40);Assert.Equal(300+grant.ApproachDistance-30,grant.LaneClearance);
FreshTicks(r,10);Assert.All(r.Sent.Where(p=>p.Kind=="DOCKPOSE" && p.Target==r.Miners[1].Id && P.DockFrame.FromIni(p.Body).Token.Length>0),p=>
{var pose=P.DockFrame.FromIni(p.Body);Assert.Equal(grant.LaneClearance,pose.LaneClearance);Assert.Equal(grant.ApproachDistance,pose.ApproachDistance);});
var after=new FleetStateTests.Rig(2,false);after.Fleet.Load(r.Save());var saved=after.Save();
Assert.Equal(grant.LaneClearance,P.Data.ReadNumber(saved,"fleetLane1","LaneClearance",0));
Assert.Equal(grant.ApproachDistance,P.Data.ReadNumber(saved,"fleetLane1","LaneApproachDistance",0));
Assert.Empty(P.Data.Text(saved,"fleetLane1","LaneToken"));
foreach(var key in new[]{"LaneStart","LaneForward","LaneUp","LaneRadius"})Assert.False(saved.ContainsKey("fleetLane1",key));
}
[Theory]
[InlineData(4)]
[InlineData(16)]
public void IndependentParallelBerthsCanRemainGrantedAtTheSameTime(int count)
{
var r=Arrivals(count);foreach(var t in r.Miners)r.Receive("DOCK_REQUEST",t);FreshTicks(r,count*12);
var grants=r.Sent.Where(p=>p.Kind=="DOCK_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(count,grants.Count);Assert.Equal(count,grants.Select(g=>g.ConnectorId).Distinct().Count());
Assert.Equal(Enumerable.Range(0,count).Select(i=>30d+i*10),grants.Select(g=>g.ApproachDistance).OrderBy(d=>d));
Assert.Equal(count,grants.Select(g=>g.Token).Distinct().Count());Assert.Equal(count,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void AdjacentArrivalBerthsGrantTogetherAndDoNotExpireTheirUnknownOccupants()
{
var r=Arrivals(2);r.SetPort(1,Vector3D.Right*4,Vector3D.Forward);r.Miners[1].Position=new Vector3D(4,0,-130);
FreshTicks(r,30);foreach(var t in r.Miners)r.Receive("DOCK_REQUEST",t);FreshTicks(r,30);
var grants=r.Sent.Where(p=>p.Kind=="DOCK_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(2,grants.Count);Assert.Equal(2,grants.Select(g=>g.ConnectorId).Distinct().Count());
r.Tick(1000);r.Sent.Clear();
var waiting=new P.Telemetry {Id="waiting-miner",State=P.FlightState.Holding,Position=new Vector3D(0,0,-160),Radius=3,Battery=1,Hydrogen=1};
r.Miners.Add(waiting);r.Receive("HELLO",waiting);r.Receive("DOCK_REQUEST",waiting);r.Tick(20);
Assert.DoesNotContain(r.Sent,p=>p.Kind=="DOCK_GRANT");Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void ArrivalAndDepartureOnDifferentConnectorsRunTogether()
{
var r=new FleetStateTests.Rig(2,true);r.SetConnected(1,false);r.Miners[1].Position=new Vector3D(40,0,-100);r.Miners[1].State=P.FlightState.Holding;
r.RegisterAll();r.Fleet.Command("mark 6 6 20 GPS:entry:0:0:-200: GPS:inward:0:0:-201:");r.Fleet.Command("start");FreshTicks(r,30);
r.Miners[0].JobId=r.Fleet.GetJobs().Single().Id;r.Receive("DEPART_REQUEST",r.Miners[0]);r.Receive("DOCK_REQUEST",r.Miners[1]);FreshTicks(r,30);
Assert.Single(r.Sent,p=>p.Kind=="DOCK_GRANT");Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT");Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void StoppedFaultAwayFromThePortReleasesItsConnectorAndItsRemoteTask()
{
var r=Arrivals(2);r.Receive("DOCK_REQUEST",r.Miners[0]);FreshTicks(r,15);
r.Fleet.Command("mark 6 6 20 GPS:entry:0:0:-300: GPS:inward:0:0:-301:");var job=r.Fleet.GetJobs().Single();r.Planner.SetOwner(job,r.Miners[0].Id);
r.Miners[0].State=P.FlightState.Fault;r.Miners[0].Position=new Vector3D(-50,0,-100);r.Receive("TELEMETRY",r.Miners[0]);
r.Receive("DOCK_REQUEST",r.Miners[1]);FreshTicks(r,20);
Assert.Equal("",job.Owner);Assert.Equal(1,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
Assert.Equal(r.Miners[1].Id,P.Data.Text(r.Save(),"fleet","LaneOwner"));Assert.Contains(r.Sent,p=>p.Kind=="DOCK_GRANT" && p.Target==r.Miners[1].Id);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void FaultAtAContactOnlyReservesItsAssignedConnector(bool stale)
{
var r=Arrivals(2);r.Receive("DOCK_REQUEST",r.Miners[0]);FreshTicks(r,15);
r.Miners[0].State=P.FlightState.Fault;r.Miners[0].Position=Vector3D.Forward*20;r.Receive("TELEMETRY",r.Miners[0]);
if(stale)r.Program.Now+=10;else r.Tick(12);
r.Receive("DOCK_REQUEST",r.Miners[1]);r.Tick(12);
var second=Assert.Single(r.Sent,p=>p.Kind=="DOCK_GRANT" && p.Target==r.Miners[1].Id);
Assert.Equal(101,P.DockFrame.FromIni(second.Body).ConnectorId);
Assert.Equal(stale?2:1,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void DockRetreatReleaseAcceptsAStoppedShipBeforeTheFarWaitingPoint()
{
var r=Arrivals(1);r.Receive("DOCK_REQUEST",r.Miners[0]);FreshTicks(r,15);var grant=P.DockFrame.FromIni(r.Sent.First(p=>p.Kind=="DOCK_GRANT").Body);
r.Miners[0].Position=Vector3D.Forward*35;r.Miners[0].State=P.FlightState.DockRetreat;r.Receive("TELEMETRY",r.Miners[0]);
var body=new MyIni();body.Set("lease","Token",grant.Token);r.Fleet.Receive(new P.Packet {Kind="LANE_RELEASE",From=r.Miners[0].Id,Source=5000,Body=body});
Assert.Equal(0,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void MultipleUnknownConnectorAssignmentsSurviveRemovalAndReloadWithTokensRevoked()
{
var r=Arrivals(2);r.Checkpoint();foreach(var t in r.Miners)r.Receive("DOCK_REQUEST",t);FreshTicks(r,30);Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
r.Program.Now+=10;r.Fleet.Command("clear-offline");var after=new FleetStateTests.Rig(2,false);after.Restore(r.Program.Storage);
Assert.Empty(after.Fleet.GetTelemetry());var saved=after.Save();Assert.Equal(2,P.Data.ReadLong(saved,"fleet","LaneCount"));
Assert.Empty(P.Data.Text(saved,"fleet","LaneToken"));Assert.Empty(P.Data.Text(saved,"fleetLane1","LaneToken"));
Assert.Equal(2,P.Data.ReadLong(saved,"fleet","ProtectedOwnerCount"));
}
static P.Packet Traffic(FleetStateTests.Rig r,int index)
{
typeof(P.FleetController).GetMethod("SendTraffic",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(r.Fleet,new object[]{r.Fleet.GetTelemetry().Single(t=>t.Id==r.Miners[index].Id)});
return r.Sent.Last(p=>p.Kind=="TRAFFIC" && p.Target==r.Miners[index].Id);
}
[Fact]
public void CruiseShipsIgnoreDistantCrossingsAndKeepOneYieldChoiceForANearEncounter()
{
var r=Arrivals(2);var a=r.Miners[0];var b=r.Miners[1];a.State=b.State=P.FlightState.Transit;a.HasRoute=b.HasRoute=true;
a.Position=Vector3D.Left*30;a.RouteTarget=Vector3D.Right*30;a.Velocity=Vector3D.Right*3;
b.Position=Vector3D.Forward*30;b.RouteTarget=Vector3D.Backward*30;b.Velocity=Vector3D.Backward*3;
a.ThrustMargin=b.ThrustMargin=2;r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);
for(int i=0;i<2;i++)
{
var clear=Traffic(r,i).Body;Assert.False(P.Data.Flag(clear,"traffic","Wait"));Assert.False(P.Data.Flag(clear,"traffic","Avoid"));
}
a.Position=Vector3D.Left*10;b.Position=Vector3D.Forward*10;r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);
var first=new[]{Traffic(r,0).Body,Traffic(r,1).Body};
Assert.Single(first,p=>P.Data.Flag(p,"traffic","Avoid"));Assert.Single(first,p=>P.Data.Flag(p,"traffic","Wait"));
r.Program.Now+=.5;r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);
for(int i=0;i<2;i++)
{
var repeated=Traffic(r,i).Body;
Assert.Equal(P.Data.Flag(first[i],"traffic","Wait"),P.Data.Flag(repeated,"traffic","Wait"));
Assert.Equal(P.Data.Flag(first[i],"traffic","Avoid"),P.Data.Flag(repeated,"traffic","Avoid"));
Assert.Equal(P.Data.ReadVector(first[i],"traffic","Point",Vector3D.Zero),P.Data.ReadVector(repeated,"traffic","Point",Vector3D.Zero));
}
}
[Fact]
public void NearbyStationaryFaultCreatesACameraCheckedDetour()
{
var r=Arrivals(2);var a=r.Miners[0];var b=r.Miners[1];a.State=P.FlightState.Transit;a.HasRoute=true;
a.Position=Vector3D.Forward*100;a.RouteTarget=Vector3D.Zero;a.Velocity=Vector3D.Backward*2;a.ThrustMargin=2;b.Position=Vector3D.Forward*94;b.State=P.FlightState.Fault;
r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);var body=Traffic(r,0).Body;
Assert.True(P.Data.Flag(body,"traffic","Avoid"));Assert.False(P.Data.Flag(body,"traffic","Wait"));
var point=P.Data.ReadVector(body,"traffic","Point",Vector3D.Zero);Assert.InRange(Vector3D.Distance(point,a.Position),1.9,2.01);
Assert.True(Vector3D.Distance(point,b.Position)>Vector3D.Distance(a.Position,b.Position));
}
[Fact]
public void NearbyStaleFaultPositionRequiresWaitingInsteadOfAnUnverifiedDetour()
{
var r=Arrivals(2);var a=r.Miners[0];var b=r.Miners[1];a.State=P.FlightState.Transit;a.HasRoute=true;
a.Position=Vector3D.Forward*100;a.RouteTarget=Vector3D.Zero;a.Velocity=Vector3D.Backward*2;b.Position=Vector3D.Forward*94;b.State=P.FlightState.Fault;r.Receive("TELEMETRY",b);
r.Program.Now+=5;r.Receive("TELEMETRY",a);var body=Traffic(r,0).Body;Assert.True(P.Data.Flag(body,"traffic","Wait"));Assert.False(P.Data.Flag(body,"traffic","Avoid"));
}
[Fact]
public void AStoppedFaultStillProtectsItsPointAfterOfflineRegistryRemoval()
{
var r=Arrivals(2);r.Checkpoint();r.Miners[0].State=P.FlightState.Fault;r.Miners[0].Position=Vector3D.Forward*20;
r.Receive("TELEMETRY",r.Miners[0]);r.Tick(12);r.Program.Now+=10;r.Receive("TELEMETRY",r.Miners[1]);r.Fleet.Command("clear-offline");
var after=new FleetStateTests.Rig(2,false);after.Restore(r.Program.Storage);
Assert.Equal(1,P.Data.ReadLong(after.Save(),"fleet","ClearanceCount"));Assert.Equal(1,P.Data.ReadLong(after.Save(),"fleet","ProtectedOwnerCount"));
}
[Fact]
public void FrontShipDoesNotYieldToItsFollower()
{
var r=Arrivals(2);var rear=r.Miners[0];var front=r.Miners[1];rear.HasRoute=front.HasRoute=true;rear.State=front.State=P.FlightState.Transit;
rear.Position=Vector3D.Forward*80;front.Position=Vector3D.Forward*60;rear.RouteTarget=front.RouteTarget=Vector3D.Zero;
rear.Velocity=front.Velocity=Vector3D.Backward*3;r.Receive("TELEMETRY",rear);r.Receive("TELEMETRY",front);
Assert.False(P.Data.Flag(Traffic(r,1).Body,"traffic","Wait"));
}
sealed class MovingShip
{
public readonly ReturnNavigationTests.Rig R=new ReturnNavigationTests.Rig();
public Vector3D Position,Velocity,Target;
public bool Arrived;
public MovingShip(Vector3D start,Vector3D target)
{
Position=start;Target=target;R.Orientation=MatrixD.CreateWorld(start,Vector3D.Normalize(target-start),Vector3D.Up);
R.Controller.Method("GetPosition",a=>Position).Method("GetShipVelocities",a=>new MyShipVelocities(Velocity,Vector3D.Zero));
R.Camera.Method("GetPosition",a=>Position).Method("CanScan",a=>true);
}
public void Step(double now)
{
R.Program.Now=now;
bool avoidance=R.Flight.TrafficAvoid && now-R.Flight.TrafficAt<1.5;
if(R.Flight.TrafficWait && now-R.Flight.TrafficAt<1.5){R.Flight.Hold(.05);Arrived=false;}
else
{
var target=avoidance?R.Flight.TrafficPoint:Target;
Arrived=R.Flight.Move(target,Vector3D.Zero,P.Data.Unit(target-Position,Vector3D.Forward),Vector3D.Up,avoidance?1.5:5,avoidance?.3:.5,true,.05,true,!avoidance) && !avoidance;
}
Vector3D force=Vector3D.Zero;foreach(var thrust in R.Ship.Thrusters)force+=thrust.WorldMatrix.Backward*thrust.ThrustOverridePercentage*thrust.MaxEffectiveThrust;
Velocity+=force/1000*.05;Position+=Velocity*.05;
var desired=(MatrixD)typeof(P.FlightController).GetField("previousDesired",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(R.Flight);
R.Orientation=MatrixD.CreateWorld(Position,desired.Forward,desired.Up);
}
}
[Theory]
[InlineData(0)]
[InlineData(1)]
public void CrossingShipsBothReachTheirGoalsWithPhysicalSeparationInTheControlModel(int seed)
{
var r=Arrivals(2);var ships=new[]{new MovingShip(Vector3D.Left*40,Vector3D.Right*40),new MovingShip(Vector3D.Forward*40,Vector3D.Backward*40)};
typeof(P.FleetController).GetField("trafficRandom",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(r.Fleet,new Random(seed));
double separation=double.MaxValue;bool yielded=false;
for(int n=0;n<2000 && ships.Any(s=>!s.Arrived);n++)
{
r.Program.Now+=.05;
if(n%10==0)
{
for(int i=0;i<2;i++)
{
var t=r.Miners[i];var s=ships[i];t.State=P.FlightState.Transit;t.Position=s.Position;t.Velocity=s.Velocity;t.Radius=2;t.ThrustMargin=100;
t.HasRoute=!s.Arrived;t.RouteTarget=s.R.Flight.HasRoute?s.R.Flight.RouteTarget:s.Target;r.Receive("TELEMETRY",t);
}
for(int i=0;i<2;i++)if(!ships[i].Arrived)
{
var body=Traffic(r,i).Body;var f=ships[i].R.Flight;f.TrafficWait=P.Data.Flag(body,"traffic","Wait");f.TrafficAvoid=P.Data.Flag(body,"traffic","Avoid");
f.TrafficPoint=P.Data.ReadVector(body,"traffic","Point",Vector3D.Zero);f.TrafficAt=r.Program.Now;yielded|=f.TrafficWait;
}
}
foreach(var s in ships)s.Step(r.Program.Now);
separation=Math.Min(separation,Vector3D.Distance(ships[0].Position,ships[1].Position));
}
Assert.True(yielded,"No close encounter was exercised.");Assert.True(ships.All(s=>s.Arrived),string.Join(" / ",ships.Select(s=>s.Position+" -> "+s.Target+" / "+s.R.Flight.Problem)));Assert.True(separation>=5,"Minimum separation: "+separation);
}
[Fact]
public void FinalDockingDoesNotUseAnOldTrafficWaitOrDetour()
{
var r=new ReturnNavigationTests.Rig();r.Flight.TrafficWait=r.Flight.TrafficAvoid=true;r.Flight.TrafficPoint=Vector3D.Right*100;r.Flight.TrafficAt=0;
r.Flight.Move(Vector3D.Forward*3,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,.2,.03,false,.05,false,false);
Assert.True(r.Flight.CommandSpeed>0);Assert.Equal(0,r.Rays);Assert.Equal(Vector3D.Forward*3,r.Flight.RouteTarget);
}
}
}
using System;
using System.Linq;
using System.Reflection;
using Sandbox.ModAPI.Ingame;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class FleetTrafficTests
{
static FleetStateTests.Rig Arrivals(int count)
{
var r=new FleetStateTests.Rig(count,false);
foreach(var t in r.Miners){t.State=P.FlightState.Holding;t.Position+=Vector3D.Forward*100;}
r.RegisterAll();r.Tick(30);return r;
}
static void FreshTicks(FleetStateTests.Rig r,int count)
{for(int n=0;n<count;n++){if(n%5==0)foreach(var t in r.Miners)r.Receive("TELEMETRY",t);r.Tick(1);}}
static FleetStateTests.Rig Departures(int count,string config="")
{
var r=new FleetStateTests.Rig(count,true,config);r.RegisterAll();r.Tick(30);r.Fleet.Command("start");
for(int n=0;n<count;n++)
{
r.Fleet.Command("mark 6 6 20 GPS:entry:"+(n*100)+":0:-1000: GPS:inward:"+(n*100)+":0:-1001:");
var job=r.Fleet.GetJobs().Last();r.Planner.SetOwner(job,r.Miners[n].Id);r.Miners[n].JobId=job.Id;
}
return r;
}
static void Schedule(FleetStateTests.Rig r)
{typeof(P.FleetController).GetMethod("ScheduleLane",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(r.Fleet,new object[0]);}
[Theory]
[InlineData(4)]
[InlineData(16)]
public void DifferentConnectorsReceiveUniqueStableEntryDistancesInOneDepartureSchedule(int count)
{
var r=Departures(count);foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grants=r.Sent.Where(p=>p.Kind=="DEPART_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(count,grants.Count);Assert.Equal(count,grants.Select(g=>g.ConnectorId).Distinct().Count());
Assert.Equal(Enumerable.Range(0,count).Select(i=>30d+i*10),grants.Select(g=>g.ApproachDistance).OrderBy(d=>d));
Assert.Equal(count,grants.Select(g=>g.Token).Distinct().Count());Assert.Equal(count,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void DepartureBatchDefersRemainingRequestsWhenTheFrameBudgetRunsOut()
{
var r=Departures(4);
var runtime=new Stub<IMyGridProgramRuntimeInfo>().Set("MaxInstructionCount",100000)
.Method("get_CurrentInstructionCount",a=>r.Sent.Any(p=>p.Kind=="DEPART_GRANT")?66000:0);
TestRig.SetBase(r.Program,"Runtime",runtime.Value);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);
Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT");
runtime.Set("CurrentInstructionCount",0);Schedule(r);Assert.Equal(4,r.Sent.Count(p=>p.Kind=="DEPART_GRANT"));
}
[Fact]
public void NearbyDifferentConnectorsCanBothBeAllocatedForDeparture()
{
var r=Departures(2);r.SetPort(1,Vector3D.Right*6,Vector3D.Forward);
r.Miners[0].Position=Vector3D.Left*3;r.Miners[1].Position=Vector3D.Right*9;FreshTicks(r,30);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
Assert.Equal(2,r.Sent.Count(p=>p.Kind=="DEPART_GRANT"));
}
[Fact]
public void DivergingConnectorsRetainTheirDirectionsAndCanDepartTogether()
{
var r=Departures(2);r.SetPort(0,Vector3D.Left*20,Vector3D.Left);r.SetPort(1,Vector3D.Right*20,Vector3D.Right);
r.Miners[0].Position=Vector3D.Left*20;r.Miners[1].Position=Vector3D.Right*20;FreshTicks(r,30);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grants=r.Sent.Where(p=>p.Kind=="DEPART_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(2,grants.Count);Assert.Equal(Vector3D.Left,grants.Single(g=>g.ConnectorId==100).Forward);Assert.Equal(Vector3D.Right,grants.Single(g=>g.ConnectorId==101).Forward);
}
[Fact]
public void CrossingFutureDepartureSegmentsDoNotBlockDifferentConnectorAssignments()
{
var r=Departures(2);var position=new Vector3D(30,0,-30);r.SetPort(1,position,Vector3D.Left);r.Miners[1].Position=position;FreshTicks(r,30);
foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grants=r.Sent.Where(p=>p.Kind=="DEPART_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(2,grants.Count);Assert.Equal(2,grants.Select(g=>g.ConnectorId).Distinct().Count());
Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void ADockedShipOnTheForwardLineDoesNotBlockAnotherConnectorDeparture()
{
var r=Departures(2);r.SetPort(1,Vector3D.Forward*30,Vector3D.Forward);r.Miners[1].Position=Vector3D.Forward*30;FreshTicks(r,30);
r.Receive("DEPART_REQUEST",r.Miners[0]);Schedule(r);Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT" && p.Target==r.Miners[0].Id);
r.Receive("DEPART_REQUEST",r.Miners[1]);Schedule(r);Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT" && p.Target==r.Miners[1].Id);
Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void ADepartingMinerKeepsItsConnectorExclusiveUntilItConfirmsDepartureComplete()
{
var r=Departures(1);var owner=r.Miners[0];r.Receive("DEPART_REQUEST",owner);Schedule(r);
var departure=P.DockFrame.FromIni(Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT").Body);
r.SetConnected(0,false);owner.State=P.FlightState.Departing;owner.Position=Vector3D.Forward*100;r.Receive("TELEMETRY",owner);
var waiting=new P.Telemetry {Id="returning-miner",State=P.FlightState.Holding,Position=Vector3D.Forward*80,Radius=3,Battery=1,Hydrogen=1};
r.Miners.Add(waiting);r.Receive("HELLO",waiting);r.Receive("DOCK_REQUEST",waiting);FreshTicks(r,12);
Assert.DoesNotContain(r.Sent,p=>p.Kind=="DOCK_GRANT");Assert.Equal(owner.Id,P.Data.Text(r.Save(),"fleet","LaneOwner"));
owner.State=P.FlightState.Transit;r.Receive("TELEMETRY",owner);FreshTicks(r,12);
var arrival=P.DockFrame.FromIni(Assert.Single(r.Sent,p=>p.Kind=="DOCK_GRANT" && p.Target==waiting.Id).Body);
Assert.Equal(departure.ConnectorId,arrival.ConnectorId);Assert.NotEqual(departure.Token,arrival.Token);
Assert.Equal(1,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void AssignedEntryAndDepartureDistancesSurviveWirePosesAndCompactStorageReload()
{
var r=Departures(2,"[Dock]\nLaneClearance=300\n");foreach(var t in r.Miners)r.Receive("DEPART_REQUEST",t);Schedule(r);
var grant=P.DockFrame.FromIni(r.Sent.Single(p=>p.Kind=="DEPART_GRANT" && p.Target==r.Miners[1].Id).Body);
Assert.InRange(grant.ApproachDistance,30,40);Assert.Equal(300+grant.ApproachDistance-30,grant.LaneClearance);
FreshTicks(r,10);Assert.All(r.Sent.Where(p=>p.Kind=="DOCKPOSE" && p.Target==r.Miners[1].Id && P.DockFrame.FromIni(p.Body).Token.Length>0),p=>
{var pose=P.DockFrame.FromIni(p.Body);Assert.Equal(grant.LaneClearance,pose.LaneClearance);Assert.Equal(grant.ApproachDistance,pose.ApproachDistance);});
var after=new FleetStateTests.Rig(2,false);after.Fleet.Load(r.Save());var saved=after.Save();
Assert.Equal(grant.LaneClearance,P.Data.ReadNumber(saved,"fleetLane1","LaneClearance",0));
Assert.Equal(grant.ApproachDistance,P.Data.ReadNumber(saved,"fleetLane1","LaneApproachDistance",0));
Assert.Empty(P.Data.Text(saved,"fleetLane1","LaneToken"));
foreach(var key in new[]{"LaneStart","LaneForward","LaneUp","LaneRadius"})Assert.False(saved.ContainsKey("fleetLane1",key));
}
[Theory]
[InlineData(4)]
[InlineData(16)]
public void IndependentParallelBerthsCanRemainGrantedAtTheSameTime(int count)
{
var r=Arrivals(count);foreach(var t in r.Miners)r.Receive("DOCK_REQUEST",t);FreshTicks(r,count*12);
var grants=r.Sent.Where(p=>p.Kind=="DOCK_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(count,grants.Count);Assert.Equal(count,grants.Select(g=>g.ConnectorId).Distinct().Count());
Assert.Equal(Enumerable.Range(0,count).Select(i=>30d+i*10),grants.Select(g=>g.ApproachDistance).OrderBy(d=>d));
Assert.Equal(count,grants.Select(g=>g.Token).Distinct().Count());Assert.Equal(count,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void AdjacentArrivalBerthsGrantTogetherAndDoNotExpireTheirUnknownOccupants()
{
var r=Arrivals(2);r.SetPort(1,Vector3D.Right*4,Vector3D.Forward);r.Miners[1].Position=new Vector3D(4,0,-130);
FreshTicks(r,30);foreach(var t in r.Miners)r.Receive("DOCK_REQUEST",t);FreshTicks(r,30);
var grants=r.Sent.Where(p=>p.Kind=="DOCK_GRANT").Select(p=>P.DockFrame.FromIni(p.Body)).ToList();
Assert.Equal(2,grants.Count);Assert.Equal(2,grants.Select(g=>g.ConnectorId).Distinct().Count());
r.Tick(1000);r.Sent.Clear();
var waiting=new P.Telemetry {Id="waiting-miner",State=P.FlightState.Holding,Position=new Vector3D(0,0,-160),Radius=3,Battery=1,Hydrogen=1};
r.Miners.Add(waiting);r.Receive("HELLO",waiting);r.Receive("DOCK_REQUEST",waiting);r.Tick(20);
Assert.DoesNotContain(r.Sent,p=>p.Kind=="DOCK_GRANT");Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void ArrivalAndDepartureOnDifferentConnectorsRunTogether()
{
var r=new FleetStateTests.Rig(2,true);r.SetConnected(1,false);r.Miners[1].Position=new Vector3D(40,0,-100);r.Miners[1].State=P.FlightState.Holding;
r.RegisterAll();r.Fleet.Command("mark 6 6 20 GPS:entry:0:0:-200: GPS:inward:0:0:-201:");r.Fleet.Command("start");FreshTicks(r,30);
r.Miners[0].JobId=r.Fleet.GetJobs().Single().Id;r.Receive("DEPART_REQUEST",r.Miners[0]);r.Receive("DOCK_REQUEST",r.Miners[1]);FreshTicks(r,30);
Assert.Single(r.Sent,p=>p.Kind=="DOCK_GRANT");Assert.Single(r.Sent,p=>p.Kind=="DEPART_GRANT");Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void StoppedFaultAwayFromThePortReleasesItsConnectorAndItsRemoteTask()
{
var r=Arrivals(2);r.Receive("DOCK_REQUEST",r.Miners[0]);FreshTicks(r,15);
r.Fleet.Command("mark 6 6 20 GPS:entry:0:0:-300: GPS:inward:0:0:-301:");var job=r.Fleet.GetJobs().Single();r.Planner.SetOwner(job,r.Miners[0].Id);
r.Miners[0].State=P.FlightState.Fault;r.Miners[0].Position=new Vector3D(-50,0,-100);r.Receive("TELEMETRY",r.Miners[0]);
r.Receive("DOCK_REQUEST",r.Miners[1]);FreshTicks(r,20);
Assert.Equal("",job.Owner);Assert.Equal(1,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
Assert.Equal(r.Miners[1].Id,P.Data.Text(r.Save(),"fleet","LaneOwner"));Assert.Contains(r.Sent,p=>p.Kind=="DOCK_GRANT" && p.Target==r.Miners[1].Id);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void FaultAtAContactOnlyReservesItsAssignedConnector(bool stale)
{
var r=Arrivals(2);r.Receive("DOCK_REQUEST",r.Miners[0]);FreshTicks(r,15);
r.Miners[0].State=P.FlightState.Fault;r.Miners[0].Position=Vector3D.Forward*20;r.Receive("TELEMETRY",r.Miners[0]);
if(stale)r.Program.Now+=10;else r.Tick(12);
r.Receive("DOCK_REQUEST",r.Miners[1]);r.Tick(12);
var second=Assert.Single(r.Sent,p=>p.Kind=="DOCK_GRANT" && p.Target==r.Miners[1].Id);
Assert.Equal(101,P.DockFrame.FromIni(second.Body).ConnectorId);
Assert.Equal(stale?2:1,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void DockRetreatReleaseAcceptsAStoppedShipBeforeTheFarWaitingPoint()
{
var r=Arrivals(1);r.Receive("DOCK_REQUEST",r.Miners[0]);FreshTicks(r,15);var grant=P.DockFrame.FromIni(r.Sent.First(p=>p.Kind=="DOCK_GRANT").Body);
r.Miners[0].Position=Vector3D.Forward*35;r.Miners[0].State=P.FlightState.DockRetreat;r.Receive("TELEMETRY",r.Miners[0]);
var body=new MyIni();body.Set("lease","Token",grant.Token);r.Fleet.Receive(new P.Packet {Kind="LANE_RELEASE",From=r.Miners[0].Id,Source=5000,Body=body});
Assert.Equal(0,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
}
[Fact]
public void MultipleUnknownConnectorAssignmentsSurviveRemovalAndReloadWithTokensRevoked()
{
var r=Arrivals(2);r.Checkpoint();foreach(var t in r.Miners)r.Receive("DOCK_REQUEST",t);FreshTicks(r,30);Assert.Equal(2,P.Data.ReadLong(r.Save(),"fleet","LaneCount"));
r.Program.Now+=10;r.Fleet.Command("clear-offline");var after=new FleetStateTests.Rig(2,false);after.Restore(r.Program.Storage);
Assert.Empty(after.Fleet.GetTelemetry());var saved=after.Save();Assert.Equal(2,P.Data.ReadLong(saved,"fleet","LaneCount"));
Assert.Empty(P.Data.Text(saved,"fleet","LaneToken"));Assert.Empty(P.Data.Text(saved,"fleetLane1","LaneToken"));
Assert.Equal(2,P.Data.ReadLong(saved,"fleet","ProtectedOwnerCount"));
}
static P.Packet Traffic(FleetStateTests.Rig r,int index)
{
typeof(P.FleetController).GetMethod("SendTraffic",BindingFlags.NonPublic|BindingFlags.Instance).Invoke(r.Fleet,new object[]{r.Fleet.GetTelemetry().Single(t=>t.Id==r.Miners[index].Id)});
return r.Sent.Last(p=>p.Kind=="TRAFFIC" && p.Target==r.Miners[index].Id);
}
[Fact]
public void CruiseShipsIgnoreDistantCrossingsAndKeepOneYieldChoiceForANearEncounter()
{
var r=Arrivals(2);var a=r.Miners[0];var b=r.Miners[1];a.State=b.State=P.FlightState.Transit;a.HasRoute=b.HasRoute=true;
a.Position=Vector3D.Left*30;a.RouteTarget=Vector3D.Right*30;a.Velocity=Vector3D.Right*3;
b.Position=Vector3D.Forward*30;b.RouteTarget=Vector3D.Backward*30;b.Velocity=Vector3D.Backward*3;
a.ThrustMargin=b.ThrustMargin=2;r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);
for(int i=0;i<2;i++)
{
var clear=Traffic(r,i).Body;Assert.False(P.Data.Flag(clear,"traffic","Wait"));Assert.False(P.Data.Flag(clear,"traffic","Avoid"));
}
a.Position=Vector3D.Left*10;b.Position=Vector3D.Forward*10;r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);
var first=new[]{Traffic(r,0).Body,Traffic(r,1).Body};
Assert.Single(first,p=>P.Data.Flag(p,"traffic","Avoid"));Assert.Single(first,p=>P.Data.Flag(p,"traffic","Wait"));
r.Program.Now+=.5;r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);
for(int i=0;i<2;i++)
{
var repeated=Traffic(r,i).Body;
Assert.Equal(P.Data.Flag(first[i],"traffic","Wait"),P.Data.Flag(repeated,"traffic","Wait"));
Assert.Equal(P.Data.Flag(first[i],"traffic","Avoid"),P.Data.Flag(repeated,"traffic","Avoid"));
Assert.Equal(P.Data.ReadVector(first[i],"traffic","Point",Vector3D.Zero),P.Data.ReadVector(repeated,"traffic","Point",Vector3D.Zero));
}
}
[Fact]
public void NearbyStationaryFaultCreatesACameraCheckedDetour()
{
var r=Arrivals(2);var a=r.Miners[0];var b=r.Miners[1];a.State=P.FlightState.Transit;a.HasRoute=true;
a.Position=Vector3D.Forward*100;a.RouteTarget=Vector3D.Zero;a.Velocity=Vector3D.Backward*2;a.ThrustMargin=2;b.Position=Vector3D.Forward*94;b.State=P.FlightState.Fault;
r.Receive("TELEMETRY",a);r.Receive("TELEMETRY",b);var body=Traffic(r,0).Body;
Assert.True(P.Data.Flag(body,"traffic","Avoid"));Assert.False(P.Data.Flag(body,"traffic","Wait"));
var point=P.Data.ReadVector(body,"traffic","Point",Vector3D.Zero);Assert.InRange(Vector3D.Distance(point,a.Position),1.9,2.01);
Assert.True(Vector3D.Distance(point,b.Position)>Vector3D.Distance(a.Position,b.Position));
}
[Fact]
public void NearbyStaleFaultPositionRequiresWaitingInsteadOfAnUnverifiedDetour()
{
var r=Arrivals(2);var a=r.Miners[0];var b=r.Miners[1];a.State=P.FlightState.Transit;a.HasRoute=true;
a.Position=Vector3D.Forward*100;a.RouteTarget=Vector3D.Zero;a.Velocity=Vector3D.Backward*2;b.Position=Vector3D.Forward*94;b.State=P.FlightState.Fault;r.Receive("TELEMETRY",b);
r.Program.Now+=5;r.Receive("TELEMETRY",a);var body=Traffic(r,0).Body;Assert.True(P.Data.Flag(body,"traffic","Wait"));Assert.False(P.Data.Flag(body,"traffic","Avoid"));
}
[Fact]
public void AStoppedFaultStillProtectsItsPointAfterOfflineRegistryRemoval()
{
var r=Arrivals(2);r.Checkpoint();r.Miners[0].State=P.FlightState.Fault;r.Miners[0].Position=Vector3D.Forward*20;
r.Receive("TELEMETRY",r.Miners[0]);r.Tick(12);r.Program.Now+=10;r.Receive("TELEMETRY",r.Miners[1]);r.Fleet.Command("clear-offline");
var after=new FleetStateTests.Rig(2,false);after.Restore(r.Program.Storage);
Assert.Equal(1,P.Data.ReadLong(after.Save(),"fleet","ClearanceCount"));Assert.Equal(1,P.Data.ReadLong(after.Save(),"fleet","ProtectedOwnerCount"));
}
[Fact]
public void FrontShipDoesNotYieldToItsFollower()
{
var r=Arrivals(2);var rear=r.Miners[0];var front=r.Miners[1];rear.HasRoute=front.HasRoute=true;rear.State=front.State=P.FlightState.Transit;
rear.Position=Vector3D.Forward*80;front.Position=Vector3D.Forward*60;rear.RouteTarget=front.RouteTarget=Vector3D.Zero;
rear.Velocity=front.Velocity=Vector3D.Backward*3;r.Receive("TELEMETRY",rear);r.Receive("TELEMETRY",front);
Assert.False(P.Data.Flag(Traffic(r,1).Body,"traffic","Wait"));
}
sealed class MovingShip
{
public readonly ReturnNavigationTests.Rig R=new ReturnNavigationTests.Rig();
public Vector3D Position,Velocity,Target;
public bool Arrived;
public MovingShip(Vector3D start,Vector3D target)
{
Position=start;Target=target;R.Orientation=MatrixD.CreateWorld(start,Vector3D.Normalize(target-start),Vector3D.Up);
R.Controller.Method("GetPosition",a=>Position).Method("GetShipVelocities",a=>new MyShipVelocities(Velocity,Vector3D.Zero));
R.Camera.Method("GetPosition",a=>Position).Method("CanScan",a=>true);
}
public void Step(double now)
{
R.Program.Now=now;
bool avoidance=R.Flight.TrafficAvoid && now-R.Flight.TrafficAt<1.5;
if(R.Flight.TrafficWait && now-R.Flight.TrafficAt<1.5){R.Flight.Hold(.05);Arrived=false;}
else
{
var target=avoidance?R.Flight.TrafficPoint:Target;
Arrived=R.Flight.Move(target,Vector3D.Zero,P.Data.Unit(target-Position,Vector3D.Forward),Vector3D.Up,avoidance?1.5:5,avoidance?.3:.5,true,.05,true,!avoidance) && !avoidance;
}
Vector3D force=Vector3D.Zero;foreach(var thrust in R.Ship.Thrusters)force+=thrust.WorldMatrix.Backward*thrust.ThrustOverridePercentage*thrust.MaxEffectiveThrust;
Velocity+=force/1000*.05;Position+=Velocity*.05;
var desired=(MatrixD)typeof(P.FlightController).GetField("previousDesired",BindingFlags.NonPublic|BindingFlags.Instance).GetValue(R.Flight);
R.Orientation=MatrixD.CreateWorld(Position,desired.Forward,desired.Up);
}
}
[Theory]
[InlineData(0)]
[InlineData(1)]
public void CrossingShipsBothReachTheirGoalsWithPhysicalSeparationInTheControlModel(int seed)
{
var r=Arrivals(2);var ships=new[]{new MovingShip(Vector3D.Left*40,Vector3D.Right*40),new MovingShip(Vector3D.Forward*40,Vector3D.Backward*40)};
typeof(P.FleetController).GetField("trafficRandom",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(r.Fleet,new Random(seed));
double separation=double.MaxValue;bool yielded=false;
for(int n=0;n<2000 && ships.Any(s=>!s.Arrived);n++)
{
r.Program.Now+=.05;
if(n%10==0)
{
for(int i=0;i<2;i++)
{
var t=r.Miners[i];var s=ships[i];t.State=P.FlightState.Transit;t.Position=s.Position;t.Velocity=s.Velocity;t.Radius=2;t.ThrustMargin=100;
t.HasRoute=!s.Arrived;t.RouteTarget=s.R.Flight.HasRoute?s.R.Flight.RouteTarget:s.Target;r.Receive("TELEMETRY",t);
}
for(int i=0;i<2;i++)if(!ships[i].Arrived)
{
var body=Traffic(r,i).Body;var f=ships[i].R.Flight;f.TrafficWait=P.Data.Flag(body,"traffic","Wait");f.TrafficAvoid=P.Data.Flag(body,"traffic","Avoid");
f.TrafficPoint=P.Data.ReadVector(body,"traffic","Point",Vector3D.Zero);f.TrafficAt=r.Program.Now;yielded|=f.TrafficWait;
}
}
foreach(var s in ships)s.Step(r.Program.Now);
separation=Math.Min(separation,Vector3D.Distance(ships[0].Position,ships[1].Position));
}
Assert.True(yielded,"No close encounter was exercised.");Assert.True(ships.All(s=>s.Arrived),string.Join(" / ",ships.Select(s=>s.Position+" -> "+s.Target+" / "+s.R.Flight.Problem)));Assert.True(separation>=5,"Minimum separation: "+separation);
}
[Fact]
public void FinalDockingDoesNotUseAnOldTrafficWaitOrDetour()
{
var r=new ReturnNavigationTests.Rig();r.Flight.TrafficWait=r.Flight.TrafficAvoid=true;r.Flight.TrafficPoint=Vector3D.Right*100;r.Flight.TrafficAt=0;
r.Flight.Move(Vector3D.Forward*3,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,.2,.03,false,.05,false,false);
Assert.True(r.Flight.CommandSpeed>0);Assert.Equal(0,r.Rays);Assert.Equal(Vector3D.Forward*3,r.Flight.RouteTarget);
}
}
}