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.Linq;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P = AutoMiningScript.Program;

namespace AutoMiningScript.Tests
{
    public class PlannerTests
    {
        [Fact]
        public void ClearTasksDropsGenerationAndAllCheckpointWorkWithoutReusingIds()
        {
            var p=Planner("[Survey]\nMaxJobs=4096\n");p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,1000,100,20);
            Assert.True(p.Generating);Assert.True(p.PendingGeneration>0);int serial=p.TaskSerial;
            var partial=new MyIni();Assert.False(p.Checkpoint(partial));List<string> records;Assert.False(p.CheckpointRecords(out records));
            p.ClearTasks();Assert.Empty(p.Jobs);Assert.Empty(p.Regions);Assert.Empty(p.OwnedJobs);Assert.Equal(0,p.PendingCount);Assert.Equal(0,p.PendingGeneration);Assert.False(p.Generating);
            Assert.Equal(serial,p.TaskSerial);for(int n=0;n<100;n++)p.Advance();Assert.Empty(p.Jobs);
            Assert.True(p.Checkpoint(partial));Assert.False(partial.ContainsKey("job0","Id"));Assert.True(p.CheckpointRecords(out records));Assert.Single(records);
            var persisted=new MyIni();p.Save(persisted);var restored=Planner();restored.Load(persisted);Assert.Empty(restored.Jobs);Assert.Equal(serial,restored.TaskSerial);
            restored.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,6,6,10);Assert.True(restored.TaskSerial>serial);
        }
        static P.TaskPlanner Planner(string config="") {return new P.TaskPlanner(TestRig.Program(config));}
        static void Drain(P.TaskPlanner planner) {for(int n=0;n<1024;n++)planner.Advance();}
        static P.Job SeedProbe(P.TaskPlanner planner)
        {
            planner.Survey(Vector3D.Zero,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20);
            P.Job survey=planner.Jobs[0];
            planner.Report(survey,new P.JobReport {JobId=survey.Id,Outcome="SurveyHit",Position=new Vector3D(0,-10,0),Direction=Vector3D.Down,Up=Vector3D.Forward,EntityId=42});
            return planner.Jobs.Single(j=>j.Kind==P.JobKind.Probe);
        }
        [Fact]
        public void ManualGridCoversRequestedBoundsAndUsesSerpentineOrder()
        {
            var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,12,12,20);Drain(p);
            Assert.Equal(9,p.Jobs.Count);
            Assert.Equal(-6,p.Jobs.Min(j=>j.Entry.X)-3,8);Assert.Equal(6,p.Jobs.Max(j=>j.Entry.X)+3,8);
            Assert.Equal(-6,p.Jobs.Min(j=>j.Entry.Y)-3,8);Assert.Equal(6,p.Jobs.Max(j=>j.Entry.Y)+3,8);
            Assert.True(p.Jobs[0].Entry.X<p.Jobs[1].Entry.X);Assert.True(p.Jobs[3].Entry.X>p.Jobs[4].Entry.X);
            Assert.Equal(p.Jobs[2].Entry.X,p.Jobs[3].Entry.X,8);
            for(int n=1;n<p.Jobs.Count;n++) Assert.InRange(Vector3D.Distance(p.Jobs[n-1].Entry,p.Jobs[n].Entry),0,5.1+1e-8);
            Assert.All(p.Jobs,j=>Assert.Equal(20,j.Depth));
        }
        [Fact]
        public void WorldOriginIsAValidEntranceAndGps()
        {
            Vector3D position;Assert.True(P.Data.TryGps("GPS:Origin:0:0:0:",out position));Assert.Equal(Vector3D.Zero,position);
            var p=Planner();p.Manual(position,Vector3D.Forward,Vector3D.Up,6,6,10);Assert.Single(p.Jobs);Assert.Equal(Vector3D.Zero,p.Jobs[0].Entry);
        }
        [Theory]
        [InlineData(0,6,10)] [InlineData(6,-1,10)] [InlineData(6,6,1001)] [InlineData(double.NaN,6,10)] [InlineData(6,double.PositiveInfinity,10)]
        public void ManualRejectsInvalidDimensions(double width,double height,double depth)
        {
            Assert.Throws<ArgumentException>(()=>Planner().Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,width,height,depth));
        }
        [Fact]
        public void ManualRejectsZeroDirectionAndOversizedJobBudgetWithoutPartialJobs()
        {
            var p=Planner("[Survey]\nMaxJobs=32\n");
            Assert.Throws<ArgumentException>(()=>p.Manual(Vector3D.Zero,Vector3D.Zero,Vector3D.Up,6,6,10));
            Assert.Throws<ArgumentException>(()=>p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,1000,1000,10));
            Assert.Empty(p.Jobs);Assert.Empty(p.Regions);
        }
        [Fact]
        public void PlanetSurveyIsFiniteOnFrozenHorizontalPlane()
        {
            var p=Planner();Vector3D origin=new Vector3D(123,456,789);p.Survey(origin,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,120);Drain(p);
            Assert.Equal(13,p.Jobs.Count);
            Assert.All(p.Jobs,j=>{Assert.Equal(origin.Y+80,j.Entry.Y,8);Assert.InRange(new Vector3D(j.Entry.X-origin.X,0,j.Entry.Z-origin.Z).Length(),0,120.00001);Assert.True(Vector3D.Dot(j.Direction,Vector3D.Down)>0.999);});
            Vector3D saved=p.Jobs[0].Entry;p.Survey(origin+new Vector3D(1000,0,0),Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20);
            Assert.Equal(saved,p.Jobs[0].Entry);Assert.Equal(origin,p.Regions[0].Center);
        }
        [Fact]
        public void SpaceSurveyHasFiniteObservationPointsAndRaysEndingAtSphere()
        {
            var p=Planner();Vector3D origin=new Vector3D(10,20,30);p.Survey(origin,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);Drain(p);
            Assert.Equal(294,p.Jobs.Count);
            Assert.All(p.Jobs,j=>{Assert.InRange(Vector3D.Distance(origin,j.Entry),0,700.0001);Assert.Equal(2000,Vector3D.Distance(origin,j.Entry+j.Direction*j.Depth),6);});
            Assert.Equal(7,p.Jobs.Select(j=>j.Entry).Distinct().Count());
        }
        [Fact]
        public void LargeSearchKeepsEachMessageWithinJobScanBounds()
        {
            var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,20000);Drain(p);
            Assert.All(p.Jobs,j=>{Assert.InRange(j.Depth,1,10000);Assert.NotNull(P.Job.FromIni(j.ToIni()));});
        }
        [Fact]
        public void RepeatedSurfaceHitsDeduplicateEntityAndPosition()
        {
            var p=Planner();P.Job probe=SeedProbe(p);
            p.Survey(Vector3D.Zero,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20);
            P.Job survey=p.Jobs.Last();
            p.Report(survey,new P.JobReport {Outcome="SurveyHit",Position=probe.Entry+new Vector3D(0.1,0,0),Direction=Vector3D.Down,Up=Vector3D.Forward,EntityId=42});
            Assert.Single(p.Jobs,j=>j.Kind==P.JobKind.Probe);
            p.Survey(Vector3D.Zero,Vector3D.Down*9.81,Vector3D.Forward,Vector3D.Up,20);survey=p.Jobs.Last();
            p.Report(survey,new P.JobReport {Outcome="SurveyHit",Position=probe.Entry,Direction=Vector3D.Down,Up=Vector3D.Forward,EntityId=43});
            Assert.Equal(2,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));
        }
        [Fact]
        public void SurfaceOutsideFrozenSearchDoesNotGenerateProbe()
        {
            var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,100);
            p.Report(p.Jobs[0],new P.JobReport {Outcome="SurveyHit",Position=new Vector3D(101,0,0),Direction=Vector3D.Left,EntityId=42});
            Assert.DoesNotContain(p.Jobs,j=>j.Kind==P.JobKind.Probe);
        }
        [Fact]
        public void DiscoveryWaitsForRetreatCompletionBeforeSchedulingNeighborRing()
        {
            var p=Planner();P.Job probe=SeedProbe(p);probe.Owner="miner-1";
            p.Report(probe,new P.JobReport {Outcome="Discovery",Ore="Cobalt,Iron",Progress=5});Drain(p);
            Assert.Single(p.Jobs,j=>j.Kind==P.JobKind.Probe);Assert.Equal("miner-1",probe.Owner);
            Assert.Equal("Cobalt,Iron",p.Regions.Single(r=>r.Patch).Ore);
            Assert.Equal("Iron",p.PreferredOre(p.Regions.Single(r=>r.Patch).Ore));
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p);
            Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));Assert.Equal("",probe.Owner);
            Assert.All(p.Jobs.Where(j=>j.Kind==P.JobKind.Probe),j=>Assert.Equal(42,j.EntityId));
        }
        [Fact]
        public void TerminalOreReportRecoversMissingOrReorderedDiscoveryOnce()
        {
            var p=Planner();P.Job probe=SeedProbe(p);
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p);
            Assert.Single(p.Regions,r=>r.Patch);Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));
            p.Report(probe,new P.JobReport {Outcome="Discovery",Ore="Iron",Progress=5});
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p);
            Assert.Single(p.Regions,r=>r.Patch);Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));
        }
        [Fact]
        public void TwoCompletedEmptyRingsStopExpansion()
        {
            var p=Planner("[Survey]\nExpansionWidth=60\nExpansionHeight=60\n");P.Job probe=SeedProbe(p);
            // Widen the frozen search so both complete expansion rings fit.
            p.Regions[0].Radius=100;
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p);
            Assert.Equal(8,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)));
            foreach(P.Job j in p.Jobs.Where(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)).ToList()) p.Report(j,new P.JobReport {Outcome="Complete",Progress=j.Depth});Drain(p);
            Assert.Equal(16,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)));
            foreach(P.Job j in p.Jobs.Where(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)).ToList()) p.Report(j,new P.JobReport {Outcome="Complete",Progress=j.Depth});Drain(p);
            Assert.Equal(25,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));Assert.DoesNotContain(p.Jobs,j=>!P.TaskPlanner.Finished(j));
            Assert.Contains("two empty rings",p.Regions.Single(r=>r.Patch).Status);
        }
        [Fact]
        public void InvalidSamplesNeverCountAsConfirmedEmptyRings()
        {
            var p=Planner();P.Job probe=SeedProbe(p);p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron",Progress=probe.Depth});Drain(p);
            foreach(P.Job j in p.Jobs.Where(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j)).ToList()) p.Report(j,new P.JobReport {Outcome="InvalidSample",Progress=j.Depth});Drain(p);
            var patch=p.Regions.Single(r=>r.Patch);Assert.Equal(0,patch.EmptyRings);Assert.Contains("incomplete",patch.Status);
        }
        [Fact]
        public void PausedReportPreservesOwnershipAndMonotonicProgress()
        {
            var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,6,6,30);P.Job j=p.Jobs[0];j.Owner="miner-1";j.Progress=12;
            p.Report(j,new P.JobReport {Outcome="Paused",Progress=8});Drain(p);
            Assert.False(P.TaskPlanner.Finished(j));Assert.Equal("miner-1",j.Owner);Assert.Equal(12,j.Progress);Assert.Equal("Pending",p.Regions[0].Status);
        }
        [Fact]
        public void PlannerStoragePreservesFrozenGeometryOccupancyAndProgress()
        {
            var p=Planner();p.Manual(new Vector3D(1,2,3),Vector3D.Forward,Vector3D.Up,12,12,30);p.Jobs[0].Owner="miner-1";p.Jobs[0].Progress=12;
            var saved=new MyIni();p.Save(saved);var restored=Planner();restored.Load(saved);
            Assert.Equal(p.Jobs.Count,restored.Jobs.Count);Assert.Equal(p.Regions[0].Center,restored.Regions[0].Center);
            Assert.Equal("miner-1",restored.Jobs[0].Owner);Assert.Equal(12,restored.Jobs[0].Progress);
            Assert.Equal(p.Jobs.Select(j=>j.Entry),restored.Jobs.Select(j=>j.Entry));
        }
        [Fact]
        public void StorageRejectsInvalidBoundsDuplicateIdentifiersAndSerial()
        {
            var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,12,6,30);var saved=new MyIni();p.Save(saved);
            saved.Set("job0","Depth",-1);Assert.Throws<ArgumentException>(()=>Planner().Load(saved));
            saved.Clear();p.Save(saved);saved.Set("job1","Id",p.Jobs[0].Id);Assert.Throws<ArgumentException>(()=>Planner().Load(saved));
            saved.Clear();p.Save(saved);saved.Set("planner","Serial",-1);Assert.Throws<ArgumentException>(()=>Planner().Load(saved));
        }
        [Fact]
        public void IncrementalCheckpointPatchesEarlierCompletionsAndAppendedTasks()
        {
            var p=Planner();p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,300,6,20);Drain(p);p.Jobs[0].Owner="miner-1";
            var saved=new MyIni();Assert.False(p.Checkpoint(saved));
            p.Report(p.Jobs[0],new P.JobReport {Outcome="Complete",Progress=20});p.Jobs[30].Owner="miner-2";
            p.Manual(new Vector3D(1000,0,0),Vector3D.Forward,Vector3D.Up,6,6,20);
            int frames=0;while(!p.Checkpoint(saved)&&frames++<100){}Assert.True(frames<100);
            var restored=Planner();restored.Load(saved);Assert.Equal(p.Jobs.Count,restored.Jobs.Count);
            Assert.Equal("Complete",restored.Jobs[0].Outcome);Assert.Equal("",restored.Jobs[0].Owner);Assert.Equal("miner-2",restored.Jobs[30].Owner);
        }
        [Fact]
        public void SurveyCommandQueuesBoundedWorkAndPreservesAllRays()
        {
            var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);
            Assert.InRange(p.Jobs.Count,1,8);Assert.True(p.Generating);
            int count=p.Jobs.Count;p.Advance();Assert.InRange(p.Jobs.Count-count,0,8);
            Drain(p);Assert.False(p.Generating);Assert.Equal(294,p.Jobs.Count);Assert.Equal(294,p.Jobs.Select(j=>j.Id).Distinct().Count());
        }
        [Fact]
        public void PendingGenerationReservesCapacityAcrossCommands()
        {
            var p=Planner("[Survey]\nMaxJobs=300\n");p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);
            Assert.Throws<ArgumentException>(()=>p.Manual(Vector3D.Zero,Vector3D.Forward,Vector3D.Up,12,12,20));
            Drain(p);Assert.Equal(294,p.Jobs.Count);
        }
        [Fact]
        public void GenerationYieldsWhenInstructionBudgetIsSpent()
        {
            var program=TestRig.Program();var runtime=new Stub<Sandbox.ModAPI.Ingame.IMyGridProgramRuntimeInfo>().Set("MaxInstructionCount",50000).Set("CurrentInstructionCount",30000);TestRig.SetBase(program,"Runtime",runtime.Value);
            var p=new P.TaskPlanner(program);p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);p.Advance();Assert.Empty(p.Jobs);Assert.True(p.Generating);
            runtime.Set("CurrentInstructionCount",0);Drain(p);Assert.Equal(294,p.Jobs.Count);
        }
        [Fact]
        public void RestartResumesPartialGenerationWithoutMissingOrRepeatingHoles()
        {
            var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Zero,Vector3D.Forward,Vector3D.Up,2000);var save=new MyIni();p.Save(save);
            var restored=Planner();restored.Load(save);Assert.True(restored.Generating);Drain(p);Drain(restored);
            Assert.Equal(p.Jobs.Select(j=>j.Id),restored.Jobs.Select(j=>j.Id));Assert.Equal(p.Jobs.Select(j=>j.Entry),restored.Jobs.Select(j=>j.Entry));
        }
        [Fact]
        public void RecordWriterRejectsAnyRecordTheReaderCannotRestore()
        {
            var ini=new MyIni();ini.Set("payload","Value",new string('x',32768));Assert.Throws<ArgumentException>(()=>P.TaskPlanner.StorageRecord(ini,"Fleet"));
        }
        [Fact]
        public void RegionSummaryRestartsWhenJobsAreAppendedBetweenFrames()
        {
            var p=Planner();p.Survey(Vector3D.Zero,Vector3D.Down,Vector3D.Forward,Vector3D.Up,20);
            p.Report(p.Jobs[0],new P.JobReport {Outcome="SurveyEmpty"});
            for(int n=0;n<100;n++)p.Manual(new Vector3D(1000+n*20,0,0),Vector3D.Forward,Vector3D.Up,6,6,10);
            p.Advance();p.Advance();
            // Append a new pending hole in the region after its earlier jobs were scanned.
            p.Manual(new Vector3D(5000,0,0),Vector3D.Forward,Vector3D.Up,6,6,10);p.Jobs.Last().RegionId=p.Regions[0].Id;
            Drain(p);Assert.Equal("Pending",p.Regions[0].Status);
        }
        [Fact]
        public void UnknownOreDoesNotCreateTargetDeposit()
        {
            var p=Planner("[Mining]\nTargetOres=Iron,Nickel,Cobalt\n");P.Job probe=SeedProbe(p);p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Stone,Ice",Progress=probe.Depth});Drain(p);
            Assert.DoesNotContain(p.Regions,r=>r.Patch);Assert.Equal(0,p.OrePriority("Stone"));Assert.True(p.OrePriority("Iron")>p.OrePriority("Cobalt"));
            Assert.Equal("Stone,Ice",probe.ConfirmedOres);
        }
        [Theory]
        [InlineData("")]
        [InlineData("[Mining]\nTargetOres=\n")]
        [InlineData("[Mining]\nTargetOres= , , \n")]
        public void EmptyTargetsMineStoneAndAnyOtherOre(string config)
        {
            var p=Planner(config);var probe=SeedProbe(p);
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Stone,Ice,ModOre",Progress=probe.Depth});Drain(p);
            var patch=Assert.Single(p.Regions,r=>r.Patch);Assert.Equal("Stone,Ice,ModOre",patch.Ore);
            Assert.Equal(9,p.Jobs.Count(j=>j.Kind==P.JobKind.Probe));Assert.Equal(0,p.OrePriority("Stone"));
        }
        [Fact]
        public void TargetOrderDoesNotSetPriorityAndPriorityCannotBypassTargets()
        {
            var p=Planner("[Mining]\nTargetOres=Stone,Gold,Iron,Cobalt\nPriorityOres=Cobalt,Nickel,Iron\n");
            Assert.Equal("Cobalt",p.PreferredOre("Stone,Iron,Gold,Cobalt,Nickel"));
            Assert.Equal("",p.PreferredOre("Nickel"));Assert.Equal(0,p.OrePriority("Nickel"));
            Assert.Equal(0,p.OrePriority("Gold"));Assert.Equal(0,p.OrePriority("Stone"));
            var unranked=Planner("[Mining]\nPriorityOres=\n");Assert.Equal("Stone",unranked.PreferredOre("Stone,Iron"));Assert.Equal(0,unranked.OrePriority("Iron"));
        }
        [Fact]
        public void DefaultPriorityOrderIsIronCobaltNickelSilicon()
        {
            var p=Planner();Assert.True(p.OrePriority("Iron")>p.OrePriority("Cobalt"));Assert.True(p.OrePriority("Cobalt")>p.OrePriority("Nickel"));
            Assert.True(p.OrePriority("Nickel")>p.OrePriority("Silicon"));Assert.True(p.OrePriority("Silicon")>p.OrePriority("Gold"));
            Assert.Equal("Iron",p.PreferredOre("Stone,Silicon,Nickel,Cobalt,Iron"));
        }
        [Fact]
        public void CompleteReportAddsLaterOresAndRestoredJobsUseCurrentPreference()
        {
            var p=Planner();var probe=SeedProbe(p);
            p.Report(probe,new P.JobReport {Outcome="Discovery",Ore="Stone",Progress=5});
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Stone,Iron,Cobalt",Progress=probe.Depth});Drain(p);
            Assert.Equal("Stone,Iron,Cobalt",probe.ConfirmedOres);Assert.Equal("Stone,Iron,Cobalt",p.Regions.Single(r=>r.Patch).Ore);
            var saved=new MyIni();p.Save(saved);
            var restored=Planner("[Mining]\nTargetOres=Cobalt\nPriorityOres=Cobalt\n");restored.Load(saved);
            var pending=restored.Jobs.First(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j));
            Assert.Equal(11,restored.JobPriority(pending));
            var excluded=Planner("[Mining]\nTargetOres=Gold\n");excluded.Load(saved);
            Assert.Equal(-1,excluded.JobPriority(excluded.Find(pending.Id)));
        }
        [Fact]
        public void LateTerminalOreReportUpdatesPatchWithoutReopeningFinishedHole()
        {
            var p=Planner();var probe=SeedProbe(p);
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Stone",Progress=probe.Depth});Drain(p);
            int count=p.Jobs.Count;var pending=p.Jobs.First(j=>j.Kind==P.JobKind.Probe&&!P.TaskPlanner.Finished(j));Assert.Equal(10,p.JobPriority(pending));
            p.Report(probe,new P.JobReport {Outcome="Complete",Ore="Iron,Stone",Progress=probe.Depth});Drain(p);
            Assert.Equal(count,p.Jobs.Count);Assert.True(P.TaskPlanner.Finished(probe));Assert.Equal("Stone,Iron",probe.ConfirmedOres);
            Assert.Equal("Stone,Iron",p.Regions.Single(r=>r.Patch).Ore);Assert.Equal(14,p.JobPriority(pending));
        }
    }
}