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

AutoMiningScript

【SpaceEngineer】全自动挖矿脚本

公开
关注 0 Fork 0 Star 0
返回提交历史

SpaceEngineersModDev/AutoMiningScript

Fix fleet exploration capacity and bulk-mining navigation stalls

f2f9797
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

17 个文件 +510 -30
Modified AutoMiningScript.Tests/BulkMiningTests.cs +34 -0
@@ -17,6 +17,29 @@ namespace AutoMiningScript.Tests
17 17 return p.Jobs.Single(j=>j.Kind==P.JobKind.Probe);
18 18 }
19 19 [Theory]
20 [InlineData(8)] [InlineData(12)] [InlineData(16)]
21 public void SpaceExplorationUsesEveryReadyMinerAtADistinctObservation(int count)
22 {
23 var r=new FleetStateTests.Rig(count,true);foreach(var t in r.Miners)t.Radius=4.9;r.RegisterAll();
24 r.Fleet.Command("survey 2000");r.Fleet.Command("start");
25 for(int n=0;n<300;n++){if(n%5==0)foreach(var t in r.Miners)r.Receive("TELEMETRY",t);r.Tick(1);}
26 var jobs=r.Planner.OwnedJobs.Values.ToList();Assert.Equal(count,jobs.Count);
27 Assert.All(jobs,j=>Assert.Equal(P.JobKind.Survey,j.Kind));Assert.Equal(count,jobs.Select(j=>j.Entry).Distinct().Count());
28 for(int i=0;i<jobs.Count;i++)for(int k=i+1;k<jobs.Count;k++)Assert.True(Vector3D.Distance(jobs[i].Entry,jobs[k].Entry)>=11.8);
29 Assert.All(r.Planner.Jobs,j=>Assert.Equal(2000,Vector3D.Distance(Vector3D.Zero,j.Entry+j.Direction*j.Depth),6));
30 }
31 [Theory]
32 [InlineData(0)] [InlineData(12)]
33 public void SpaceObservationGenerationRestoresLegacyAndFleetSizedCursors(int ships)
34 {
35 var p=new P.TaskPlanner(TestRig.Program());p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000,ships);
36 var saved=new MyIni();p.Save(saved);if(ships==0)saved.Set("region0","Rows",0);
37 var after=new P.TaskPlanner(TestRig.Program());after.Load(saved);Drain(p);Drain(after);
38 Assert.Equal(p.Jobs.Select(j=>j.Entry),after.Jobs.Select(j=>j.Entry));
39 for(int n=0;n<p.Jobs.Count;n++)Assert.True(Vector3D.Distance(p.Jobs[n].Direction,after.Jobs[n].Direction)<1e-12);
40 Assert.Equal(Math.Max(7,ships),after.Jobs.Select(j=>j.Entry).Distinct().Count());
41 }
42 [Theory]
20 43 [InlineData(12)] [InlineData(16)]
21 44 public void OneSurfaceHitCanEmployTheWholeReadyFleetWithoutOreConfirmation(int count)
22 45 {
@@ -113,5 +136,16 @@ namespace AutoMiningScript.Tests
113 136 var p=new P.TaskPlanner(TestRig.Program("[Survey]\nMaxDepth=80\nProbeDepth=3\n"));Hit(p);Drain(p);
114 137 Assert.All(p.Jobs.Where(j=>j.Kind==P.JobKind.Probe),j=>Assert.Equal(80,j.Depth));
115 138 }
139
140 [Fact]
141 public void GrazingAsteroidDiscoveryBuildsAnInwardPatchInsteadOfFollowingTheTangentialRay()
142 {
143 var p=new P.TaskPlanner(TestRig.Program());p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);
144 var hit=new Vector3D(100,100,-700);var center=new Vector3D(200,250,-560);
145 p.Report(p.Jobs[0],new P.JobReport {Outcome="SurveyHit",Position=hit,Direction=new Vector3D(.2,-.1,.97),EntityId=42,
146 Scan=new P.ScanRecord {Start=Vector3D.Zero,End=hit,Center=center,Size=new Vector3D(512),Asteroid=42}});Drain(p);
147 var direction=Vector3D.Normalize(center-hit);var holes=p.Jobs.Where(j=>j.Kind==P.JobKind.Probe).ToList();Assert.Equal(145,holes.Count);
148 Assert.All(holes,j=>{Assert.True(Vector3D.Distance(direction,j.Direction)<1e-12);Assert.Equal(100,j.Depth);Assert.True(Vector3D.Distance(hit,j.Entry)<=43);});
149 }
116 150 }
117 151 }
Modified AutoMiningScript.Tests/CameraReadinessTests.cs +18 -0
@@ -394,6 +394,24 @@ namespace AutoMiningScript.Tests
394 394 Assert.True(selfHits>=18);Assert.True(clearHits>=18);
395 395 }
396 396
397 [Fact]
398 public void ACameraSeeingItsOwnHullCannotPreventTurningToAnUnobstructedLens()
399 {
400 var r=new ReturnNavigationTests.Rig();int sideHits=0;
401 r.Hit=()=>ReturnNavigationTests.Rig.Detection(r.Program.Me.CubeGrid.EntityId);
402 var mount=MatrixD.CreateWorld(Vector3D.Zero,Vector3D.Right,Vector3D.Up);
403 r.Ship.Cameras.Add(Camera(r,"Unobstructed side",2,()=>mount*r.Orientation)
404 .Method("Raycast",a=>{sideHits++;return default(MyDetectedEntityInfo);}).Value);
405 for(int n=0;n<80 && r.Flight.ScanClearance==0;n++)
406 {
407 r.Program.Now+=.05;r.Flight.TrafficAvoid=true;r.Flight.TrafficAt=r.Program.Now;
408 r.Flight.Move(Vector3D.Forward*2,Vector3D.Zero,r.Orientation.Forward,r.Orientation.Up,1.5,.3,true,.05,false,true,0,true);
409 r.Orientation=Field<MatrixD>(r.Flight,"previousDesired").GetOrientation();
410 }
411 Assert.True(sideHits>=18,r.Flight.Problem);Assert.True(r.HasTranslation);
412 Assert.True((r.Orientation.Forward-Vector3D.Forward).Length()>.1);
413 }
414
397 415 [Fact]
398 416 public void ACameraBlockedForOnePointCanStillCoverOtherPoints()
399 417 {
Modified AutoMiningScript.Tests/DockingNavigationTests.cs +40 -2
@@ -835,6 +835,24 @@ namespace AutoMiningScript.Tests
835 835 Assert.Equal(0,r.ConnectCalls);
836 836 }
837 837
838 [Theory]
839 [InlineData(true)]
840 [InlineData(false)]
841 public void TrafficSideStepUsesSideCamerasAndTurnsOnlyIfCoverageIsMissing(bool sideCamera)
842 {
843 var r=new DockRig();r.R.Position=r.Contact+Vector3D.Forward*1000;r.R.State(P.FlightState.Returning);
844 if(sideCamera)r.AddCamera(Vector3D.Right);
845 var start=r.R.Position;var target=start+Vector3D.Right*2;double turned=0;
846 for(int n=0;n<160 && r.R.Position.X<start.X+1;n++)
847 {
848 r.R.Program.Now+=.05;r.Flight.TrafficAvoid=true;r.Flight.TrafficAt=r.R.Program.Now;
849 r.Flight.TrafficPoint=target;Call(r.R.Miner,"YieldAtPort",.05);r.Integrate(.05);
850 turned=Math.Max(turned,(r.Orientation.Forward-Vector3D.Forward).Length());
851 }
852 Assert.True(r.Rays>=18);Assert.True(r.R.Position.X>start.X+1,r.Reason);
853 if(sideCamera)Assert.InRange(turned,0,.001);else Assert.True(turned>.1);
854 }
855
838 856 [Fact]
839 857 public void TrafficSideStepScansTheMotherHullWithoutTheDockContactExemption()
840 858 {
@@ -843,13 +861,16 @@ namespace AutoMiningScript.Tests
843 861 r.Flight.SetDockContact(50,wall,100);
844 862 r.Hit=end=>new MyDetectedEntityInfo(50,"Mother side wall",MyDetectedEntityType.LargeGrid,wall,MatrixD.Identity,Vector3.Zero,
845 863 VRage.Game.MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(wall-new Vector3D(.1),wall+new Vector3D(.1)),1);
864 bool sawWall=false;
846 865 for(int n=0;n<80;n++)
847 866 {
848 867 r.R.Program.Now+=.05;r.Flight.TrafficAvoid=true;r.Flight.TrafficAt=r.R.Program.Now;
849 868 r.Flight.TrafficPoint=r.R.Position+Vector3D.Right*2;Call(r.R.Miner,"YieldAtPort",.05);
869 sawWall|=r.Flight.Problem.Contains("Mother side wall");
850 870 }
851 Assert.True(r.Rays>0);Assert.Equal(0,r.Flight.CommandSpeed);Assert.Contains("Mother side wall",r.Flight.Problem);
852 Assert.Contains(P.L.MinerObstacleBraking,r.Flight.Problem);Assert.Equal(0,r.ConnectCalls);
871 Assert.True(r.Rays>0);Assert.True(sawWall);Assert.Equal(0,r.Flight.CommandSpeed);
872 Assert.Equal(P.L.MinerFindingRoute,r.Flight.Problem);Assert.Equal(0,r.ConnectCalls);
873 Assert.InRange(Vector3D.Distance(r.R.Position,r.Flight.RouteTarget),1.9,3);
853 874 }
854 875
855 876 [Fact]
@@ -869,6 +890,23 @@ namespace AutoMiningScript.Tests
869 890 Assert.True(passed);Assert.Equal(0,Read<int>(r.R.Miner,"returnIndex"));
870 891 }
871 892
893 [Theory]
894 [InlineData(false)]
895 [InlineData(true)]
896 public void ReturnRejoinsANearbyEarlierTrailButStillScansTheShortcut(bool blocked)
897 {
898 var r=new DockRig();r.R.State(P.FlightState.Returning);Write(r.R.Miner,"lane","");
899 r.R.Hardware.Radius=4.8;r.R.Position=r.Contact+Vector3D.Forward*1000;var start=r.R.Position;
900 var path=Read<List<Vector3D>>(r.R.Miner,"breadcrumbs");path.Clear();
901 path.AddRange(new[]{start+Vector3D.Forward*40,start+Vector3D.Forward*8,
902 start+Vector3D.Forward*8+Vector3D.Right*20,start+Vector3D.Right*20,start+Vector3D.Forward*20});
903 Write(r.R.Miner,"returnIndex",4);
904 if(blocked)r.Hit=end=>SphereHit(r,start+Vector3D.Forward*5,2,end);
905 for(int n=0;n<18;n++)r.Tick();
906 Assert.Equal(0,Read<int>(r.R.Miner,"returnIndex"));Assert.True(r.Rays>0);
907 if(blocked)Assert.Equal(0,r.Flight.CommandSpeed);else Assert.True(r.Flight.CommandSpeed>1);
908 }
909
872 910 [Fact]
873 911 public void ASharpReturnCornerIsNotCutByWaypointBlending()
874 912 {
Modified AutoMiningScript.Tests/MinerStateTests.cs +33 -0
@@ -120,6 +120,39 @@ namespace AutoMiningScript.Tests
120 120 Assert.False(r.Drill.Value.Enabled);
121 121 }
122 122
123 [Theory]
124 [InlineData(false)] [InlineData(true)]
125 public void BulkEntryInspectsTheWholeHoleAndAllowsUnevenOrEmptyEdges(bool emptyEdges)
126 {
127 var r=new MinerRig();r.Task();r.Position=Vector3D.Backward*22;r.State(P.FlightState.Align);int rays=0;
128 r.Camera.Method("Raycast",a=>
129 {
130 var end=(Vector3D)a[0];Assert.Equal(40,-end.Z,6);int ray=rays++;
131 if(emptyEdges && ray!=4)return default(MyDetectedEntityInfo);
132 return new MyDetectedEntityInfo(42,"asteroid",MyDetectedEntityType.Asteroid,Vector3D.Forward*(12+ray*6),MatrixD.Identity,Vector3.Zero,
133 MyRelationsBetweenPlayerAndBlock.NoOwnership,new BoundingBoxD(new Vector3D(-100),new Vector3D(100)),1);
134 });
135 for(int n=0;n<5;n++)Call(r.Miner,"Align",.1);
136 Assert.Equal(5,rays);Assert.Equal(P.FlightState.Drilling,r.StateNow);Assert.Equal(0,Read<P.Job>(r.Miner,"job").Progress);
137 }
138
139 [Fact]
140 public void BulkEntryWithFiveEmptyRaysDoesNotStartBlindDrilling()
141 {
142 var r=new MinerRig();r.Task();r.Position=Vector3D.Backward*22;r.State(P.FlightState.Align);
143 for(int n=0;n<5;n++)Call(r.Miner,"Align",.1);
144 Assert.NotEqual(P.FlightState.Drilling,r.StateNow);Assert.False(r.Drill.Value.Enabled);
145 Assert.Contains(r.Sent,p=>p.Kind=="RESULT" && P.JobReport.FromIni(p.Body).Message==P.L.MinerEntryVoxelUnconfirmed);
146 }
147
148 [Fact]
149 public void ManualEntryRetainsItsShortRangeSurfaceCheck()
150 {
151 var r=new MinerRig();r.Task();Read<P.Job>(r.Miner,"job").Kind=P.JobKind.Manual;r.Position=Vector3D.Backward*22;r.State(P.FlightState.Align);
152 r.Camera.Method("Raycast",a=>{Assert.Equal(5,-((Vector3D)a[0]).Z,6);return default(MyDetectedEntityInfo);});
153 Call(r.Miner,"Align",.1);Assert.NotEqual(P.FlightState.Drilling,r.StateNow);Assert.False(r.Drill.Value.Enabled);
154 }
155
123 156 [Theory]
124 157 [InlineData(false,true)]
125 158 [InlineData(false,false)]
Modified AutoMiningScript.Tests/PortYieldSafetyTests.cs +7 -0
@@ -58,6 +58,13 @@ namespace AutoMiningScript.Tests
58 58 Assert.True(P.Data.Flag(resumed.Body,"traffic","Avoid"));
59 59 Assert.Equal(priority.Id,P.Data.Text(resumed.Body,"traffic","Peer"));
60 60 Assert.Equal(point,P.Data.ReadVector(resumed.Body,"traffic","Point",Vector3D.Zero));
61
62 // The same third ship has stopped: the loser may now scan for a short
63 // detour instead of freezing a stationary cluster indefinitely.
64 third.Position=yielding.Position+lateral*8;third.Velocity=Vector3D.Zero;
65 ObserveFrame(rig);
66 var scan=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
67 Assert.True(P.Data.Flag(scan.Body,"traffic","Avoid"));Assert.False(P.Data.Flag(scan.Body,"traffic","Wait"));
61 68 }
62 69
63 70 static FleetStateTests.Rig Encounter(Vector3D first,Vector3D second,Vector3D firstVelocity,Vector3D secondVelocity,double thrustMargin=100,double response=.625)
Modified AutoMiningScript.Tests/SurveyNavigationTests.cs +14 -0
@@ -258,5 +258,19 @@ namespace AutoMiningScript.Tests
258 258 {Call(r.R.Miner,"Align",.05);r.Orientation=Read<MatrixD>(r.Flight,"previousDesired").GetOrientation();}
259 259 Assert.Equal(P.FlightState.Drilling,r.R.StateNow);
260 260 }
261
262 [Fact]
263 public void MiningApproachCannotLoopForeverOnLocallyValidDetours()
264 {
265 var r=new SurveyRig();r.Job.Kind=P.JobKind.Probe;r.R.Camera.Method("CanScan",a=>true);
266 var approach=Vector3D.Backward*22;
267 for(int n=0;n<1300 && !r.R.Sent.Any(p=>p.Kind=="RESULT");n++)
268 {
269 r.R.Position=approach+new Vector3D(Math.Cos(n*.05)*12,0,Math.Sin(n*.05)*12);r.Tick(false);
270 }
271 var report=P.JobReport.FromIni(Assert.Single(r.R.Sent,p=>p.Kind=="RESULT").Body);
272 Assert.Equal("Blocked",report.Outcome);Assert.False(report.EntryUnreachable);Assert.Null(report.Scan);
273 Assert.InRange(r.R.Program.Now,45,46);Assert.True(Read<bool>(r.R.Miner,"continuing"));
274 }
261 275 }
262 276 }
Modified AutoMiningScript/FleetController.cs +6 -3
@@ -365,7 +365,10 @@ namespace AutoMiningScript
365 365 foreach(var third in miners.Values)
366 366 {
367 367 if(!P.HasBudget(.6))return;
368 if(third.Id!=peer.Id && NearTraffic(t,third)){TrafficMessage(t,true,false,yielding.Point,third.Id);return;}
368 // A moving third ship gets braking priority. For slow/static
369 // neighbours the yielding miner scans a short free detour;
370 // stopping every member of a cluster cannot create clearance.
371 if(third.Id!=peer.Id && Data.Length(third.Velocity)>.3 && NearTraffic(t,third)){TrafficMessage(t,true,false,yielding.Point,third.Id);return;}
369 372 }
370 373 if(Data.Distance(t.Position,yielding.Point)<.5 || Data.Dot(yielding.Point-t.Position,t.Position-peer.Position)<0)
371 374 yielding.Point=t.Position+YieldSide(t,peer)*2;
@@ -515,7 +518,7 @@ namespace AutoMiningScript
515 518 else
516 519 {
517 520 if (controller == null) { problem = L.FleetSurveyControllerRequired; return; }
518 P.Log(planner.Survey(controller.GetPosition(), controller.GetNaturalGravity(), controller.WorldMatrix.Forward, controller.WorldMatrix.Up, Data.ReadNumber(packet.Body, "survey", "Radius")));
521 P.Log(planner.Survey(controller.GetPosition(), controller.GetNaturalGravity(), controller.WorldMatrix.Forward, controller.WorldMatrix.Up, Data.ReadNumber(packet.Body, "survey", "Radius"),miners.Count));
519 522 }
520 523 markerReceiptSet.Add(receipt); markerReceipts.Enqueue(receipt);
521 524 while (markerReceipts.Count > 128) markerReceiptSet.Remove(markerReceipts.Dequeue());
@@ -824,7 +827,7 @@ namespace AutoMiningScript
824 827 {
825 828 if (controller == null) throw Data.Invalid(L.FleetSurveyControllerRequired);
826 829 var radius = parts.Length > 1 ? Marking.Number(parts[1]) : 0;
827 P.Log(planner.Survey(controller.GetPosition(), controller.GetNaturalGravity(), controller.WorldMatrix.Forward, controller.WorldMatrix.Up, radius)); return;
830 P.Log(planner.Survey(controller.GetPosition(), controller.GetNaturalGravity(), controller.WorldMatrix.Forward, controller.WorldMatrix.Up, radius,miners.Count)); return;
828 831 }
829 832 if (verb == "start" || verb == ResumeCommand || verb == "pause" || verb == ReturnCommand || verb == "release" || verb == ReturnAllCommand)
830 833 {
Modified AutoMiningScript/Flight.cs +3 -3
@@ -126,7 +126,7 @@ namespace AutoMiningScript
126 126 }
127 127
128 128 public bool Move(Vector3D target, Vector3D targetVelocity, Vector3D forward, Vector3D up,
129 double speed, double tolerance, bool scan, double dt, bool faceTravel = false, bool allowDetour = true, double minimumSpeed = 0)
129 double speed, double tolerance, bool scan, double dt, bool faceTravel = false, bool allowDetour = true, double minimumSpeed = 0, bool turnForCamera = false)
130 130 {
131 131 Blocked = false; Problem = ""; holdActive = false; CommandSpeed = 0;
132 132 if (h.Controller == null) { Blocked = true; Problem = L.MinerMissingController; return false; }
@@ -221,7 +221,7 @@ namespace AutoMiningScript
221 221 Problem = scanFailed ? (cameraProblem.Length>0?cameraProblem:L.MinerCameraUnavailable) : L.MinerEnvelopeScanPending+" "+scanIndex+"/18";
222 222 }
223 223 }
224 if(faceTravel && scan && scanFailed && !scanWaitingForCharge)desired=h.CameraAttitude(cameraTarget,up);
224 if((faceTravel || turnForCamera) && scan && scanFailed && !scanWaitingForCharge)desired=h.CameraAttitude(cameraTarget,up,occludedCameras);
225 225 if (faceTravel && !CanSupport(desired.Forward, desired.Up, 0.1))
226 226 {
227 227 var failure = Problem;
@@ -245,7 +245,7 @@ namespace AutoMiningScript
245 245 void FindDetour(Vector3D position,Vector3D target)
246 246 {
247 247 if(breadcrumbs.Count==16)breadcrumbs.RemoveAt(0);breadcrumbs.Add(position);
248 routeOptions.Clear();double step=Data.Max(6,h.Radius*3+5);
248 routeOptions.Clear();double step=TrafficBraking?Data.Max(2,h.Radius*.6):Data.Max(6,h.Radius*3+5);
249 249 for(int x=-1;x<=1;x++)for(int y=-1;y<=1;y++)for(int z=-1;z<=1;z++)
250 250 if(x!=0 || y!=0 || z!=0)routeOptions.Add(position+Data.TransformNormal(Data.Unit(new Vector3D(x,y,z),basis.Forward),basis)*step);
251 251 routeOptions.Sort((a,b)=>RouteCost(a,target,step).CompareTo(RouteCost(b,target,step)));
Modified AutoMiningScript/Hardware.cs +2 -2
@@ -293,12 +293,12 @@ namespace AutoMiningScript
293 293 Math.Abs(Math.Atan2(ray.X,-ray.Z))<=cone && Math.Abs(Math.Atan2(ray.Y,Math.Sqrt(ray.X*ray.X+ray.Z*ray.Z)))<=cone);
294 294 }
295 295
296 public MatrixD CameraAttitude(Vector3D target, Vector3D up)
296 public MatrixD CameraAttitude(Vector3D target, Vector3D up, HashSet<long> occluded = null)
297 297 {
298 298 var current = Controller.WorldMatrix; IMyCameraBlock best = null; double score = -2;
299 299 foreach (var camera in Cameras)
300 300 {
301 if (!IsLocal(camera) || !camera.IsWorking) continue;
301 if (!IsLocal(camera) || !camera.IsWorking || (occluded!=null && occluded.Contains(camera.EntityId))) continue;
302 302 if (CameraCovers(camera,target)) return current;
303 303 var alignment = Data.Dot(camera.WorldMatrix.Forward,Data.Unit(target-camera.GetPosition(),current.Forward));
304 304 if (alignment>score) { score=alignment; best=camera; }
Modified AutoMiningScript/MinerController.cs +24 -12
@@ -160,8 +160,9 @@ namespace AutoMiningScript
160 160 if(Data.LengthSquared(step)<.01)step=Data.Perpendicular(normal,ship.Controller.WorldMatrix.Up)*2;
161 161 point=Position+step;
162 162 }
163 var f=near?ship.Controller.WorldMatrix.Forward:Data.Unit(point-Position,ship.Controller.WorldMatrix.Forward);
164 flight.Move(point,near?dock.PointVelocity(point):Data.Zero,f,near?ship.Controller.WorldMatrix.Up:TravelUp(f),1.5,.3,true,dt,!near,false);
163 // A short lateral escape uses the cameras on that side; turning
164 // the whole hull repeatedly invalidates scans and blocks both ships.
165 flight.Move(point,near?dock.PointVelocity(point):Data.Zero,ship.Controller.WorldMatrix.Forward,ship.Controller.WorldMatrix.Up,1.5,.3,true,dt,false,true,0,!near);
165 166 }
166 167 else if(near)flight.Move(Position,dock.PointVelocity(Position),ship.Controller.WorldMatrix.Forward,ship.Controller.WorldMatrix.Up,0,.3,false,dt);
167 168 else flight.Hold(dt);
@@ -170,7 +171,7 @@ namespace AutoMiningScript
170 171 double grace=Data.Min(Data.Max(0,dt),Data.Max(0,10-trafficDelay));trafficDelay+=grace;progressAt+=grace;
171 172 stableSince=-1;reason=L.F(L.MinerTrafficYield,flight.TrafficPeer);
172 173 if(flight.TrafficAvoid && flight.Problem.Length>0)reason+=" / "+flight.Problem;
173 if(SurveyTransitResult(dt,false))return true;
174 if(TransitResult(dt,false))return true;
174 175 if(state==FlightState.DockAlign || state==FlightState.DockApproach)CheckDockTimeout();
175 176 return true;
176 177 }
@@ -246,22 +247,23 @@ namespace AutoMiningScript
246 247 // Observation rays may point sideways from the arrival path. Keep the
247 248 // travel camera facing the actual leg (including detours) until stopped.
248 249 var arrived=flight.Move(target,Data.Zero,face,Data.Perpendicular(face,cruiseUp),CruiseSpeed,surveying?1:.4,true,dt,true);
249 if(SurveyTransitResult(dt,arrived))return;
250 if(TransitResult(dt,arrived))return;
250 251 reason=L.F(L.MinerDockNavigationProgress,L.MinerTravelTaskEntry,distance,Data.Length(ship.Controller.GetShipVelocities().LinearVelocity),flight.AttitudeError*180/Math.PI);
251 252 if(flight.Problem.Length>0)reason=flight.Problem+" / "+reason;
252 253 RecordBreadcrumb();
253 if(!surveying && flight.Blocked && P.Now-stateSince>45) {FinishBlocked(L.MinerRouteBlocked+" / "+flight.Problem);return;}
254 254 if(arrived)Set(surveying?FlightState.Survey:FlightState.Align,L.MinerTaskEntryReached);
255 255 }
256 bool SurveyTransitResult(double dt,bool arrived)
256 bool TransitResult(double dt,bool arrived)
257 257 {
258 if(state!=FlightState.Transit || job==null || job.Kind!=JobKind.Survey)return false;
259 double distance=Data.Distance(Position,job.Entry);
258 if(state!=FlightState.Transit || job==null)return false;
259 bool survey=job.Kind==JobKind.Survey;Vector3D f,u;
260 double distance=Data.Distance(Position,survey?job.Entry:TipTarget(-Approach,out f,out u));
260 261 // Detours/yields must actually advance toward the observation entry.
261 262 if(distance<lastProgress-2){lastProgress=distance;progressAt=P.Now;trafficDelay=0;}
262 263 // A real safety-camera surface observation takes precedence over timeout.
263 if(flight.SurfaceHit!=null){var hit=flight.SurfaceHit;hit.JobId=job.Id;CompleteJob(hit);return true;}
264 if(survey && flight.SurfaceHit!=null){var hit=flight.SurfaceHit;hit.JobId=job.Id;CompleteJob(hit);return true;}
264 265 if(arrived || P.Now-progressAt<=45)return false;
266 if(!survey){flight.Hold(dt);FinishBlocked(L.MinerRouteBlocked+" / "+flight.Problem);return true;}
265 267 flight.Hold(dt);var report=MakeReport("Blocked","",L.MinerSurveyEntryStalled);report.EntryUnreachable=true;CompleteJob(report);return true;
266 268 }
267 269 void Align(double dt)
@@ -280,7 +282,11 @@ namespace AutoMiningScript
280 282 else if(entryProbe==2)offset=-right*halfWidth;
281 283 else if(entryProbe==3)offset=job.Up*halfHeight;
282 284 else if(entryProbe==4)offset=-job.Up*halfHeight;
283 var test=job.Entry+offset+job.Direction*Data.Min(job.Depth,Data.Max(relief,job.Progress+3));
285 // Bulk holes share a reference plane, not a flat asteroid surface.
286 // Inspect the whole reserved depth; empty edges are valid if another
287 // ray confirms the voxel and the drills cover the complete hull.
288 bool bulk=job.Kind==JobKind.Probe;
289 var test=job.Entry+offset+job.Direction*(bulk?job.Depth:Data.Min(job.Depth,Data.Max(relief,job.Progress+3)));
284 290 foreach(var camera in ship.Cameras)
285 291 {
286 292 if(!camera.IsWorking || camera.CubeGrid!=P.Me.CubeGrid || !camera.CanScan(test))continue;
@@ -294,9 +300,10 @@ namespace AutoMiningScript
294 300 }
295 301 if(!ready) {reason=L.MinerWaitEntryScan;if(P.Now-stateSince>60)FinishBlocked(reason);return;}
296 302 if(obstructed) {FinishBlocked(L.MinerEntryOccupied);return;}
297 if(!voxel && job.Progress<=0) {FinishBlocked(L.MinerEntryVoxelUnconfirmed);return;}
298 if(job.Progress<=0 && entryFarthest-entryNearest>relief) {FinishBlocked(L.MinerEntryReliefExcessive);return;}
303 if(!bulk && !voxel && job.Progress<=0) {FinishBlocked(L.MinerEntryVoxelUnconfirmed);return;}
304 if(!bulk && job.Progress<=0 && entryFarthest-entryNearest>relief) {FinishBlocked(L.MinerEntryReliefExcessive);return;}
299 305 if(++entryProbe<5) {reason=L.F(L.MinerEntryScanProgress, entryProbe);return;}
306 if(bulk && entryNearest==double.MaxValue && job.Progress<=0) {FinishBlocked(L.MinerEntryVoxelUnconfirmed);return;}
300 307 ship.SetSampling(true);ResetSample();sampleDepth=job.Progress;lastProgress=job.Progress;progressAt=P.Now;
301 308 Set(FlightState.Drilling,L.MinerDrillingAxis);
302 309 lastProgress=Data.Dot(DrillTip()-job.Entry,job.Direction);progressAt=P.Now;
@@ -459,6 +466,11 @@ namespace AutoMiningScript
459 466 {returnIndex=-1;Set(FlightState.DockAlign,L.MinerDockOuterEntry,true);DockAlign(dt);return;}
460 467 if(returnIndex>=0)
461 468 {
469 // Entry searches can leave loops in the trail. Rejoin its earliest
470 // nearby point instead of replaying the loop; Move still scans the
471 // entire shortcut and finds a detour if its actual space is occupied.
472 for(int n=0;n<returnIndex;n++)if(Data.Distance(Position,breadcrumbs[n])<ship.Radius*2)
473 {returnIndex=n;flight.ResetRoute();break;}
462 474 // Look beyond the cruise braking distance, not just the next 10 m
463 475 // sample. Preserve sharp corners; each blended leg still needs scans.
464 476 double speed=CruiseSpeed,look=speed*1.5+speed*speed/(2*Data.Max(.1,flight.BrakeAcceleration*.65));
Modified AutoMiningScript/Planning.cs +17 -6
@@ -196,7 +196,7 @@ namespace AutoMiningScript
196 196 AddRegion(r);Generate(r);GenerateStep();
197 197 return L.F(L.FleetManualQueued,r.Id,nx*ny);
198 198 }
199 public string Survey(Vector3D center,Vector3D gravity,Vector3D forward,Vector3D up,double requestedRadius)
199 public string Survey(Vector3D center,Vector3D gravity,Vector3D forward,Vector3D up,double requestedRadius,int ships=0)
200 200 {
201 201 var planet=gravity.LengthSquared()>0.01;
202 202 var radius=requestedRadius>0?requestedRadius:N("Survey","Radius",planet?300:2000,20,20000);
@@ -211,7 +211,7 @@ namespace AutoMiningScript
211 211 }
212 212 else
213 213 {
214 r.Columns=p.Config.Integer("Survey","SpaceRays",42,12,128);r.Total=7*r.Columns;
214 r.Columns=p.Config.Integer("Survey","SpaceRays",42,12,128);r.Rows=Data.Max(7,Data.Min(16,ships));r.Total=r.Rows*r.Columns;
215 215 if(Jobs.Count+reserved+r.Total>Limit) throw Data.Invalid(L.FleetSpaceSurveyLimit);
216 216 r.Generation="Space";
217 217 }
@@ -229,6 +229,11 @@ namespace AutoMiningScript
229 229 else if(k<side*3) {x=ring-(k-side*2);y=ring;}
230 230 else {x=-ring;y=ring-(k-side*3);}
231 231 }
232 static Vector3D SkyDirection(MatrixD basis,int ray,int count,double phase=0)
233 {
234 double y=1-2*(ray+.5)/count,phi=(ray+phase)*2.399963229728653,radial=Math.Sqrt(Data.Max(0,1-y*y));
235 return basis.Right*(Math.Cos(phi)*radial)+basis.Up*y+basis.Forward*(Math.Sin(phi)*radial);
236 }
232 237 public void GenerateStep()
233 238 {
234 239 int work=0;
@@ -257,8 +262,8 @@ namespace AutoMiningScript
257 262 {
258 263 int point=index/r.Columns,ray=index%r.Columns;var basis=Data.Frame(r.Center,r.Direction,r.Up);
259 264 var offset=point==1?basis.Right:point==2?basis.Left:point==4?basis.Down:point==5?basis.Forward:point==6?basis.Backward:basis.Up;
260 double y=1-2*(ray+0.5)/r.Columns,phi=ray*2.399963229728653,radial=Math.Sqrt(Data.Max(0,1-y*y));
261 dir=basis.Right*(Math.Cos(phi)*radial)+basis.Up*y+basis.Forward*(Math.Sin(phi)*radial);
265 if(point>=7)offset=SkyDirection(basis,point-7,r.Rows-7,.5);
266 dir=SkyDirection(basis,ray,r.Columns);
262 267 entry+=offset*(point==0?Data.Min(r.BaseClearance,r.Radius*0.2):r.Radius*0.35);
263 268 var local=entry-r.Center;var dot=Data.Dot(local,dir);depth=Data.Min(10000,-dot+Math.Sqrt(dot*dot+r.Radius*r.Radius-local.LengthSquared()));kind=JobKind.Survey;
264 269 }
@@ -321,7 +326,13 @@ namespace AutoMiningScript
321 326 var known=KnownSurface(report);
322 327 if(r!=null&&!known&&Inside(r,report.Position)&&report.EntityId!=0&&report.Direction.LengthSquared()>0.1)
323 328 {
324 var direction=Data.Unit(report.Direction,Data.Forward);var depth=BoundDepth(r,report.Position,direction,N("Survey","MaxDepth",100,1,100));
329 var direction=Data.Unit(report.Direction,Data.Forward);
330 // A grazing discovery ray makes neighbouring holes cut mostly
331 // empty space. Point the patch inward; every hole still checks
332 // actual voxel hits, since the bounds are only an estimate.
333 if(report.Scan!=null && report.Scan.Sized && report.Scan.Asteroid==report.EntityId && Data.Dot(report.Scan.Center-report.Position,direction)>0)
334 direction=Data.Unit(report.Scan.Center-report.Position,direction);
335 var depth=BoundDepth(r,report.Position,direction,N("Survey","MaxDepth",100,1,100));
325 336 if(depth>=1) {var seed=Add(r,JobKind.Probe,report.Position,direction,depth,report.EntityId);if(seed!=null)Expand(seed);}
326 337 }
327 338 }
@@ -498,7 +509,7 @@ namespace AutoMiningScript
498 509 {
499 510 if(r.Generation.Length==0)return;
500 511 if((r.Generation!="Manual"&&r.Generation!="Ground"&&r.Generation!="Space"&&r.Generation!="Mine")||r.Cursor<0||r.Total<1||r.Total>4096||r.Cursor>=r.Total||r.Columns<0||r.Columns>4096||r.Rows<0||r.Rows>4096||r.Spacing<0||r.BaseClearance<0||r.ScanDistance<0)throw Data.Invalid(L.FleetStoredRegionInvalid);
501 if((r.Generation=="Manual"&&(r.Columns<1||r.Rows<1||(long)r.Columns*r.Rows!=r.Total))||(r.Generation=="Space"&&(r.Columns<12||r.Columns>128||r.Total!=7*r.Columns)))throw Data.Invalid(L.FleetStoredRegionInvalid);
512 if((r.Generation=="Manual"&&(r.Columns<1||r.Rows<1||(long)r.Columns*r.Rows!=r.Total))||(r.Generation=="Space"&&(r.Columns<12||r.Columns>128||r.Rows>16||r.Total!=Data.Max(7,r.Rows)*r.Columns)))throw Data.Invalid(L.FleetStoredRegionInvalid);
502 513 if(r.Generation=="Mine" && (!r.Bulk||!r.Patch||r.Columns<1||r.Rows<1||r.Total>(long)r.Columns*r.Rows))throw Data.Invalid(L.FleetStoredRegionInvalid);
503 514 Generate(r);
504 515 }
Modified README.md +4 -2
@@ -184,7 +184,7 @@ XFE AMS 是 Space Engineers 原版编程块采矿舰队系统:自主推进器
184 184 启动
185 185 ```
186 186
187 `探索` 在主控当前位置冻结一个有限区域;标点端的同名命令也使用主控当前位置。默认大气半径 300 m、太空半径 2000 m,可用参数覆盖。大气按地表采样点搜索,太空按有限观察点及方向扫描小行星。
187 `探索` 在主控当前位置冻结一个有限区域;标点端的同名命令也使用主控当前位置。默认大气半径 300 m、太空半径 2000 m,可用参数覆盖。大气按地表采样点搜索,太空按有限观察点及方向扫描小行星。新建太空搜索区按登记舰队数量提供 7–16 个独立观察点,十二台舰队可同时领取十二个位置;原有搜索区保持其已保存的布局。
188 188
189 189 航行避障与探索扫描都利用已安装的前后、左右、上下各方向镜头。目标方向已有工作镜头覆盖时保留船体姿态;镜头正在充能时等待或使用同方向其他镜头,不因缺电转船。只有目标射线缺少有效视场时,才按实际镜头安装朝向转动探测,并检查该姿态的悬停与制动能力。扫描阶段以已接受的观察位置停驻,不再要求朝侧面挪回更精确的坐标。射线终点仍由冻结的探索任务决定,实际命中位置与方向回传主控。导航避障射线命中的行星或小行星表面也会交给探矿逻辑:若小行星挡在观察点前方,可直接报告实际命中并申请试钻,不必先穿过小行星到点。主控仍核对冻结区域边界并去重,摄像机列表仍仅在初始化或明确重扫时更新。
190 190
@@ -204,7 +204,9 @@ XFE AMS 是 Space Engineers 原版编程块采矿舰队系统:自主推进器
204 204
205 205 加载旧任务时,主控会逐步扩展孤立试钻点,并把旧环带区域迁移为整片网格,保留已完成孔、正在执行的孔及其进度和归属。因旧“两圈没矿”规则结束的区域可继续开采;迁移后的网格和生成游标会持久保存,重载不会反复创建相同任务。无需清空任务或重新标定。
206 206
207 `[探索] 母舰安全间距=80` 将大气观察点沿反重力方向抬高 80 m;太空首个观察点也避开母舰中心,且保持在搜索球内。搜索区域仍以创建时的世界坐标固定。按母舰体积和附近地形增大净空;不把母舰内部当作可到达的观察位置。入孔前执行中心和四周共五点检查,`[采矿] 入口高差上限=5` 限制新孔入口的地表起伏;已有孔道发现其他船体占用时拒绝钻进。
207 `[探索] 母舰安全间距=80` 将大气观察点沿反重力方向抬高 80 m;太空首个观察点也避开母舰中心,且保持在搜索球内。搜索区域仍以创建时的世界坐标固定。按母舰体积和附近地形增大净空;不把母舰内部当作可到达的观察位置。取得小行星范围后,新开采区朝内部布孔,减少擦边射线造成的空孔。自动开采入孔前沿完整任务深度检查中心及周围五条射线,允许凹凸表面和部分空射线;全部未命中、其他船体占用或钻头不能覆盖机身时拒绝钻进。手动钻孔仍由 `[采矿] 入口高差上限=5` 限制新孔入口的地表起伏。
208
209 前往扫描点或开采入口时,持续 45 秒未接近至少 2 米会报告受阻并申请续派,短暂避让只增加有限宽限时间。近距离避让优先使用侧向镜头平移;实际被自身船体遮挡的镜头不再阻止改用其他镜头或转向探测。返航会消除邻近航迹点形成的回环,缩短后的航段仍须扫描确认空间。
208 210
209 211 请区分结果:`SurveyEmpty` 表示该次扫描没有表面命中;钻采未检出只表示已采样部分没有目标矿增量;“采样完成”不等于已经确定地下无矿;开采区的“指定范围采掘完成”表示其计划钻孔全部完成。受阻、无效采样或任务预算不足的区域不能当作正常完成。
210 212
Modified docs/VALIDATION.md +6 -0
Added docs/agent-debug/FULL_CYCLE_20261010.md +30 -0
Added tools/agent-debug/fleet_cycle.py +210 -0
Added tools/agent-debug/tests/test_fleet_cycle.py +41 -0
Modified tools/agent-debug/trace_ams.py +21 -0