using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Sandbox.ModAPI.Ingame; using VRage.Game; using VRageMath; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class SavedMinerNavigationTests { // Actual remote-relative positions and directions from miner 7's 28 cameras, // 2026-09-14 14:41:28 save, grid 87777267991379386. Obstacles below are // controlled wall/railing models; they do not claim to reproduce game physics. static readonly double[,] Cameras={ {1,1,1,0,1,0},{0,1,1.5,0,0,1},{0,1,0,0,0,-1},{2,0,1,1,0,0}, {0,-.5,-.5,0,0,-1},{0,-1.5,.5,0,0,-1},{-2,0,1,-1,0,0},{1.5,0,2,0,0,1}, {-1.5,0,2,0,0,1},{-1,1,1,0,1,0},{1,1,1.5,0,1,0},{-1,1,1.5,0,1,0}, {.5,-1.5,1.5,0,-1,0},{-.5,-1.5,1.5,0,-1,0},{.5,-1.5,1,0,-1,0},{-.5,-1.5,1,0,-1,0}, {0,-1.5,1.5,0,0,1},{1.5,.5,2,0,0,1},{-1.5,.5,2,0,0,1},{0,0,-.5,0,0,-1}, {-.5,1,.5,0,0,-1},{.5,1,.5,0,0,-1},{-.5,1,1.5,0,0,1},{.5,1,1.5,0,0,1}, {-2,0,.5,-1,0,0},{2,0,.5,1,0,0},{-2,0,1.5,-1,0,0},{2,0,1.5,1,0,0} }; static readonly Vector3D[] Axes={Vector3D.Right,Vector3D.Left,Vector3D.Up,Vector3D.Down,Vector3D.Forward,Vector3D.Backward}; const BindingFlags Private=BindingFlags.Instance|BindingFlags.NonPublic; static T Read(NavigationDetourTests.World w,string name) => (T)typeof(P.FlightController).GetField(name,Private).GetValue(w.R.Flight); static bool Covers(MatrixD lens,Vector3D target) { var q=Vector3D.TransformNormal(target-lens.Translation,MatrixD.Transpose(lens)); return -q.Z>0 && Math.Abs(Math.Atan2(q.X,-q.Z))<=Math.PI/4 && Math.Abs(Math.Atan2(q.Y,Math.Sqrt(q.X*q.X+q.Z*q.Z)))<=Math.PI/4; } static NavigationDetourTests.World World(HashSet used,bool wholeGridBounds) { var w=new NavigationDetourTests.World();w.R.Ship.Cameras.Clear(); // All 85 blocks expanded by their installed definition sizes and mounting // orientations, plus a half-cell, from the same ship's 15:01:28 save. w.R.Ship.BodyMin=new Vector3D(-2.25,-1.75,-2.25);w.R.Ship.BodyMax=new Vector3D(2.25,1.25,2.25); w.R.Ship.BodyHalfSize=new Vector3D(2.25,1.75,2.25);w.R.Ship.Radius=4.63146096089488; for(int n=0;nVector3D.Dot(a,direction)>.999); var camera=w.R.MakeCamera(n+1,()=>100000,()=>default(MyDetectedEntityInfo)) .Method("get_WorldMatrix",a=>local*w.R.Orientation).Method("GetPosition",a=>(local*w.R.Orientation).Translation) .Method("CanScan",a=>Covers(local*w.R.Orientation,(Vector3D)a[0]) && Vector3D.Distance((local*w.R.Orientation).Translation,(Vector3D)a[0])<=100000) .Method("Raycast",a=>{ used.Add(id);w.Rays[axis]++;var lens=local*w.R.Orientation;var end=(Vector3D)a[0]; Assert.True(Covers(lens,end));var offset=end-lens.Translation;double length=offset.Length(),nearest=length+1; var ray=new RayD(lens.Translation,offset/length);BoundingBoxD nearestBox=default(BoundingBoxD); var gridMin=new Vector3D(double.MaxValue);var gridMax=new Vector3D(double.MinValue); foreach(var box in w.Obstacles) {gridMin=Vector3D.Min(gridMin,box.Min);gridMax=Vector3D.Max(gridMax,box.Max); var hit=box.Intersects(ray);if(hit.HasValue && hit.Value<=length && hit.Valuelength)return default(MyDetectedEntityInfo); return new MyDetectedEntityInfo(80768303324895649L,"mother ship",MyDetectedEntityType.LargeGrid,ray.Position+ray.Direction*nearest, MatrixD.Identity,Vector3.Zero,MyRelationsBetweenPlayerAndBlock.Owner,wholeGridBounds?new BoundingBoxD(gridMin,gridMax):nearestBox,1); }); w.R.Ship.Cameras.Add(camera.Value); } return w; } static double HullGap(NavigationDetourTests.World w) { // The enclosing world box contains every corner of the full oriented body. // A positive separation proves the entire body is clear, not only its center. var body=new BoundingBoxD(w.R.Ship.BodyMin,w.R.Ship.BodyMax); var min=new Vector3D(double.MaxValue);var max=new Vector3D(double.MinValue); for(int n=0;n<8;n++) {var corner=new Vector3D((n&1)==0?body.Min.X:body.Max.X,(n&2)==0?body.Min.Y:body.Max.Y,(n&4)==0?body.Min.Z:body.Max.Z); var point=Vector3D.Transform(corner,w.R.Orientation);min=Vector3D.Min(min,point);max=Vector3D.Max(max,point);} double nearest=double.MaxValue; foreach(var box in w.Obstacles) { var dx=Math.Max(box.Min.X-max.X,min.X-box.Max.X); var dy=Math.Max(box.Min.Y-max.Y,min.Y-box.Max.Y); var dz=Math.Max(box.Min.Z-max.Z,min.Z-box.Max.Z); nearest=Math.Min(nearest,Math.Max(dx,Math.Max(dy,dz))); } return nearest; } [Theory] [InlineData(false,false,false)] [InlineData(false,true,false)] [InlineData(true,false,false)] [InlineData(true,true,false)] [InlineData(false,false,true)] [InlineData(false,true,true)] [InlineData(true,false,true)] [InlineData(true,true,true)] public void ActualSevenCameraLayoutFindsAScannedRoutePastACloseMotherWall(bool railing,bool actualBraking,bool wholeGridBounds) { var used=new HashSet();var w=World(used,wholeGridBounds); // World integrates force / 1000 kg. Match the save's available 5.24 m/s² // braking acceleration without assuming its unknown per-thruster split. if(actualBraking)foreach(var thrust in w.R.Thrusters)thrust.Set("MaxEffectiveThrust",5240f); var front=w.R.Ship.BodyMin.Z-3; w.Obstacles.Add(new BoundingBoxD(new Vector3D(-30,-3,front-2),new Vector3D(30,3,front))); if(railing) { w.Obstacles.Clear(); w.Obstacles.Add(new BoundingBoxD(new Vector3D(-30,2.7,front-.3),new Vector3D(30,3,front))); for(int n=-5;n<=5;n++)w.Obstacles.Add(new BoundingBoxD(new Vector3D(n*4-.2,-3,front-.3),new Vector3D(n*4+.2,3,front))); } var target=new Vector3D(15,0,-75);bool arrived=false;double gap=HullGap(w);int highestCandidate=0; w.Step(target);Assert.False(w.Turned,w.Diagnostics); for(int n=0;n<7000 && !arrived;n++) { arrived=w.Step(target);gap=Math.Min(gap,HullGap(w));highestCandidate=Math.Max(highestCandidate,Read(w,"detourAttempts")); Assert.True(gap>=.01,"Whole-body gap="+gap+" t="+w.R.Program.Now+" position="+w.Position+" velocity="+w.Velocity+ " waypoint="+w.R.Flight.RouteTarget+" / "+w.R.Flight.Problem+" / "+w.R.Flight.CameraDiagnostics); } Assert.True(arrived,w.Diagnostics);Assert.NotEmpty(w.Waypoints);Assert.True(used.Count>=3,w.Diagnostics); Assert.False(w.TurnedWithoutCoverageGap,w.Diagnostics);Assert.False(w.Collided,w.Diagnostics); Assert.InRange(w.Travel,70,210); Assert.True(w.Waypoints.Any(p=>Math.Abs(p.Y)>4),"Failed to choose a full-body opening; max candidate="+highestCandidate+"\n"+w.Diagnostics); } } }