XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
UTF-8
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);
        }
    }
}