using System;
using System.Linq;
using System.Reflection;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class PortYieldSafetyTests
{
static void ObserveFrame(FleetStateTests.Rig rig)
{
rig.Program.Now+=.6;
foreach(var miner in rig.Miners)rig.Receive("TELEMETRY",miner);
rig.Sent.Clear();rig.Tick(1);
}
[Fact]
public void AnExistingYieldStopsForAThirdShipAndResumesItsOriginalPointWhenClear()
{
var rig=new FleetStateTests.Rig(3,false);
typeof(P.FleetController).GetField("trafficRandom",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(rig.Fleet,new Random(0));
foreach(var miner in rig.Miners){miner.State=P.FlightState.Holding;miner.Radius=4;miner.ThrustMargin=100;miner.HasRoute=true;}
rig.Miners[0].Position=new Vector3D(-5,0,-50);rig.Miners[0].Velocity=Vector3D.Right*2;rig.Miners[0].RouteTarget=new Vector3D(30,0,-50);
rig.Miners[1].Position=new Vector3D(5,0,-50);rig.Miners[1].Velocity=Vector3D.Left*2;rig.Miners[1].RouteTarget=new Vector3D(-30,0,-50);
var third=rig.Miners[2];third.Position=new Vector3D(1000,1000,-50);third.RouteTarget=third.Position;
rig.RegisterAll();rig.Tick(1);
// Obtain the actual randomly selected yielding ship and its original
// point from traffic packets, rather than installing a private plan.
var initial=Assert.Single(rig.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var yielding=rig.Miners.Single(t=>t.Id==initial.Target);
var priority=rig.Miners.Single(t=>t.Id==P.Data.Text(initial.Body,"traffic","Peer"));
var point=P.Data.ReadVector(initial.Body,"traffic","Point",Vector3D.Zero);
var lateral=Vector3D.Normalize(point-yielding.Position);
Assert.InRange(Vector3D.Distance(point,yielding.Position),1.9,2.01);
// A subsequent observed frame reports that the yielding ship has
// started its lateral move and another ship is closing along that leg.
// This tests one scheduling decision; no movement is integrated here.
yielding.Velocity=lateral*3;yielding.RouteTarget=point;
priority.Velocity=Vector3D.Zero;priority.HasRoute=false;
third.Position=yielding.Position+lateral*11;third.Velocity=-lateral*2;third.RouteTarget=yielding.Position;
ObserveFrame(rig);
var blocked=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.True(P.Data.Flag(blocked.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(blocked.Body,"traffic","Avoid"));
Assert.Equal(third.Id,P.Data.Text(blocked.Body,"traffic","Peer"));
Assert.Equal(point,P.Data.ReadVector(blocked.Body,"traffic","Point",Vector3D.Zero));
third.Position=new Vector3D(1000,1000,-50);third.Velocity=Vector3D.Zero;third.HasRoute=false;
ObserveFrame(rig);
var resumed=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.False(P.Data.Flag(resumed.Body,"traffic","Wait"));
Assert.True(P.Data.Flag(resumed.Body,"traffic","Avoid"));
Assert.Equal(priority.Id,P.Data.Text(resumed.Body,"traffic","Peer"));
Assert.Equal(point,P.Data.ReadVector(resumed.Body,"traffic","Point",Vector3D.Zero));
}
static FleetStateTests.Rig Encounter(Vector3D first,Vector3D second,Vector3D firstVelocity,Vector3D secondVelocity,double thrustMargin=100,double response=.625)
{
var r=new FleetStateTests.Rig(2,false);
for(int n=0;n<2;n++)
{
var t=r.Miners[n];t.State=P.FlightState.Transit;t.Radius=3;t.ThrustMargin=thrustMargin;t.BrakeResponse=response;t.HasRoute=true;
t.Position=n==0?first:second;t.Velocity=n==0?firstVelocity:secondVelocity;t.RouteTarget=t.Position+t.Velocity*30;
}
r.RegisterAll();r.Tick(1);return r;
}
[Fact]
public void SlowDistantConvergenceAndNearbySafeMissesDoNotTriggerYielding()
{
var slow=Encounter(new Vector3D(-7.5,0,-50),new Vector3D(7.5,0,-50),Vector3D.Right*.2,Vector3D.Left*.2);
var miss=Encounter(new Vector3D(-5,0,-50),new Vector3D(5,0,-42),Vector3D.Right*2,Vector3D.Left*2);
foreach(var r in new[]{slow,miss})
Assert.All(r.Sent.Where(p=>p.Kind=="TRAFFIC"),p=>{Assert.False(P.Data.Flag(p.Body,"traffic","Wait"));Assert.False(P.Data.Flag(p.Body,"traffic","Avoid"));});
Assert.Equal(2,slow.Sent.Count(p=>p.Kind=="TRAFFIC"));Assert.Equal(2,miss.Sent.Count(p=>p.Kind=="TRAFFIC"));
}
[Theory]
[InlineData(0,false)]
[InlineData(.1,true)]
public void UnknownBrakingDoesNotInventAnEarlyEncounterButKnownLowBrakingRemainsProtected(double thrustMargin,bool mustYield)
{
var r=Encounter(new Vector3D(-25,0,-50),new Vector3D(25,0,-50),Vector3D.Right*3,Vector3D.Left*3,thrustMargin);
var responses=r.Sent.Where(p=>p.Kind=="TRAFFIC").ToList();
Assert.Equal(2,responses.Count);
if(mustYield)Assert.Single(responses,p=>P.Data.Flag(p.Body,"traffic","Avoid"));
else Assert.All(responses,p=>{Assert.False(P.Data.Flag(p.Body,"traffic","Wait"));Assert.False(P.Data.Flag(p.Body,"traffic","Avoid"));});
}
[Fact]
public void AnActiveYieldEndsWhenThePeerTurnsAwayEvenThoughItStillHasARoute()
{
var r=Encounter(new Vector3D(-4.5,0,-50),new Vector3D(4.5,0,-50),Vector3D.Right*2,Vector3D.Left*2);
var initial=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var yielding=r.Miners.Single(t=>t.Id==initial.Target);
var peer=r.Miners.Single(t=>t.Id==P.Data.Text(initial.Body,"traffic","Peer"));
// The peer now travels away at the yielding ship's speed. Their
// measured relative motion is safe, while the old encounter velocity
// would still describe a head-on collision. HasRoute stays true.
peer.Velocity=Vector3D.Normalize(peer.Position-yielding.Position)*2;
peer.RouteTarget=peer.Position+peer.Velocity*30;peer.HasRoute=true;
ObserveFrame(r);
var clear=r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.True(peer.HasRoute);
Assert.False(P.Data.Flag(clear.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(clear.Body,"traffic","Avoid"));
}
[Fact]
public void BothEndsKeepTheSameEncounterUntilTheOriginalCoursesAreClear()
{
var r=Encounter(new Vector3D(-4.5,0,-50),new Vector3D(4.5,0,-50),Vector3D.Right*2,Vector3D.Left*2);
var initial=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var loser=r.Miners.Single(t=>t.Id==initial.Target);var peer=r.Miners.Single(t=>t.Id==P.Data.Text(initial.Body,"traffic","Peer"));
var side=Vector3D.Normalize(P.Data.ReadVector(initial.Body,"traffic","Point")-loser.Position);
loser.Velocity=side*.2;peer.Velocity=Vector3D.Zero;ObserveFrame(r);
var waiting=r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==peer.Id);
Assert.True(P.Data.Flag(waiting.Body,"traffic","Wait"));Assert.False(P.Data.Flag(waiting.Body,"traffic","Avoid"));
Assert.True(P.Data.Flag(r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==loser.Id).Body,"traffic","Avoid"));
loser.Position+=side*7;loser.Velocity=Vector3D.Zero;ObserveFrame(r);
Assert.All(r.Sent.Where(p=>p.Kind=="TRAFFIC"),p=>{Assert.False(P.Data.Flag(p.Body,"traffic","Wait"));Assert.False(P.Data.Flag(p.Body,"traffic","Avoid"));});
}
[Fact]
public void NearPortSideStepsStayOutsideTheConnectorPlaneAndAreOnlyTwoMetres()
{
var r=Encounter(new Vector3D(-4.5,0,-4),new Vector3D(4.5,0,-5),Vector3D.Right*2,Vector3D.Left*2);
var packet=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var miner=r.Miners.Single(t=>t.Id==packet.Target);var step=P.Data.ReadVector(packet.Body,"traffic","Point",Vector3D.Zero)-miner.Position;
Assert.InRange(step.Length(),.1,2.01);
Assert.True(Vector3D.Dot(miner.Position+step,Vector3D.Forward)>=miner.Radius-.001);
}
[Fact]
public void APortPlaneDoesNotReverseASafeShortStepTowardsTheOtherMiner()
{
var r=new FleetStateTests.Rig(2,false);r.SetPort(0,new Vector3D(-40,0,0),Vector3D.Right);r.Tick(1);
var loser=r.Miners[0];var winner=r.Miners[1];loser.Position=new Vector3D(-13,0,1);winner.Position=new Vector3D(-10,0,-4);winner.Velocity=Vector3D.Forward;
var side=(Vector3D)typeof(P.FleetController).GetMethod("YieldSide",BindingFlags.Instance|BindingFlags.NonPublic).Invoke(r.Fleet,new object[]{loser,winner});
Assert.True(side.X<-.9);Assert.True(Vector3D.Dot(side,loser.Position-winner.Position)>=0);
Assert.True(Vector3D.Dot(loser.Position+side*2-new Vector3D(-40,0,0),Vector3D.Right)>=loser.Radius);
}
[Theory]
[InlineData(.2,false)]
[InlineData(4,true)]
public void CommonTranslationDoesNotChangeTheActualEncounterDecision(double closure,bool expected)
{
foreach(var drift in new[]{Vector3D.Zero,Vector3D.Right*20,Vector3D.Up*20,new Vector3D(20,20,20)})
{
var r=Encounter(new Vector3D(-7.5,0,-50),new Vector3D(7.5,0,-50),drift+Vector3D.Right*closure,drift+Vector3D.Left*closure);
var packets=r.Sent.Where(p=>p.Kind=="TRAFFIC").ToList();Assert.Equal(2,packets.Count);
Assert.Equal(expected,packets.Any(p=>P.Data.Flag(p.Body,"traffic","Avoid")));
}
}
[Theory]
[InlineData(.625,false)]
[InlineData(5,true)]
public void SlowFeedbackBrakesEarlyEnoughEvenWithPlentifulHardwareThrust(double response,bool expected)
{
var r=Encounter(new Vector3D(-10,0,-50),new Vector3D(10,0,-50),Vector3D.Right*2,Vector3D.Left*2,100,response);
Assert.Equal(expected,r.Sent.Any(p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid")));
}
[Fact]
public void OldTelemetryDefaultsToTheExistingFeedbackResponse()
{
var body=new P.Telemetry().ToIni();body.Delete("telemetry","BrakeResponse");
Assert.Equal(.625,P.Telemetry.FromIni(body).BrakeResponse);
foreach(double invalid in new[]{0d,.2,5.1,double.NaN,double.PositiveInfinity})
{body.Set("telemetry","BrakeResponse",invalid);Assert.Throws<ArgumentException>(()=>P.Telemetry.FromIni(body));}
}
[Fact]
public void YieldEndsAsSoonAsResumingTheEncounterCourseIsSafeWithoutFortyFiveMetresSeparation()
{
var r=Encounter(new Vector3D(-4.5,0,-50),new Vector3D(4.5,0,-50),Vector3D.Right*2,Vector3D.Left*2);
var initial=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var yielding=r.Miners.Single(t=>t.Id==initial.Target);var point=P.Data.ReadVector(initial.Body,"traffic","Point",Vector3D.Zero);
yielding.Position+=Vector3D.Normalize(point-yielding.Position)*7;yielding.Velocity=Vector3D.Zero;
ObserveFrame(r);var clear=r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.False(P.Data.Flag(clear.Body,"traffic","Wait"));Assert.False(P.Data.Flag(clear.Body,"traffic","Avoid"));
Assert.True(Vector3D.Distance(r.Miners[0].Position,r.Miners[1].Position)<20);
}
}
}
using System;
using System.Linq;
using System.Reflection;
using VRageMath;
using Xunit;
using P=AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class PortYieldSafetyTests
{
static void ObserveFrame(FleetStateTests.Rig rig)
{
rig.Program.Now+=.6;
foreach(var miner in rig.Miners)rig.Receive("TELEMETRY",miner);
rig.Sent.Clear();rig.Tick(1);
}
[Fact]
public void AnExistingYieldStopsForAThirdShipAndResumesItsOriginalPointWhenClear()
{
var rig=new FleetStateTests.Rig(3,false);
typeof(P.FleetController).GetField("trafficRandom",BindingFlags.Instance|BindingFlags.NonPublic).SetValue(rig.Fleet,new Random(0));
foreach(var miner in rig.Miners){miner.State=P.FlightState.Holding;miner.Radius=4;miner.ThrustMargin=100;miner.HasRoute=true;}
rig.Miners[0].Position=new Vector3D(-5,0,-50);rig.Miners[0].Velocity=Vector3D.Right*2;rig.Miners[0].RouteTarget=new Vector3D(30,0,-50);
rig.Miners[1].Position=new Vector3D(5,0,-50);rig.Miners[1].Velocity=Vector3D.Left*2;rig.Miners[1].RouteTarget=new Vector3D(-30,0,-50);
var third=rig.Miners[2];third.Position=new Vector3D(1000,1000,-50);third.RouteTarget=third.Position;
rig.RegisterAll();rig.Tick(1);
// Obtain the actual randomly selected yielding ship and its original
// point from traffic packets, rather than installing a private plan.
var initial=Assert.Single(rig.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var yielding=rig.Miners.Single(t=>t.Id==initial.Target);
var priority=rig.Miners.Single(t=>t.Id==P.Data.Text(initial.Body,"traffic","Peer"));
var point=P.Data.ReadVector(initial.Body,"traffic","Point",Vector3D.Zero);
var lateral=Vector3D.Normalize(point-yielding.Position);
Assert.InRange(Vector3D.Distance(point,yielding.Position),1.9,2.01);
// A subsequent observed frame reports that the yielding ship has
// started its lateral move and another ship is closing along that leg.
// This tests one scheduling decision; no movement is integrated here.
yielding.Velocity=lateral*3;yielding.RouteTarget=point;
priority.Velocity=Vector3D.Zero;priority.HasRoute=false;
third.Position=yielding.Position+lateral*11;third.Velocity=-lateral*2;third.RouteTarget=yielding.Position;
ObserveFrame(rig);
var blocked=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.True(P.Data.Flag(blocked.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(blocked.Body,"traffic","Avoid"));
Assert.Equal(third.Id,P.Data.Text(blocked.Body,"traffic","Peer"));
Assert.Equal(point,P.Data.ReadVector(blocked.Body,"traffic","Point",Vector3D.Zero));
third.Position=new Vector3D(1000,1000,-50);third.Velocity=Vector3D.Zero;third.HasRoute=false;
ObserveFrame(rig);
var resumed=rig.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.False(P.Data.Flag(resumed.Body,"traffic","Wait"));
Assert.True(P.Data.Flag(resumed.Body,"traffic","Avoid"));
Assert.Equal(priority.Id,P.Data.Text(resumed.Body,"traffic","Peer"));
Assert.Equal(point,P.Data.ReadVector(resumed.Body,"traffic","Point",Vector3D.Zero));
}
static FleetStateTests.Rig Encounter(Vector3D first,Vector3D second,Vector3D firstVelocity,Vector3D secondVelocity,double thrustMargin=100,double response=.625)
{
var r=new FleetStateTests.Rig(2,false);
for(int n=0;n<2;n++)
{
var t=r.Miners[n];t.State=P.FlightState.Transit;t.Radius=3;t.ThrustMargin=thrustMargin;t.BrakeResponse=response;t.HasRoute=true;
t.Position=n==0?first:second;t.Velocity=n==0?firstVelocity:secondVelocity;t.RouteTarget=t.Position+t.Velocity*30;
}
r.RegisterAll();r.Tick(1);return r;
}
[Fact]
public void SlowDistantConvergenceAndNearbySafeMissesDoNotTriggerYielding()
{
var slow=Encounter(new Vector3D(-7.5,0,-50),new Vector3D(7.5,0,-50),Vector3D.Right*.2,Vector3D.Left*.2);
var miss=Encounter(new Vector3D(-5,0,-50),new Vector3D(5,0,-42),Vector3D.Right*2,Vector3D.Left*2);
foreach(var r in new[]{slow,miss})
Assert.All(r.Sent.Where(p=>p.Kind=="TRAFFIC"),p=>{Assert.False(P.Data.Flag(p.Body,"traffic","Wait"));Assert.False(P.Data.Flag(p.Body,"traffic","Avoid"));});
Assert.Equal(2,slow.Sent.Count(p=>p.Kind=="TRAFFIC"));Assert.Equal(2,miss.Sent.Count(p=>p.Kind=="TRAFFIC"));
}
[Theory]
[InlineData(0,false)]
[InlineData(.1,true)]
public void UnknownBrakingDoesNotInventAnEarlyEncounterButKnownLowBrakingRemainsProtected(double thrustMargin,bool mustYield)
{
var r=Encounter(new Vector3D(-25,0,-50),new Vector3D(25,0,-50),Vector3D.Right*3,Vector3D.Left*3,thrustMargin);
var responses=r.Sent.Where(p=>p.Kind=="TRAFFIC").ToList();
Assert.Equal(2,responses.Count);
if(mustYield)Assert.Single(responses,p=>P.Data.Flag(p.Body,"traffic","Avoid"));
else Assert.All(responses,p=>{Assert.False(P.Data.Flag(p.Body,"traffic","Wait"));Assert.False(P.Data.Flag(p.Body,"traffic","Avoid"));});
}
[Fact]
public void AnActiveYieldEndsWhenThePeerTurnsAwayEvenThoughItStillHasARoute()
{
var r=Encounter(new Vector3D(-4.5,0,-50),new Vector3D(4.5,0,-50),Vector3D.Right*2,Vector3D.Left*2);
var initial=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var yielding=r.Miners.Single(t=>t.Id==initial.Target);
var peer=r.Miners.Single(t=>t.Id==P.Data.Text(initial.Body,"traffic","Peer"));
// The peer now travels away at the yielding ship's speed. Their
// measured relative motion is safe, while the old encounter velocity
// would still describe a head-on collision. HasRoute stays true.
peer.Velocity=Vector3D.Normalize(peer.Position-yielding.Position)*2;
peer.RouteTarget=peer.Position+peer.Velocity*30;peer.HasRoute=true;
ObserveFrame(r);
var clear=r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.True(peer.HasRoute);
Assert.False(P.Data.Flag(clear.Body,"traffic","Wait"));
Assert.False(P.Data.Flag(clear.Body,"traffic","Avoid"));
}
[Fact]
public void BothEndsKeepTheSameEncounterUntilTheOriginalCoursesAreClear()
{
var r=Encounter(new Vector3D(-4.5,0,-50),new Vector3D(4.5,0,-50),Vector3D.Right*2,Vector3D.Left*2);
var initial=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var loser=r.Miners.Single(t=>t.Id==initial.Target);var peer=r.Miners.Single(t=>t.Id==P.Data.Text(initial.Body,"traffic","Peer"));
var side=Vector3D.Normalize(P.Data.ReadVector(initial.Body,"traffic","Point")-loser.Position);
loser.Velocity=side*.2;peer.Velocity=Vector3D.Zero;ObserveFrame(r);
var waiting=r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==peer.Id);
Assert.True(P.Data.Flag(waiting.Body,"traffic","Wait"));Assert.False(P.Data.Flag(waiting.Body,"traffic","Avoid"));
Assert.True(P.Data.Flag(r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==loser.Id).Body,"traffic","Avoid"));
loser.Position+=side*7;loser.Velocity=Vector3D.Zero;ObserveFrame(r);
Assert.All(r.Sent.Where(p=>p.Kind=="TRAFFIC"),p=>{Assert.False(P.Data.Flag(p.Body,"traffic","Wait"));Assert.False(P.Data.Flag(p.Body,"traffic","Avoid"));});
}
[Fact]
public void NearPortSideStepsStayOutsideTheConnectorPlaneAndAreOnlyTwoMetres()
{
var r=Encounter(new Vector3D(-4.5,0,-4),new Vector3D(4.5,0,-5),Vector3D.Right*2,Vector3D.Left*2);
var packet=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var miner=r.Miners.Single(t=>t.Id==packet.Target);var step=P.Data.ReadVector(packet.Body,"traffic","Point",Vector3D.Zero)-miner.Position;
Assert.InRange(step.Length(),.1,2.01);
Assert.True(Vector3D.Dot(miner.Position+step,Vector3D.Forward)>=miner.Radius-.001);
}
[Fact]
public void APortPlaneDoesNotReverseASafeShortStepTowardsTheOtherMiner()
{
var r=new FleetStateTests.Rig(2,false);r.SetPort(0,new Vector3D(-40,0,0),Vector3D.Right);r.Tick(1);
var loser=r.Miners[0];var winner=r.Miners[1];loser.Position=new Vector3D(-13,0,1);winner.Position=new Vector3D(-10,0,-4);winner.Velocity=Vector3D.Forward;
var side=(Vector3D)typeof(P.FleetController).GetMethod("YieldSide",BindingFlags.Instance|BindingFlags.NonPublic).Invoke(r.Fleet,new object[]{loser,winner});
Assert.True(side.X<-.9);Assert.True(Vector3D.Dot(side,loser.Position-winner.Position)>=0);
Assert.True(Vector3D.Dot(loser.Position+side*2-new Vector3D(-40,0,0),Vector3D.Right)>=loser.Radius);
}
[Theory]
[InlineData(.2,false)]
[InlineData(4,true)]
public void CommonTranslationDoesNotChangeTheActualEncounterDecision(double closure,bool expected)
{
foreach(var drift in new[]{Vector3D.Zero,Vector3D.Right*20,Vector3D.Up*20,new Vector3D(20,20,20)})
{
var r=Encounter(new Vector3D(-7.5,0,-50),new Vector3D(7.5,0,-50),drift+Vector3D.Right*closure,drift+Vector3D.Left*closure);
var packets=r.Sent.Where(p=>p.Kind=="TRAFFIC").ToList();Assert.Equal(2,packets.Count);
Assert.Equal(expected,packets.Any(p=>P.Data.Flag(p.Body,"traffic","Avoid")));
}
}
[Theory]
[InlineData(.625,false)]
[InlineData(5,true)]
public void SlowFeedbackBrakesEarlyEnoughEvenWithPlentifulHardwareThrust(double response,bool expected)
{
var r=Encounter(new Vector3D(-10,0,-50),new Vector3D(10,0,-50),Vector3D.Right*2,Vector3D.Left*2,100,response);
Assert.Equal(expected,r.Sent.Any(p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid")));
}
[Fact]
public void OldTelemetryDefaultsToTheExistingFeedbackResponse()
{
var body=new P.Telemetry().ToIni();body.Delete("telemetry","BrakeResponse");
Assert.Equal(.625,P.Telemetry.FromIni(body).BrakeResponse);
foreach(double invalid in new[]{0d,.2,5.1,double.NaN,double.PositiveInfinity})
{body.Set("telemetry","BrakeResponse",invalid);Assert.Throws<ArgumentException>(()=>P.Telemetry.FromIni(body));}
}
[Fact]
public void YieldEndsAsSoonAsResumingTheEncounterCourseIsSafeWithoutFortyFiveMetresSeparation()
{
var r=Encounter(new Vector3D(-4.5,0,-50),new Vector3D(4.5,0,-50),Vector3D.Right*2,Vector3D.Left*2);
var initial=Assert.Single(r.Sent,p=>p.Kind=="TRAFFIC"&&P.Data.Flag(p.Body,"traffic","Avoid"));
var yielding=r.Miners.Single(t=>t.Id==initial.Target);var point=P.Data.ReadVector(initial.Body,"traffic","Point",Vector3D.Zero);
yielding.Position+=Vector3D.Normalize(point-yielding.Position)*7;yielding.Velocity=Vector3D.Zero;
ObserveFrame(r);var clear=r.Sent.Last(p=>p.Kind=="TRAFFIC"&&p.Target==yielding.Id);
Assert.False(P.Data.Flag(clear.Body,"traffic","Wait"));Assert.False(P.Data.Flag(clear.Body,"traffic","Avoid"));
Assert.True(Vector3D.Distance(r.Miners[0].Position,r.Miners[1].Position)<20);
}
}
}