using System;
using System.Collections.Generic;
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 BreadcrumbCompressionTests
{
static readonly BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic;
static T Read<T>(object target,string name)=> (T)target.GetType().GetField(name,Private).GetValue(target);
static void Write(object target,string name,object value)=>target.GetType().GetField(name,Private).SetValue(target,value);
static void Call(object target,string name,params object[] arguments)=>target.GetType().GetMethod(name,Private).Invoke(target,arguments);
static List<Vector3D> Path(MinerStateTests.MinerRig r)=>Read<List<Vector3D>>(r.Miner,"breadcrumbs");
static MinerStateTests.MinerRig Rig()
{var r=new MinerStateTests.MinerRig();r.Task();r.State(P.FlightState.Transit);return r;}
static void Record(MinerStateTests.MinerRig r,Vector3D point)
{r.Position=point;Call(r.Miner,"RecordBreadcrumb");}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void FiveKilometreStraightTravelRetainsBothEndpointsWithoutExhaustingTheBudget(bool smallNoise)
{
var r=Rig();var origin=new Vector3D(-67700,-123600,-162000);
for(int n=0;n<=500;n++)Record(r,origin+Vector3D.Right*(n*10.1)+Vector3D.Up*(smallNoise && n>1?Math.Sin(n)*.05:0));
Assert.Equal(P.FlightState.Transit,r.StateNow);Assert.Equal(2,Path(r).Count);
Assert.Equal(origin,Path(r)[0]);Assert.Equal(r.Position,Path(r)[1]);
}
[Theory]
[InlineData(90)]
[InlineData(180)]
[InlineData(1)]
public void ARealCornerOrReversalKeepsItsTurningPoint(double degrees)
{
var r=Rig();Record(r,Vector3D.Zero);Record(r,Vector3D.Right*11);Record(r,Vector3D.Right*22);
var corner=r.Position;var direction=new Vector3D(Math.Cos(degrees*Math.PI/180),Math.Sin(degrees*Math.PI/180),0);
Record(r,corner+direction*11);Record(r,corner+direction*22);
Assert.Equal(3,Path(r).Count);Assert.Equal(corner,Path(r)[1]);Assert.Equal(r.Position,Path(r)[2]);
}
[Fact]
public void AGradualCurveCannotAccumulateUnboundedDeviationFromRetainedChords()
{
var r=Rig();var samples=new List<Vector3D>();
// Adjacent headings differ by only 0.063 degrees; repeatedly refitting the
// last chord would otherwise hide an increasingly large circular arc.
for(int n=0;n<=300;n++)
{double angle=n*.0011;var point=new Vector3D(10000*Math.Sin(angle),10000*(1-Math.Cos(angle)),0);samples.Add(point);Record(r,point);}
Assert.Equal(P.FlightState.Transit,r.StateNow);Assert.InRange(Path(r).Count,20,127);
int index=0;
for(int n=1;n<Path(r).Count;n++)
{
var start=Path(r)[n-1];var end=Path(r)[n];var delta=end-start;
while(index<samples.Count)
{
var point=samples[index++];double t=Math.Max(0,Math.Min(1,Vector3D.Dot(point-start,delta)/delta.LengthSquared()));
Assert.InRange(Vector3D.Distance(point,start+delta*t),0,.200001);
if(point==end)break;
}
}
Assert.Equal(samples.Count,index);
}
[Fact]
public void RepeatedRealTurnsStillUseTheExisting128PointReturnBudget()
{
var r=Rig();var point=Vector3D.Zero;Record(r,point);
for(int n=1;n<=128;n++){point+=(n%2==0?Vector3D.Right:Vector3D.Up)*11;Record(r,point);}
Assert.Equal(128,Path(r).Count);Assert.Equal(P.FlightState.Returning,r.StateNow);
Assert.Equal(P.L.MinerRouteBudgetFull,Read<string>(r.Miner,"reason"));Assert.Equal(127,Read<int>(r.Miner,"returnIndex"));
}
[Fact]
public void RestoringACompressedPathKeepsItsPriorSegmentAndStartsANewBearing()
{
var r=Rig();Record(r,Vector3D.Zero);Record(r,Vector3D.Right*11);Record(r,Vector3D.Right*1000);
var saved=new MyIni();r.Miner.Save(saved);Assert.Equal(2,saved.Get("miner","BreadcrumbCount").ToInt32());
var restored=Rig();restored.Miner.Load(saved);Assert.Equal(Path(r),Path(restored));
restored.State(P.FlightState.Transit);Record(restored,Vector3D.Right*1011);Record(restored,Vector3D.Right*1022);
Assert.Equal(3,Path(restored).Count);Assert.Equal(Vector3D.Right*1000,Path(restored)[1]);
}
[Theory]
[InlineData(P.FlightState.Returning,1,0,2)]
[InlineData(P.FlightState.Returning,-1,0,0)]
[InlineData(P.FlightState.Holding,-1,0,0)]
[InlineData(P.FlightState.DockAlign,-1,0,0)]
[InlineData(P.FlightState.DockApproach,-1,0,0)]
[InlineData(P.FlightState.DockRetreat,-1,2,0)]
[InlineData(P.FlightState.Fault,-1,3,0)]
[InlineData(P.FlightState.Transit,-1,1,3)]
[InlineData(P.FlightState.Drilling,-1,0,3)]
[InlineData(P.FlightState.Fault,-1,0,3)]
public void ReloadResumesOnlyUnfinishedReturnLegsAndRetainsOutboundPaths(P.FlightState state,int remaining,int failures,int expected)
{
var r=Rig();var points=new[]{Vector3D.Zero,Vector3D.Right*20,Vector3D.Right*20+Vector3D.Up*1000};
Path(r).AddRange(points);r.State(state);Write(r.Miner,"returnIndex",remaining);Write(r.Miner,"retry",failures);
var saved=new MyIni();r.Miner.Save(saved);
Assert.Equal(3,Path(r).Count);Assert.Equal(expected,saved.Get("miner","BreadcrumbCount").ToInt32());
var restored=Rig();restored.Connector.Set("Status",MyShipConnectorStatus.Unconnected);restored.Miner.Load(saved);
Assert.Equal(expected,Path(restored).Count);
for(int n=0;n<expected;n++)Assert.Equal(points[n],Path(restored)[n]);
restored.Miner.Command("return");
Assert.Equal(P.FlightState.Returning,restored.StateNow);Assert.Equal(expected-1,Read<int>(restored.Miner,"returnIndex"));
Assert.Null(Read<P.DockFrame>(restored.Miner,"dock"));
}
[Fact]
public void ReturningAlongACompressedSegmentStillRequiresFreshEnvelopeRays()
{
var r=Rig();for(int n=0;n<=200;n++)Record(r,Vector3D.Forward*(n*11));
r.State(P.FlightState.Returning);Write(r.Miner,"returnIndex",Path(r).Count-1);
r.Controller.Method("get_WorldMatrix",a=>MatrixD.CreateWorld(r.Position,Vector3D.Backward,Vector3D.Up));
bool ready=false;int rays=0;
r.Camera.Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d)
.Method("get_WorldMatrix",a=>r.Controller.Value.WorldMatrix).Method("CanScan",a=>ready)
.Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);});
var flight=Read<P.FlightController>(r.Miner,"flight");
for(int n=0;n<30;n++){r.Program.Now+=.05;Call(r.Miner,"ReturnHome",.05);}
Assert.Equal(0,rays);Assert.Equal(0,flight.CommandSpeed);Assert.All(r.Hardware.Thrusters,t=>Assert.Equal(0,t.ThrustOverridePercentage));
ready=true;for(int n=0;n<15;n++){r.Program.Now+=.05;Call(r.Miner,"ReturnHome",.05);}
Assert.True(rays>=18);Assert.True(flight.ScanClearance>0);Assert.True(flight.CommandSpeed>0);
}
}
}
using System;
using System.Collections.Generic;
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 BreadcrumbCompressionTests
{
static readonly BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic;
static T Read<T>(object target,string name)=> (T)target.GetType().GetField(name,Private).GetValue(target);
static void Write(object target,string name,object value)=>target.GetType().GetField(name,Private).SetValue(target,value);
static void Call(object target,string name,params object[] arguments)=>target.GetType().GetMethod(name,Private).Invoke(target,arguments);
static List<Vector3D> Path(MinerStateTests.MinerRig r)=>Read<List<Vector3D>>(r.Miner,"breadcrumbs");
static MinerStateTests.MinerRig Rig()
{var r=new MinerStateTests.MinerRig();r.Task();r.State(P.FlightState.Transit);return r;}
static void Record(MinerStateTests.MinerRig r,Vector3D point)
{r.Position=point;Call(r.Miner,"RecordBreadcrumb");}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void FiveKilometreStraightTravelRetainsBothEndpointsWithoutExhaustingTheBudget(bool smallNoise)
{
var r=Rig();var origin=new Vector3D(-67700,-123600,-162000);
for(int n=0;n<=500;n++)Record(r,origin+Vector3D.Right*(n*10.1)+Vector3D.Up*(smallNoise && n>1?Math.Sin(n)*.05:0));
Assert.Equal(P.FlightState.Transit,r.StateNow);Assert.Equal(2,Path(r).Count);
Assert.Equal(origin,Path(r)[0]);Assert.Equal(r.Position,Path(r)[1]);
}
[Theory]
[InlineData(90)]
[InlineData(180)]
[InlineData(1)]
public void ARealCornerOrReversalKeepsItsTurningPoint(double degrees)
{
var r=Rig();Record(r,Vector3D.Zero);Record(r,Vector3D.Right*11);Record(r,Vector3D.Right*22);
var corner=r.Position;var direction=new Vector3D(Math.Cos(degrees*Math.PI/180),Math.Sin(degrees*Math.PI/180),0);
Record(r,corner+direction*11);Record(r,corner+direction*22);
Assert.Equal(3,Path(r).Count);Assert.Equal(corner,Path(r)[1]);Assert.Equal(r.Position,Path(r)[2]);
}
[Fact]
public void AGradualCurveCannotAccumulateUnboundedDeviationFromRetainedChords()
{
var r=Rig();var samples=new List<Vector3D>();
// Adjacent headings differ by only 0.063 degrees; repeatedly refitting the
// last chord would otherwise hide an increasingly large circular arc.
for(int n=0;n<=300;n++)
{double angle=n*.0011;var point=new Vector3D(10000*Math.Sin(angle),10000*(1-Math.Cos(angle)),0);samples.Add(point);Record(r,point);}
Assert.Equal(P.FlightState.Transit,r.StateNow);Assert.InRange(Path(r).Count,20,127);
int index=0;
for(int n=1;n<Path(r).Count;n++)
{
var start=Path(r)[n-1];var end=Path(r)[n];var delta=end-start;
while(index<samples.Count)
{
var point=samples[index++];double t=Math.Max(0,Math.Min(1,Vector3D.Dot(point-start,delta)/delta.LengthSquared()));
Assert.InRange(Vector3D.Distance(point,start+delta*t),0,.200001);
if(point==end)break;
}
}
Assert.Equal(samples.Count,index);
}
[Fact]
public void RepeatedRealTurnsStillUseTheExisting128PointReturnBudget()
{
var r=Rig();var point=Vector3D.Zero;Record(r,point);
for(int n=1;n<=128;n++){point+=(n%2==0?Vector3D.Right:Vector3D.Up)*11;Record(r,point);}
Assert.Equal(128,Path(r).Count);Assert.Equal(P.FlightState.Returning,r.StateNow);
Assert.Equal(P.L.MinerRouteBudgetFull,Read<string>(r.Miner,"reason"));Assert.Equal(127,Read<int>(r.Miner,"returnIndex"));
}
[Fact]
public void RestoringACompressedPathKeepsItsPriorSegmentAndStartsANewBearing()
{
var r=Rig();Record(r,Vector3D.Zero);Record(r,Vector3D.Right*11);Record(r,Vector3D.Right*1000);
var saved=new MyIni();r.Miner.Save(saved);Assert.Equal(2,saved.Get("miner","BreadcrumbCount").ToInt32());
var restored=Rig();restored.Miner.Load(saved);Assert.Equal(Path(r),Path(restored));
restored.State(P.FlightState.Transit);Record(restored,Vector3D.Right*1011);Record(restored,Vector3D.Right*1022);
Assert.Equal(3,Path(restored).Count);Assert.Equal(Vector3D.Right*1000,Path(restored)[1]);
}
[Theory]
[InlineData(P.FlightState.Returning,1,0,2)]
[InlineData(P.FlightState.Returning,-1,0,0)]
[InlineData(P.FlightState.Holding,-1,0,0)]
[InlineData(P.FlightState.DockAlign,-1,0,0)]
[InlineData(P.FlightState.DockApproach,-1,0,0)]
[InlineData(P.FlightState.DockRetreat,-1,2,0)]
[InlineData(P.FlightState.Fault,-1,3,0)]
[InlineData(P.FlightState.Transit,-1,1,3)]
[InlineData(P.FlightState.Drilling,-1,0,3)]
[InlineData(P.FlightState.Fault,-1,0,3)]
public void ReloadResumesOnlyUnfinishedReturnLegsAndRetainsOutboundPaths(P.FlightState state,int remaining,int failures,int expected)
{
var r=Rig();var points=new[]{Vector3D.Zero,Vector3D.Right*20,Vector3D.Right*20+Vector3D.Up*1000};
Path(r).AddRange(points);r.State(state);Write(r.Miner,"returnIndex",remaining);Write(r.Miner,"retry",failures);
var saved=new MyIni();r.Miner.Save(saved);
Assert.Equal(3,Path(r).Count);Assert.Equal(expected,saved.Get("miner","BreadcrumbCount").ToInt32());
var restored=Rig();restored.Connector.Set("Status",MyShipConnectorStatus.Unconnected);restored.Miner.Load(saved);
Assert.Equal(expected,Path(restored).Count);
for(int n=0;n<expected;n++)Assert.Equal(points[n],Path(restored)[n]);
restored.Miner.Command("return");
Assert.Equal(P.FlightState.Returning,restored.StateNow);Assert.Equal(expected-1,Read<int>(restored.Miner,"returnIndex"));
Assert.Null(Read<P.DockFrame>(restored.Miner,"dock"));
}
[Fact]
public void ReturningAlongACompressedSegmentStillRequiresFreshEnvelopeRays()
{
var r=Rig();for(int n=0;n<=200;n++)Record(r,Vector3D.Forward*(n*11));
r.State(P.FlightState.Returning);Write(r.Miner,"returnIndex",Path(r).Count-1);
r.Controller.Method("get_WorldMatrix",a=>MatrixD.CreateWorld(r.Position,Vector3D.Backward,Vector3D.Up));
bool ready=false;int rays=0;
r.Camera.Set("RaycastConeLimit",45f).Set("RaycastDistanceLimit",-1d)
.Method("get_WorldMatrix",a=>r.Controller.Value.WorldMatrix).Method("CanScan",a=>ready)
.Method("Raycast",a=>{rays++;return default(MyDetectedEntityInfo);});
var flight=Read<P.FlightController>(r.Miner,"flight");
for(int n=0;n<30;n++){r.Program.Now+=.05;Call(r.Miner,"ReturnHome",.05);}
Assert.Equal(0,rays);Assert.Equal(0,flight.CommandSpeed);Assert.All(r.Hardware.Thrusters,t=>Assert.Equal(0,t.ThrustOverridePercentage));
ready=true;for(int n=0;n<15;n++){r.Program.Now+=.05;Call(r.Miner,"ReturnHome",.05);}
Assert.True(rays>=18);Assert.True(flight.ScanClearance>0);Assert.True(flight.CommandSpeed>0);
}
}
}