using System; using System.Linq; using VRage.Game.ModAPI.Ingame.Utilities; using VRageMath; using Xunit; using P=AutoMiningScript.Program; namespace AutoMiningScript.Tests { public class DockRecoveryTests { // Recorded positions from the stalled 11:06 save. Keep them in the test // rather than depending on an untracked local save under obj/. static readonly Vector3D SavedForward=new Vector3D(-.7583930492401123,.43053719401359558,.4893641471862793); static readonly Vector3D SavedUp=new Vector3D(.62588357925415039,.27143645286560059,.73115783929824829); static readonly Vector3D FourPort=new Vector3D(-67812.319486450288,-123701.75128268334,-162123.89634542784); static readonly Vector3D FivePort=new Vector3D(-67799.8018148652,-123696.32255362603,-162109.27318864188); static readonly Vector3D FourPosition=new Vector3D(-67923.280464637908,-123859.14376708004,-161997.76596521458); static readonly Vector3D FourStart=new Vector3D(-67920.109540823731,-123850.31650989386,-162001.60530758521); static readonly Vector3D FivePosition=new Vector3D(-67846.562285724911,-123647.61979939672,-162059.99176219723); static readonly Vector3D OnePosition=new Vector3D(-67811.91664713982,-123698.84802260133,-162118.48642270884); static readonly Vector3D TwoPosition=new Vector3D(-67845.112023729715,-123659.2111615825,-162077.86188009998); static readonly Vector3D TwoWaitingPoint=new Vector3D(-67849.776998099245,-123665.0629307388,-162050.74675762336); static void SeedLane(FleetStateTests.Rig rig,int miner,long dock,Vector3D position,Vector3D start,Vector3D forward,Vector3D up,string kind="DOCK_REQUEST") { rig.RegisterAll(); var saved=rig.Save(); saved.Set("fleet","LaneCount",1); saved.Set("fleet","LaneOwner",rig.Miners[miner].Id); saved.Set("fleet","LaneKind",kind); saved.Set("fleet","LaneDock",dock); saved.Set("fleet","LaneToken","obsolete-before-restart"); P.Data.PutVector(saved,"fleet","LanePosition",position); P.Data.PutVector(saved,"fleet","LaneStart",start); P.Data.PutVector(saved,"fleet","LaneForward",forward); P.Data.PutVector(saved,"fleet","LaneUp",up); saved.Set("fleet","LaneRadius",Math.Max(3,rig.Miners[miner].Radius)); saved.Set("fleet","LaneClearance",60); rig.Fleet.Load(saved); // Exercise the same record storage restoration used by live reloads. rig.Restore(rig.Checkpoint()); } static FleetStateTests.Rig BlockedReturn(P.FlightState state=P.FlightState.Holding,bool outside=true,string kind="DOCK_REQUEST",bool occupied=false) { var rig=new FleetStateTests.Rig(2,false); rig.Miners[0].State=state; rig.Miners[0].Position=new Vector3D(0,0,outside?-100:-15); rig.Miners[1].Position=new Vector3D(0,0,-30); rig.Miners[1].State=P.FlightState.Holding; if(occupied) { // A different miner is physically connected to the original berth. // A future approach line alone no longer prevents an inbound grant. rig.SetConnected(0,true);rig.Miners[0].Connected=false;rig.Miners[1].Connected=true; rig.Miners[1].ConnectorId=rig.Miners[0].ConnectorId;rig.Miners[0].ConnectorId=9999; } SeedLane(rig,0,100,Vector3D.Zero,rig.Miners[0].Position,Vector3D.Forward,Vector3D.Up,kind); rig.RegisterAll();rig.Tick(30);rig.Sent.Clear(); return rig; } static void AssertOriginalLaneRetained(FleetStateTests.Rig rig,bool renewed=false) { var saved=rig.Save(); Assert.Equal(1,P.Data.ReadLong(saved,"fleet","LaneCount")); Assert.Equal(rig.Miners[0].Id,saved.Get("fleet","LaneOwner").ToString()); Assert.Equal(100,P.Data.ReadLong(saved,"fleet","LaneDock")); if(renewed) { Assert.NotEmpty(saved.Get("fleet","LaneToken").ToString()); var grant=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body); Assert.Equal(100,grant.ConnectorId);Assert.Equal(saved.Get("fleet","LaneToken").ToString(),grant.Token); } else { Assert.Equal("",saved.Get("fleet","LaneToken").ToString()); Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT"); } } [Fact] public void SavedFourCanUseItsFreeBerthBesideTheAlreadyDockedOne() { var rig=new FleetStateTests.Rig(3,false); rig.SetPort(0,FourPort,SavedForward); rig.SetPort(1,OnePosition,SavedForward); rig.SetPort(2,FivePort,SavedForward); var four=rig.Miners[0];four.Id="深空工业4号";four.Position=FourPosition;four.State=P.FlightState.Returning;four.Radius=4.1124754356677382; var one=rig.Miners[1];one.Id="深空工业1号";one.Position=OnePosition;one.Radius=4.631461208568588;rig.SetConnected(1,true); rig.Miners[2].Position=FivePort+SavedUp*1000; SeedLane(rig,0,100,FourPort,FourStart,SavedForward,SavedUp); rig.RegisterAll();rig.Tick(30); Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT"); rig.Receive("DOCK_REQUEST",four);rig.Tick(12); var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==four.Id).Body); Assert.Equal(100,granted.ConnectorId); Assert.NotEqual("obsolete-before-restart",granted.Token); Assert.NotEmpty(granted.Token); Assert.Equal("",granted.Problem); Assert.True(one.Connected); } [Fact] public void SavedFiveGetsItsBerthWithoutHoldingTwoForTheFutureApproachLine() { var rig=new FleetStateTests.Rig(1,false); rig.SetPort(0,FivePort,SavedForward); var five=rig.Miners[0];five.Id="深空工业5号";five.Position=FivePosition;five.State=P.FlightState.Holding;five.Radius=4.11247548414806; five.HasRoute=true;five.RouteTarget=new Vector3D(-67846.136002352228,-123647.842939467,-162060.25410989788); var two=new P.Telemetry {Id="深空工业2号",Position=TwoPosition,State=P.FlightState.Holding,Radius=4.1124763592894293, HasRoute=true,RouteTarget=TwoWaitingPoint,Battery=1,Hydrogen=1,ThrustMargin=2.2698880858019943}; rig.Miners.Add(two); SeedLane(rig,0,100,FivePort,FivePosition,SavedForward,SavedUp); rig.RegisterAll();rig.Tick(30); var initial=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==two.Id); Assert.False(P.Data.Flag(initial.Body,"traffic","Wait")); Assert.False(P.Data.Flag(initial.Body,"traffic","Avoid")); rig.Sent.Clear(); rig.Receive("DOCK_REQUEST",five);rig.Receive("TELEMETRY",two);rig.Tick(12); var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==five.Id).Body); Assert.Equal(100,granted.ConnectorId);Assert.NotEmpty(granted.Token);Assert.Equal("",granted.Problem); var traffic=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==two.Id); Assert.False(P.Data.Flag(traffic.Body,"traffic","Wait")); Assert.False(P.Data.Flag(traffic.Body,"traffic","Avoid")); var waiting=P.DockFrame.FromIni(rig.Sent.Last(p=>p.Kind=="DOCKPOSE"&&p.Target==five.Id).Body); Assert.Equal(granted.Token,waiting.Token);Assert.Equal("",waiting.Problem); Assert.Equal(1,P.Data.ReadLong(rig.Save(),"fleet","LaneCount")); Assert.Equal(TwoPosition,two.Position); } [Theory] [InlineData(P.FlightState.Returning)] [InlineData(P.FlightState.Holding)] public void FreshReturningAndHoldingRequestsReuseAFreeBerthDespiteDistantShipsOnTheApproach(P.FlightState phase) { var rig=BlockedReturn(phase); rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); var granted=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body); Assert.Equal(100,granted.ConnectorId); Assert.NotEmpty(granted.Token); } [Theory] [InlineData(P.FlightState.DockAlign)] [InlineData(P.FlightState.DockApproach)] [InlineData(P.FlightState.Departing)] [InlineData(P.FlightState.DockRetreat)] public void ActiveOrRetreatingDockingPhaseRenewsItsOriginalFreeBerth(P.FlightState phase) { var rig=BlockedReturn(phase); rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); AssertOriginalLaneRetained(rig,true); } [Fact] public void NearPortHoldingMinerRenewsItsOriginalFreeBerth() { var rig=BlockedReturn(P.FlightState.Holding,false); rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); AssertOriginalLaneRetained(rig,true); } [Theory] [InlineData(-.1,false)] [InlineData(0,false)] [InlineData(.1,true)] public void ReassignmentUsesTheAssignedConnectorEntryDistanceAndMinerMargin(double extra,bool canReassign) { var rig=BlockedReturn(occupied:true); var entry=P.Data.ReadNumber(rig.Save(),"fleet","LaneApproachDistance",0);Assert.InRange(entry,30,40); var distance=entry+rig.Miners[0].Radius+2+extra; rig.Miners[0].Position=new Vector3D(0,0,-distance); rig.Miners[1].Position=Vector3D.Zero; rig.Receive("TELEMETRY",rig.Miners[1]); rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); if(!canReassign)AssertOriginalLaneRetained(rig); else Assert.Equal(101,P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body).ConnectorId); } [Fact] public void LegacyLaneGeometryLoadsAsAConnectorAssignmentAndIsNotWrittenBack() { var rig=new FleetStateTests.Rig(1,false); rig.Miners[0].State=P.FlightState.Holding;rig.Miners[0].Position=Vector3D.Forward*100; SeedLane(rig,0,100,Vector3D.Zero,Vector3D.Forward*100,Vector3D.Forward,Vector3D.Up); var saved=rig.Save(); Assert.Equal(100,P.Data.ReadLong(saved,"fleet","LaneDock"));Assert.Equal(rig.Miners[0].Id,P.Data.Text(saved,"fleet","LaneOwner")); Assert.Equal(Vector3D.Zero,P.Data.ReadVector(saved,"fleet","LanePosition",Vector3D.One)); Assert.Equal(30,P.Data.ReadNumber(saved,"fleet","LaneApproachDistance",0)); Assert.Equal(60,P.Data.ReadNumber(saved,"fleet","LaneClearance",0)); foreach(var key in new[]{"LaneStart","LaneForward","LaneUp","LaneRadius"})Assert.False(saved.ContainsKey("fleet",key)); Assert.Empty(P.Data.Text(saved,"fleet","LaneToken")); rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); var grant=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body); Assert.Equal(100,grant.ConnectorId);Assert.Equal(30,grant.ApproachDistance);Assert.NotEmpty(grant.Token); } [Fact] public void RegistrationAloneDoesNotAuthorizeReplacingRestoredOccupancy() { var rig=BlockedReturn(); rig.Receive("TELEMETRY",rig.Miners[0]);rig.Tick(12); AssertOriginalLaneRetained(rig); } [Fact] public void StaleTelemetryCannotReleaseAPreviouslyConsentedReservation() { var rig=BlockedReturn(P.FlightState.Holding,false,occupied:true); rig.Receive("DOCK_REQUEST",rig.Miners[0]); rig.Miners[0].Position=new Vector3D(0,0,-100); rig.Receive("TELEMETRY",rig.Miners[0]); rig.Program.Now+=4;rig.Tick(12); AssertOriginalLaneRetained(rig); } [Fact] public void LostMinerReservationRemainsProtectedAfterReload() { var rig=BlockedReturn(); rig.Program.Now+=60;rig.Receive("TELEMETRY",rig.Miners[1]);rig.Tick(12); AssertOriginalLaneRetained(rig); } [Fact] public void ValidCurrentGrantIsNotReassignedByAnotherRequest() { var rig=new FleetStateTests.Rig(2,false); rig.Miners[0].Position=new Vector3D(0,0,-100);rig.Miners[0].State=P.FlightState.Returning; rig.Miners[1].Position=new Vector3D(1000,0,-30); rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); var original=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT").Body); Assert.Equal(100,original.ConnectorId); rig.Miners[1].Position=new Vector3D(0,0,-30);rig.Receive("TELEMETRY",rig.Miners[1]);rig.Sent.Clear(); rig.Receive("DOCK_REQUEST",rig.Miners[0]);rig.Tick(12); Assert.All(rig.Sent.Where(p=>p.Kind=="DOCK_GRANT"),p=> { var repeated=P.DockFrame.FromIni(p.Body); Assert.Equal(original.Token,repeated.Token); Assert.Equal(original.ConnectorId,repeated.ConnectorId); }); Assert.Equal(original.Token,rig.Save().Get("fleet","LaneToken").ToString()); } [Fact] public void OccupiedDockExplainsWhyAnInboundRequestIsWaiting() { var rig=new FleetStateTests.Rig(1,true); var waiting=rig.Miners[0];waiting.Connected=false;waiting.ConnectorId=9999; waiting.Position=new Vector3D(0,0,-100);waiting.State=P.FlightState.Holding; rig.RegisterAll();rig.Tick(30);rig.Receive("DOCK_REQUEST",waiting);rig.Tick(12); Assert.DoesNotContain(rig.Sent,p=>p.Kind=="DOCK_GRANT"); var pose=P.DockFrame.FromIni(rig.Sent.Last(p=>p.Kind=="DOCKPOSE"&&p.Target==waiting.Id).Body); Assert.Equal("",pose.Token); Assert.Equal(P.L.FleetNoFreeDock,pose.Problem); } [Fact] public void AnUnstableDockWindowDoesNotDelayAnInboundConnectorAssignment() { var rig=new FleetStateTests.Rig(1,false,"[Dock]\nStableSeconds=30\n"); var waiting=rig.Miners[0];waiting.Position=new Vector3D(0,0,-100);waiting.State=P.FlightState.Holding; rig.RegisterAll();rig.Receive("DOCK_REQUEST",waiting); var pose=P.DockFrame.FromIni(Assert.Single(rig.Sent,p=>p.Kind=="DOCK_GRANT"&&p.Target==waiting.Id).Body); Assert.NotEmpty(pose.Token); Assert.Equal("",pose.Problem); Assert.Equal(0,rig.Program.Now); } } }