using System; using System.Linq; using System.Reflection; using System.Runtime.Remoting; using Sandbox.ModAPI.Ingame; using VRageMath; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class SurveyNavigationTests { static readonly BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic; static T Read(object instance,string name) {return (T)instance.GetType().GetField(name,Private).GetValue(instance);} static void Write(object instance,string name,object value) {instance.GetType().GetField(name,Private).SetValue(instance,value);} static void Call(object instance,string name,params object[] args) {instance.GetType().GetMethod(name,Private).Invoke(instance,args);} sealed class SurveyRig { public readonly MinerStateTests.MinerRig R=new MinerStateTests.MinerRig(); public readonly P.FlightController Flight; public P.Job Job; public MatrixD Orientation=MatrixD.Identity; public Vector3D Velocity; public bool Ready=true; public int NavigationRays,SurveyRays; public Func Observation=end=>default(MyDetectedEntityInfo); public SurveyRig() { R.Task();Job=Read(R.Miner,"job");Job.Kind=P.JobKind.Survey;Job.Depth=2000;Job.EntityId=0; R.State(P.FlightState.Transit);Flight=Read(R.Miner,"flight"); R.Controller.Method("get_WorldMatrix",a=>MatrixD.CreateWorld(R.Position,Orientation.Forward,Orientation.Up)) .Method("GetShipVelocities",a=>new MyShipVelocities(Velocity,Vector3D.Zero)); foreach(var thrust in R.Hardware.Thrusters) { var proxy=(Stub)RemotingServices.GetRealProxy(thrust);var local=thrust.WorldMatrix; proxy.Values.Remove("WorldMatrix");proxy.Method("get_WorldMatrix",a=>local*Orientation); } var gyro=(Stub)RemotingServices.GetRealProxy(R.Hardware.Gyros[0]); gyro.Values.Remove("WorldMatrix");gyro.Method("get_WorldMatrix",a=>Orientation); // A single forward camera matches miners 2-4 in the reported save. R.Camera.Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d).Set("EnableRaycast",true) .Method("get_WorldMatrix",a=>MatrixD.CreateWorld(R.Position,Orientation.Forward,Orientation.Up)) .Method("CanScan",a=>{ Vector3D local=Vector3D.TransformNormal((Vector3D)a[0]-R.Position,MatrixD.Transpose(Orientation)); return Ready && -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; }).Method("Raycast",a=>{ if(R.StateNow==P.FlightState.Survey){SurveyRays++;return Observation((Vector3D)a[0]);} NavigationRays++;return default(MyDetectedEntityInfo); }); } public void Tick(bool integrate=true) { const double dt=.05;R.Program.Now+=dt; if(R.StateNow!=P.FlightState.Transit && R.StateNow!=P.FlightState.Survey)return; Call(R.Miner,R.StateNow==P.FlightState.Transit?"Transit":"Survey",dt); if(!integrate)return; Vector3D acceleration=Vector3D.Zero; foreach(var thrust in R.Hardware.Thrusters)acceleration+=thrust.WorldMatrix.Backward*thrust.MaxEffectiveThrust*thrust.ThrustOverridePercentage/1000; Velocity+=acceleration*dt;R.Position+=Velocity*dt; // Ideal attitude response; this fixture validates phase/camera geometry, // not the game's inertia, gyro torque or physics. Orientation=Read(Flight,"previousDesired").GetOrientation(); } public P.JobReport Finish() { for(int n=0;n<1200 && !R.Sent.Any(p=>p.Kind=="RESULT");n++)Tick(); Assert.Equal(P.FlightState.Survey,R.StateNow);Assert.True(Read(R.Miner,"continuing")); Assert.Equal(1,SurveyRays); return P.JobReport.FromIni(Assert.Single(R.Sent,p=>p.Kind=="RESULT").Body); } } [Fact] public void SurveyTransitKeepsFacingItsTravelUntilTheObservationPointIsReached() { var r=new SurveyRig();r.Job.Entry=Vector3D.Right*4;r.Orientation=MatrixD.CreateWorld(Vector3D.Zero,Vector3D.Right,Vector3D.Up); r.Tick(false); Assert.True(Vector3D.Dot(Read(r.Flight,"previousDesired").Forward,Vector3D.Right)>.999); Assert.Equal(P.FlightState.Transit,r.R.StateNow); } [Theory] [InlineData(0d)] [InlineData(1d)] public void AtmosphericObservationArrivalKeepsGravityUpBeforeTurningDownForTheScan(double lateral) { var r=new SurveyRig();Vector3D travel=Vector3D.Normalize(new Vector3D(lateral,0,-1)); r.Job.Entry=travel*4;r.Job.Direction=Vector3D.Down;r.Job.Up=Vector3D.Forward; r.Orientation=MatrixD.CreateWorld(Vector3D.Zero,travel,Vector3D.Up); r.R.Controller.Method("GetNaturalGravity",a=>Vector3D.Down*9.81); r.Tick(false);MatrixD desired=Read(r.Flight,"previousDesired"); Assert.True(Vector3D.Dot(desired.Forward,travel)>.999); Assert.True(Vector3D.Dot(desired.Up,Vector3D.Up)>.999); } [Theory] [InlineData(-3.336105458482,1.21255582737,-2.10187508931)] [InlineData(1.35609391866,-2.42185151840,-2.33387649985)] [InlineData(1.83350782323,2.07241880727,-2.22492569889)] [InlineData(.601750779737,-1.50573927145,3.02794924745)] public void SavedFinalMetresWithOneForwardCameraReachObservationAndPerformTheScan(double x,double y,double z) { var r=new SurveyRig();r.Job.Entry=new Vector3D(-67227.817161019266,-123791.63501934477,-161805.93741404629); r.Job.Direction=new Vector3D(.6281360745318959,.58511134572200962,-.512922786566399); r.Job.Up=new Vector3D(-.74590343947643378,.26512158333808944,-.61101440656135975); r.R.Position=r.Job.Entry+new Vector3D(x,y,z); r.Orientation=MatrixD.CreateWorld(Vector3D.Zero,r.Job.Direction,r.Job.Up); Assert.Equal("SurveyEmpty",r.Finish().Outcome);Assert.True(r.NavigationRays>0); Assert.InRange(Vector3D.Distance(r.R.Position,r.Job.Entry),0,1.25); } [Theory] [InlineData(.6)] [InlineData(.9)] public void ObservationDoesNotDemandASecondTighterPositionCorrectionBeforeScanning(double offset) { var r=new SurveyRig();r.R.Position=Vector3D.Right*offset;r.R.State(P.FlightState.Survey); Vector3D observation=r.R.Position;r.Tick(); Assert.Equal(1,r.SurveyRays);Assert.Equal(P.FlightState.Survey,r.R.StateNow);Assert.True(Read(r.R.Miner,"continuing")); Assert.InRange(Vector3D.Distance(r.R.Position,observation),0,.001); Assert.Equal("SurveyEmpty",P.JobReport.FromIni(Assert.Single(r.R.Sent,p=>p.Kind=="RESULT").Body).Outcome); } [Fact] public void LongRangeAsteroidHitBecomesAHigherPriorityProbeAtTheActualHitPoint() { var r=new SurveyRig();var planner=new P.TaskPlanner(r.R.Program); planner.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000); r.Job=planner.Jobs.First();Write(r.R.Miner,"job",r.Job); r.R.Position=r.Job.Entry+Vector3D.Right*4;r.Orientation=MatrixD.CreateWorld(Vector3D.Zero,r.Job.Direction,r.Job.Up); var surface=r.Job.Entry+r.Job.Direction*100; r.Observation=end=>new MyDetectedEntityInfo(42,"Asteroid",MyDetectedEntityType.Asteroid,surface,MatrixD.Identity,Vector3.Zero, VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(surface-new Vector3D(10),surface+new Vector3D(10)),1); P.JobReport report=r.Finish();Assert.Equal("SurveyHit",report.Outcome);planner.Report(r.Job,report); P.Job probe=Assert.Single(planner.Jobs,j=>j.Kind==P.JobKind.Probe); Assert.Equal(surface,probe.Entry);Assert.Equal(42,probe.EntityId);Assert.Equal(40,probe.Depth); Assert.True(probe.Priority>r.Job.Priority);Assert.NotEqual(Vector3D.Zero,probe.Direction); } [Fact] public void MissingNavigationCoverageReportsTheBlockerInsteadOfOnlyTheOriginalTaskMessage() { var r=new SurveyRig();r.R.Position=Vector3D.Backward*30;r.Ready=false; r.Tick(false);Assert.Equal(P.FlightState.Transit,r.R.StateNow); Assert.NotEmpty(r.Flight.Problem);Assert.Contains(r.Flight.Problem,Read(r.R.Miner,"reason")); Assert.Equal(0,r.SurveyRays);Assert.DoesNotContain(r.R.Sent,p=>p.Kind=="RESULT"); } [Fact] public void ObservationWithoutAChargedCameraWaitsWithoutReportingEmptySpace() { var r=new SurveyRig();r.Ready=false;r.R.State(P.FlightState.Survey); for(int n=0;n<30;n++)r.Tick(); Assert.Equal(P.FlightState.Survey,r.R.StateNow);Assert.Equal(0,r.SurveyRays); Assert.DoesNotContain(r.R.Sent,p=>p.Kind=="RESULT");Assert.Equal(P.L.MinerWaitSurveyScan,Read(r.R.Miner,"reason")); r.Ready=true;Assert.Equal("SurveyEmpty",r.Finish().Outcome); } [Fact] public void ProbeTransitReachesTheAlignmentToleranceBeforeTurningToTheBore() { var r=new SurveyRig();r.Job.Kind=P.JobKind.Probe;r.Job.Depth=40; Vector3D approach=r.Job.Entry-r.Job.Direction*22;r.R.Position=approach+Vector3D.Right*.8; r.Orientation=MatrixD.CreateWorld(Vector3D.Zero,Vector3D.Left,Vector3D.Up);r.Ready=false; r.Tick(false);Assert.Equal(P.FlightState.Transit,r.R.StateNow); r.Ready=true;for(int n=0;n<600 && r.R.StateNow==P.FlightState.Transit;n++)r.Tick(); Assert.Equal(P.FlightState.Align,r.R.StateNow);Assert.InRange(Vector3D.Distance(approach,r.R.Position),0,.5); r.Velocity=Vector3D.Zero;r.R.Camera.Method("Raycast",a=>MinerStateTests.MinerRig.Hit(MyDetectedEntityType.Asteroid)); for(int n=0;n<10 && r.R.StateNow==P.FlightState.Align;n++) {Call(r.R.Miner,"Align",.05);r.Orientation=Read(r.Flight,"previousDesired").GetOrientation();} Assert.Equal(P.FlightState.Drilling,r.R.StateNow); } } }