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

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
UTF-8
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Remoting;
using Sandbox.ModAPI.Ingame;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;

namespace AutoMiningScript.Tests
{
    public class CameraReadinessTests
    {
        static T Field<T>(object value,string name)
        {return (T)value.GetType().GetField(name,BindingFlags.NonPublic|BindingFlags.Instance).GetValue(value);}

        static Stub<IMyCameraBlock> Camera(ReturnNavigationTests.Rig r,string name,long id,Func<MatrixD> pose)
        {
            var c=new Stub<IMyCameraBlock>().Set("CustomName",name).Set("EntityId",id).Set("CubeGrid",r.Program.Me.CubeGrid)
                .Set("IsFunctional",true).Set("Enabled",true).Set("IsWorking",true).Set("EnableRaycast",true)
                .Set("AvailableScanRange",100000d).Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d)
                .Method("get_WorldMatrix",a=>pose()).Method("GetPosition",a=>pose().Translation);
            c.Method("CanScan",a=>{
                var target=(Vector3D)a[0];var local=Vector3D.TransformNormal(target-pose().Translation,MatrixD.Transpose(pose()));
                return c.Value.IsWorking && Vector3D.Distance(target,pose().Translation)<=c.Value.AvailableScanRange && -local.Z>0 &&
                    Math.Abs(Math.Atan2(local.X,-local.Z))<=Math.PI/4 &&
                    Math.Abs(Math.Atan2(local.Y,Math.Sqrt(local.X*local.X+local.Z*local.Z)))<=Math.PI/4;
            });
            return c;
        }

        [Fact]
        public void SavedFourteenMetreApproachCompletesWithTheInstalledCameraLayout()
        {
            var r=new ReturnNavigationTests.Rig();
            var position=new Vector3D(-67811.481492656632,-123686.02419437817,-162113.20791115251);
            r.Orientation=MatrixD.CreateWorld(position,new Vector3D(-.22140851616859436,.76646673679351807,.60291552543640137),
                new Vector3D(.86432009935379028,-.13206471502780914,.48529350757598877));
            r.Controller.Method("GetPosition",a=>position);
            r.Ship.Radius=4.6314601354267158;r.Ship.BodyMin=new Vector3D(-2.25,-1.75,-2.25);r.Ship.BodyMax=new Vector3D(2.25,1.25,2.25);
            r.Ship.BodyHalfSize=Vector3D.Max(Vector3D.Abs(r.Ship.BodyMin),Vector3D.Abs(r.Ship.BodyMax));r.Ship.Cameras.Clear();
            var positions=new[]{new Vector3D(0,1,1.5),new Vector3D(0,1,0),new Vector3D(2,0,1),new Vector3D(0,-.5,-.5),
                new Vector3D(0,-1.5,.5),new Vector3D(-2,0,1),new Vector3D(1.5,0,2),new Vector3D(-1.5,0,2)};
            var directions=new[]{Vector3D.Backward,Vector3D.Forward,Vector3D.Right,Vector3D.Forward,Vector3D.Forward,Vector3D.Left,Vector3D.Backward,Vector3D.Backward};
            var ups=new[]{Vector3D.Up,Vector3D.Up,Vector3D.Down,Vector3D.Down,Vector3D.Down,Vector3D.Backward,Vector3D.Right,Vector3D.Right};
            for(int n=0;n<positions.Length;n++)
            {
                var pose=MatrixD.CreateWorld(positions[n],directions[n],ups[n])*r.Orientation;
                r.Ship.Cameras.Add(Camera(r,"Installed "+n,n+1,()=>pose).Value);
            }
            var target=new Vector3D(-67808.36607753133,-123696.7388086597,-162121.59648775522);
            r.Flight.SetDockContact(50,new Vector3D(-67807.59084232424,-123699.42231776408,-162123.70730985893),1.5);
            for(int n=0;n<18 && r.Flight.ScanClearance==0;n++)
            {
                r.Program.Now+=1d/60;
                r.Flight.Move(target,Vector3D.Zero,r.Orientation.Forward,r.Orientation.Up,1.83,.1,true,1d/60,false,false);
            }
            Assert.InRange(r.Flight.ScanClearance,16,17);Assert.True(r.HasTranslation,r.Flight.Problem+" / "+r.Flight.CameraDiagnostics);
        }

        static MyDetectedEntityInfo Wall(Vector3D point)
        {
            return new MyDetectedEntityInfo(50,"Berth railing",MyDetectedEntityType.LargeGrid,point,MatrixD.Identity,Vector3.Zero,
                VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(point-new Vector3D(.1),point+new Vector3D(.1)),1);
        }
        static void Approach(ReturnNavigationTests.Rig r)
        {
            r.Program.Now+=.05;
            r.Flight.Move(Vector3D.Forward*14,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2,.1,true,.05,false,false);
        }

        [Fact]
        public void FarRailingHitCertifiesTheClearApproachBeforeItInsteadOfBlockingAllFourteenMetres()
        {
            var r=new ReturnNavigationTests.Rig();int rays=0;
            r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
            r.Camera.Method("Raycast",a=>{rays++;var end=(Vector3D)a[0];return -end.Z>10?Wall(end*(10/-end.Z)):default(MyDetectedEntityInfo);});
            for(int n=0;n<9;n++)
            {
                Approach(r);
                if(n<8)Assert.False(r.HasTranslation);
                Assert.False(r.Flight.Blocked,"An unfinished sweep must not classify the whole route as impassable.");
            }
            Assert.Equal(18,rays);Assert.InRange(r.Flight.ScanClearance,9,10);Assert.True(r.HasTranslation,r.Flight.Problem);
            Assert.Contains("Berth railing",r.Flight.Problem);
        }

        [Fact]
        public void ObstacleReasonRemainsVisibleWhileTheNextSweepIsIncomplete()
        {
            var r=new ReturnNavigationTests.Rig();int rays=0;
            r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
            r.Camera.Method("Raycast",a=>{rays++;return rays%18>=10 || rays%18==0?Wall(Vector3D.Forward*.8):default(MyDetectedEntityInfo);});
            for(int n=0;n<9;n++)Approach(r);
            for(int n=0;n<25;n++)
            {
                Approach(r);Assert.False(r.HasTranslation);
                Assert.Contains("Berth railing",r.Flight.Problem);Assert.DoesNotContain(P.L.MinerEnvelopeScanPending,r.Flight.Problem);
            }
        }

        [Fact]
        public void NewObstacleImmediatelyShortensExistingClearanceBeforeSweepCompletion()
        {
            var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
            for(int n=0;n<9;n++)Approach(r);Assert.True(r.Flight.ScanClearance>10);
            r.Camera.Method("Raycast",a=>Wall(Vector3D.Forward*.8));Approach(r);
            Assert.False(r.HasTranslation);Assert.InRange(r.Flight.ScanClearance,0,.8);Assert.Contains("Berth railing",r.Flight.Problem);
        }

        [Fact]
        public void RailingClearanceIsReplacedOnlyAfterAnEntireFreshUnobstructedSweep()
        {
            var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
            r.Camera.Method("Raycast",a=>Wall(Vector3D.Forward*.8));for(int n=0;n<9;n++)Approach(r);
            r.Camera.Method("Raycast",a=>default(MyDetectedEntityInfo));
            for(int n=0;n<8;n++){Approach(r);Assert.False(r.HasTranslation);Assert.Contains("Berth railing",r.Flight.Problem);}
            Approach(r);Assert.True(r.HasTranslation);Assert.DoesNotContain("Berth railing",r.Flight.Problem);
        }

        [Theory]
        [InlineData(0)]
        [InlineData(1)]
        [InlineData(2)]
        public void MissingOrInvalidSurfaceHitNeverCertifiesFreeSpaceToTheEntityCentre(int kind)
        {
            var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(50,Vector3D.Forward*20,1.5);
            for(int n=0;n<9;n++)Approach(r);Assert.True(r.HasTranslation);
            Vector3D? point=kind==0?(Vector3D?)null:new Vector3D(kind==1?double.NaN:double.PositiveInfinity);
            r.Camera.Method("Raycast",a=>new MyDetectedEntityInfo(50,"Unknown surface",MyDetectedEntityType.LargeGrid,point,
                MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,
                new BoundingBoxD(new Vector3D(-1,-1,-11),new Vector3D(1,1,-9)),1));
            for(int n=0;n<20;n++)
            {
                Approach(r);Assert.Equal(0,r.Flight.ScanClearance);Assert.False(r.HasTranslation);
                Assert.Contains(P.L.F(P.L.CameraObstacleUnknown,"Unknown surface"),r.Flight.Problem);
            }
        }

        [Fact]
        public void ALongNoseUsesVisibleSamplesInTheSameLanesInsteadOfImpossibleCloseCorners()
        {
            var r=new ReturnNavigationTests.Rig();r.Ship.Radius=44;r.Ship.BodyHalfSize=new Vector3D(5,15,40);
            r.Ship.BodyMin=-r.Ship.BodyHalfSize;r.Ship.BodyMax=r.Ship.BodyHalfSize;
            r.Ship.Cameras.Clear();Vector3D front=Vector3D.Forward*40;
            r.Ship.Cameras.Add(Camera(r,"Front",1,()=>MatrixD.CreateWorld(front,Vector3D.Forward,Vector3D.Up)).Value);
            r.Ship.Cameras.Add(Camera(r,"Rear",2,()=>MatrixD.CreateWorld(Vector3D.Backward*40,Vector3D.Backward,Vector3D.Up)).Value);
            var endpoints=new List<Vector3D>();var camera=r.Ship.Cameras[0];
            ((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Method("Raycast",a=>{
                Assert.True(camera.CanScan((Vector3D)a[0]));endpoints.Add((Vector3D)a[0]);return default(MyDetectedEntityInfo);
            });
            for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
            Assert.True(r.HasTranslation);Assert.Equal(18,endpoints.Count);Assert.Equal(50.2,r.Flight.ScanClearance,8);
            Assert.Contains(endpoints.GetRange(0,9),point=>-point.Z>Field<double>(r.Flight,"scanLength")*.35+.1);
            Assert.Contains(endpoints,point=>point.Y>15);Assert.Contains(endpoints,point=>point.Y<-15);
        }

        [Theory]
        [InlineData("disabled")]
        [InlineData("damaged")]
        [InlineData("power")]
        [InlineData("raycast")]
        public void CameraHardwareFailuresHaveDistinctNamesAndNeverAuthoriseMotion(string kind)
        {
            var r=new ReturnNavigationTests.Rig();r.Camera.Set("CustomName","Lens").Method("CanScan",a=>false);
            string format;
            if(kind=="raycast") {r.Camera.Set("EnableRaycast",false);format=P.L.CameraRaycastDisabled;}
            else
            {
                r.Camera.Set("IsWorking",false);
                if(kind=="disabled") {r.Camera.Set("Enabled",false);format=P.L.CameraDisabled;}
                else if(kind=="damaged") {r.Camera.Set("IsFunctional",false);format=P.L.CameraDamaged;}
                else format=P.L.CameraUnpowered;
            }
            r.Step(Vector3D.Forward*100);
            Assert.Equal(P.L.F(format,"Lens"),r.Flight.Problem);Assert.False(r.HasTranslation);
            Assert.False(Field<bool>(r.Flight,"scanWaitingForCharge"));
        }

        [Fact]
        public void AChargingCameraReportsAvailableAndRequiredRangeInsteadOfAnOrientationFailure()
        {
            var r=new ReturnNavigationTests.Rig();r.Charge=0;r.Camera.Set("CustomName","Lens").Method("TimeUntilScan",a=>1200);
            r.Step(Vector3D.Forward*100);
            Assert.True(Field<bool>(r.Flight,"scanWaitingForCharge"));Assert.False(r.HasTranslation);
            Assert.Equal(P.L.F(P.L.CameraCharging,"Lens",0d,Field<double>(r.Flight,"scanLength")*.35,1.2),r.Flight.Problem);
        }

        [Fact]
        public void ChargeRangeReductionCannotShrinkInsideTheLeadingHull()
        {
            var r=new ReturnNavigationTests.Rig();r.Charge=0;r.Ship.BodyMin=new Vector3D(-1,-1,-10);r.Ship.BodyMax=new Vector3D(1,1,1);
            for(int n=0;n<500;n++)r.Step(Vector3D.Forward*100,false,1);
            Assert.True(Field<double>(r.Flight,"scanLimit")>=11);Assert.False(r.HasTranslation);Assert.Equal(0,r.Flight.ScanClearance);
        }

        [Fact]
        public void PrepareFlightEnablesOnlyTheManagedLocalCameras()
        {
            var r=new ReturnNavigationTests.Rig();r.Camera.Set("Enabled",false).Set("EnableRaycast",false);
            var other=Camera(r,"Foreign",9,()=>MatrixD.Identity).Set("CubeGrid",new Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>().Value).Set("Enabled",false).Set("EnableRaycast",false);
            r.Ship.Cameras.Add(other.Value);r.Ship.PrepareFlight();
            Assert.True(r.Camera.Value.Enabled);Assert.True(r.Camera.Value.EnableRaycast);
            Assert.False(other.Value.Enabled);Assert.False(other.Value.EnableRaycast);
        }

        [Fact]
        public void AStaticHundredCamerasReportTheActualDirectionalFailureWithoutLosingRange()
        {
            var r=new ReturnNavigationTests.Rig();r.Ship.Cameras.Clear();
            for(int n=0;n<100;n++)
                r.Ship.Cameras.Add(Camera(r,"Lens-"+n,n+1,()=>MatrixD.Identity).Value);
            for(int n=0;n<90;n++)r.Step(Vector3D.Right*100);
            Assert.Contains(P.L.F(P.L.CameraInventory,100,100,0),r.Flight.CameraDiagnostics);
            Assert.False(r.HasTranslation);Assert.False(Field<bool>(r.Flight,"scanWaitingForCharge"));
            Assert.Equal(2000,Field<double>(r.Flight,"scanLimit"));Assert.Contains("90.0",r.Flight.CameraDiagnostics);
            Assert.Equal(P.L.F(P.L.CameraRegionUnavailable,1,100),r.Flight.Problem);
        }

        static List<IMyTerminalBlock> TerminalBlocks(MinerStateTests.MinerRig r)
        {
            var blocks=new List<IMyTerminalBlock> {r.Controller.Value,r.Connector.Value,r.Drill.Value,r.Camera.Value};
            foreach(var thrust in r.Hardware.Thrusters)
            {
                var definition=thrust.BlockDefinition;definition.SubtypeName="MockThruster";
                ((Stub<IMyThrust>)RemotingServices.GetRealProxy(thrust)).Set("BlockDefinition",definition);
            }
            blocks.AddRange(r.Hardware.Thrusters);blocks.AddRange(r.Hardware.Gyros);blocks.AddRange(r.Hardware.Batteries);
            return blocks;
        }
        static Stub<IMyCameraBlock> InstalledCamera(MinerStateTests.MinerRig r,long id)
        {
            return new Stub<IMyCameraBlock>().Set("CubeGrid",r.Program.Me.CubeGrid).Set("EntityId",id)
                .Set("IsFunctional",true).Set("WorldMatrix",MatrixD.Identity).Set("Enabled",false).Set("EnableRaycast",false);
        }
        static void SupplyBlocks(MinerStateTests.MinerRig r,List<IMyTerminalBlock> blocks,List<IMyTerminalBlock> groupBlocks=null)
        {
            var system=new Stub<IMyGridTerminalSystem>().Method("GetBlocksOfType",a=>{
                var list=(List<IMyTerminalBlock>)a[0];var filter=(Func<IMyTerminalBlock,bool>)a[1];
                foreach(var block in blocks)if(filter==null || filter(block))list.Add(block);return null;
            });
            if(groupBlocks!=null)
            {
                var group=new Stub<IMyBlockGroup>().Method("GetBlocks",a=>{((List<IMyTerminalBlock>)a[0]).AddRange(groupBlocks);return null;});
                system.Method("GetBlockGroupWithName",a=>{Assert.Equal("Managed",a[0]);return group.Value;});
            }
            TestRig.SetBase(r.Program,"GridTerminalSystem",system.Value);
        }

        [Fact]
        public void MinerInitializationDiscoversAndEnablesLocalCamerasAndMeasuresTheHull()
        {
            var r=new MinerStateTests.MinerRig();var grid=(Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>)RemotingServices.GetRealProxy(r.Program.Me.CubeGrid);
            grid.Set("Min",new Vector3I(-10)).Set("Max",new Vector3I(10)).Method("GridIntegerToWorld",a=>new Vector3D((Vector3I)a[0])*2.5);
            var added=InstalledCamera(r,2);var foreign=InstalledCamera(r,3).Set("CubeGrid",new Stub<VRage.Game.ModAPI.Ingame.IMyCubeGrid>().Value);
            var blocks=TerminalBlocks(r);blocks.Add(added.Value);blocks.Add(foreign.Value);SupplyBlocks(r,blocks);
            var miner=new P.MinerController(r.Program);var hardware=Field<P.ShipHardware>(miner,"ship");
            Assert.Equal(2,hardware.Cameras.Count);Assert.True(added.Value.Enabled);Assert.True(added.Value.EnableRaycast);
            Assert.False(foreign.Value.Enabled);Assert.True(hardware.BodyMax.X>25);
            Assert.True(Field<bool>(miner,"healthy"));
        }

        [Theory]
        [InlineData("init")]
        [InlineData("rescan")]
        public void ExplicitInitializationUpdatesAddedAndRemovedCameras(string command)
        {
            var r=new MinerStateTests.MinerRig();r.State(P.FlightState.Paused);
            var added=InstalledCamera(r,2);var blocks=TerminalBlocks(r);blocks.Add(added.Value);SupplyBlocks(r,blocks);
            Assert.Single(r.Hardware.Cameras);r.Miner.Command(command);
            Assert.Equal(2,r.Hardware.Cameras.Count);Assert.True(added.Value.Enabled);Assert.True(added.Value.EnableRaycast);
            blocks.RemoveAll(block=>object.ReferenceEquals(block,r.Camera.Value));r.Miner.Command(command);
            Assert.Same(added.Value,Assert.Single(r.Hardware.Cameras));
        }

        [Fact]
        public void InitializationRespectsTheConfiguredHardwareGroup()
        {
            var r=new MinerStateTests.MinerRig();r.Program.Config=new P.Settings("[Hardware]\nGroup=Managed","miner","test");
            var selected=TerminalBlocks(r);var outside=InstalledCamera(r,2);var all=new List<IMyTerminalBlock>(selected) {outside.Value};
            SupplyBlocks(r,all,selected);var miner=new P.MinerController(r.Program);
            Assert.Same(r.Camera.Value,Assert.Single(Field<P.ShipHardware>(miner,"ship").Cameras));Assert.False(outside.Value.Enabled);
        }

        [Theory]
        [InlineData(P.FlightState.Returning)]
        [InlineData(P.FlightState.Holding)]
        [InlineData(P.FlightState.Paused)]
        [InlineData(P.FlightState.Manual)]
        public void HealthyMinerNeverAutomaticallyRediscoversCameras(P.FlightState state)
        {
            var r=new MinerStateTests.MinerRig();r.State(state);int scans=0;
            TestRig.SetBase(r.Program,"GridTerminalSystem",new Stub<IMyGridTerminalSystem>().Method("GetBlocksOfType",a=>{scans++;return null;}).Value);
            foreach(double now in new[]{0d,5d,10d,30d}) {r.Program.Now=now;r.Miner.Tick(.1);}
            Assert.Equal(0,scans);Assert.Same(r.Camera.Value,Assert.Single(r.Hardware.Cameras));
        }

        static ReturnNavigationTests.Rig ViewGapRig()
        {
            var r=new ReturnNavigationTests.Rig();r.Ship.Radius=16;r.Ship.BodyHalfSize=new Vector3D(5,5,10);
            r.Ship.BodyMin=-r.Ship.BodyHalfSize;r.Ship.BodyMax=r.Ship.BodyHalfSize;r.Ship.Cameras.Clear();
            r.Ship.Cameras.Add(Camera(r,"Front",1,()=>MatrixD.CreateWorld(Vector3D.Forward*10,Vector3D.Forward,Vector3D.Up)).Value);
            r.Ship.Cameras.Add(Camera(r,"Rear",2,()=>MatrixD.CreateWorld(Vector3D.Backward*10,Vector3D.Backward,Vector3D.Up)).Value);
            return r;
        }

        [Fact]
        public void AShortWaypointDoesNotSqueezeTheCameraHorizonAgainstTheNose()
        {
            var r=ViewGapRig();int rays=0;
            foreach(var camera in r.Ship.Cameras)((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);});
            for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
            Assert.True(r.HasTranslation);Assert.False(r.Flight.Blocked);Assert.Equal("",r.Flight.Problem);Assert.Equal(18,rays);
            Assert.Equal(20.2,r.Flight.ScanClearance,8);
            Assert.Contains(P.L.F(P.L.CameraCoverage,18,1),r.Flight.CameraDiagnostics);
        }

        [Fact]
        public void AnIncompleteCombinedScanDoesNotCountInvisibleRegionsAsScanned()
        {
            var r=new ReturnNavigationTests.Rig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);
            for(int n=0;n<30;n++)r.Step(Vector3D.Right*100);
            Assert.False(r.HasTranslation);Assert.Equal(0,r.Gyro.Value.Yaw);Assert.Equal(0,r.Flight.ScanClearance);
            Assert.Equal(0,r.Rays);Assert.Contains(P.L.F(P.L.CameraCoverage,0,0),r.Flight.CameraDiagnostics);
        }

        [Fact]
        public void CombinedScanningStillBrakesForObstaclesInsideTheCurrentLeg()
        {
            var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);int hits=0;
            var camera=(Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0]);
            camera.Method("Raycast",a=>{
                if(((Vector3D)a[0]).Z<-20) {hits++;return ReturnNavigationTests.Rig.Detection(42);}
                return default(MyDetectedEntityInfo);
            });
            for(int n=0;n<20 && !r.Flight.Blocked;n++) {r.Step(Vector3D.Forward*10);Assert.False(r.HasTranslation);}
            Assert.True(r.Flight.Blocked);Assert.Equal(1,hits);Assert.Equal(0,r.Flight.ScanClearance);
        }

        [Fact]
        public void CombinedClearanceDoesNotSurviveARealObstacleOnTheNextSweep()
        {
            var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);
            for(int n=0;n<9;n++)r.Step(Vector3D.Forward*10);Assert.True(r.HasTranslation);
            ((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0])).Method("Raycast",a=>ReturnNavigationTests.Rig.Detection(42));
            r.Program.Now+=.4;r.Step(Vector3D.Forward*10);
            Assert.True(r.Flight.Blocked);Assert.False(r.HasTranslation);
        }

        [Fact]
        public void CombinedClearanceExpiresWhenCamerasCannotRecharge()
        {
            var r=ViewGapRig();for(int n=0;n<9;n++)r.Step(Vector3D.Forward*10);Assert.True(r.HasTranslation);
            foreach(var camera in r.Ship.Cameras)((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(camera)).Set("AvailableScanRange",0d);
            for(int n=0;n<40;n++)r.Step(Vector3D.Forward*10);
            Assert.False(r.HasTranslation);Assert.True(Field<bool>(r.Flight,"scanWaitingForCharge"));
        }

        [Fact]
        public void AnObservedObstaclePastAShortWaypointDoesNotBlockTheCurrentLeg()
        {
            var r=ViewGapRig();r.Flight.SetDockContact(99,Vector3D.Right*100,1);int hits=0;
            ((Stub<IMyCameraBlock>)RemotingServices.GetRealProxy(r.Ship.Cameras[0])).Method("Raycast",a=>{
                if(((Vector3D)a[0]).Z>-25)return default(MyDetectedEntityInfo);
                hits++;return new MyDetectedEntityInfo(42,"beyond waypoint",MyDetectedEntityType.LargeGrid,Vector3D.Forward*25,
                    MatrixD.Identity,Vector3.Zero,VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(new Vector3D(-1),new Vector3D(1)),1);
            });
            for(int n=0;n<30 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
            Assert.True(r.HasTranslation);Assert.Equal(9,hits);Assert.False(r.Flight.Blocked);Assert.Equal(20.2,r.Flight.ScanClearance,8);
        }

        [Fact]
        public void OneSelfObstructedCameraDoesNotInterruptTwoOtherCamerasOrExistingClearance()
        {
            var r=new ReturnNavigationTests.Rig();bool blocked=false;int selfHits=0,clearHits=0;
            r.Hit=()=>{if(blocked) {selfHits++;return ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);}return default(MyDetectedEntityInfo);};
            for(int id=2;id<=3;id++)r.Ship.Cameras.Add(r.MakeCamera(id,()=>10000,()=>{clearHits++;return default(MyDetectedEntityInfo);}).Value);
            for(int n=0;n<9;n++)r.Step(Vector3D.Forward*100);Assert.True(r.HasTranslation);
            blocked=true;
            for(int n=0;n<90;n++)
            {
                r.Step(Vector3D.Forward*100);
                Assert.True(r.HasTranslation);Assert.True(r.Flight.ScanClearance>0);Assert.False(r.Flight.Blocked);
            }
            Assert.True(selfHits>=18);Assert.True(clearHits>=18);
        }

        [Fact]
        public void ACameraBlockedForOnePointCanStillCoverOtherPoints()
        {
            var r=new ReturnNavigationTests.Rig();int recovered=0,alternate=0;
            r.Hit=()=>ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);
            r.Ship.Cameras.Add(r.MakeCamera(2,()=>10000,()=>{alternate++;return default(MyDetectedEntityInfo);}).Value);
            r.Step(Vector3D.Forward*100);
            Assert.Equal(1,alternate);r.Hit=()=>{recovered++;return default(MyDetectedEntityInfo);};
            for(int n=0;n<15 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*100);
            Assert.Equal(17,recovered);Assert.True(r.Flight.ScanClearance>0);Assert.True(r.HasTranslation);
        }

        [Fact]
        public void ThreeFrontCamerasShareRegionsEvenWhenTheCenterCameraIsObstructed()
        {
            var r=ViewGapRig();r.Ship.Cameras.Clear();int left=0,right=0,blocked=0;
            var center=Camera(r,"Center",1,()=>MatrixD.CreateWorld(new Vector3D(0,0,-10),Vector3D.Forward,Vector3D.Up));
            center.Method("Raycast",a=>{blocked++;return ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);});
            var leftCamera=Camera(r,"Left",2,()=>MatrixD.CreateWorld(new Vector3D(-4,0,-10),Vector3D.Forward,Vector3D.Up));
            leftCamera.Method("Raycast",a=>{left++;return default(MyDetectedEntityInfo);});
            var rightCamera=Camera(r,"Right",3,()=>MatrixD.CreateWorld(new Vector3D(4,0,-10),Vector3D.Forward,Vector3D.Up));
            rightCamera.Method("Raycast",a=>{right++;return default(MyDetectedEntityInfo);});
            r.Ship.Cameras.Add(center.Value);r.Ship.Cameras.Add(leftCamera.Value);r.Ship.Cameras.Add(rightCamera.Value);
            for(int n=0;n<40 && r.Flight.ScanClearance==0;n++)r.Step(Vector3D.Forward*10);
            Assert.True(r.HasTranslation);Assert.True(blocked>0);Assert.True(left>0);Assert.True(right>0);Assert.Equal(18,left+right);
            Assert.Contains(P.L.F(P.L.CameraCoverage,18,2),r.Flight.CameraDiagnostics);
        }
    }
}