using System;
using System.Globalization;
using System.Threading;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P = AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class ProtocolTests
{
static P.Packet Message()
{
var body = new MyIni();
body.Set("job", "Id", "base-hole-1");
body.Set("job", "Entry", "0,0,0");
body.Set("job", "Note", "第一行\n[not an outer section]\nkey=value; retained");
return new P.Packet { Kind = "JOB", From = "base", Target = "miner-01", Id = "session-5", Session = "session", Seq = 5, Reliable = true, Source = 123, Body = body };
}
[Fact]
public void WaitingSlotRoundTripsAndLegacyTelemetryRequestsAnUnassignedSlot()
{
var body=new P.Telemetry {WaitingSlot=23}.ToIni();Assert.Equal(23,P.Telemetry.FromIni(body).WaitingSlot);
body.Delete("telemetry","WaitingSlot");Assert.Equal(-1,P.Telemetry.FromIni(body).WaitingSlot);
body.Set("telemetry","WaitingSlot",1000001);Assert.Throws<ArgumentException>(()=>P.Telemetry.FromIni(body));
}
[Fact]
public void CancellationWatermarkRoundTripsWithoutAcknowledgingLegacyTelemetry()
{
var telemetry=new P.Telemetry {CancelledThrough=100};var body=telemetry.ToIni();
Assert.Equal(100,P.Telemetry.FromIni(body).CancelledThrough);
body.Delete("telemetry","CancelledThrough");Assert.Equal(-1,P.Telemetry.FromIni(body).CancelledThrough);
body.Set("telemetry","CancelledThrough",-2);Assert.Throws<ArgumentException>(()=>P.Telemetry.FromIni(body));
}
[Fact]
public void PacketRoundTripKeepsAtomicMultilinePayloadAndRouting()
{
var input = Message(); P.Packet output;
Assert.True(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out output));
Assert.Equal(input.Kind, output.Kind); Assert.Equal(input.From, output.From);
Assert.Equal(input.Target, output.Target); Assert.Equal(input.Id, output.Id);
Assert.Equal(input.Session, output.Session); Assert.Equal(5, output.Seq);
Assert.True(output.Reliable);
Assert.Equal(input.Body.Get("job", "Note").ToString(), output.Body.Get("job", "Note").ToString());
Assert.Equal("0,0,0", output.Body.Get("job", "Entry").ToString());
// Transport identity comes from IGC, never from a sender-supplied payload.
Assert.Equal(0, output.Source);
}
[Fact]
public void PacketRejectsOtherFleetAndOtherProtocolVersion()
{
P.Packet parsed; string encoded = Message().Encode("mine-a");
Assert.False(P.Packet.TryDecode(encoded, "mine-b", out parsed));
var ini = new MyIni(); Assert.True(ini.TryParse(encoded)); ini.Set("wire", "Version", 1);
Assert.False(P.Packet.TryDecode(ini.ToString(), "mine-a", out parsed));
}
[Theory]
[InlineData("From", "")]
[InlineData("Kind", "")]
[InlineData("Session", "")]
[InlineData("Seq", "0")]
[InlineData("Seq", "-1")]
[InlineData("Seq", "NaN")]
[InlineData("Version", "Infinity")]
[InlineData("Payload", "[broken")]
public void PacketRejectsInvalidEnvelope(string field, string value)
{
var ini = new MyIni(); Assert.True(ini.TryParse(Message().Encode("mine-a")));
ini.Set("wire", field, value); P.Packet parsed;
Assert.False(P.Packet.TryDecode(ini.ToString(), "mine-a", out parsed));
Assert.Null(parsed);
}
[Fact]
public void PacketRejectsOversizeMalformedAndOverlongIdentities()
{
P.Packet parsed;
Assert.False(P.Packet.TryDecode(null, "mine-a", out parsed));
Assert.False(P.Packet.TryDecode(new string('x', 24001), "mine-a", out parsed));
Assert.False(P.Packet.TryDecode("[broken", "mine-a", out parsed));
var input = Message(); input.From = new string('a', 65);
Assert.False(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out parsed));
input = Message(); input.Kind = new string('x', 33);
Assert.False(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out parsed));
input = Message(); input.Id = new string('x', 161);
Assert.False(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out parsed));
}
[Theory]
[InlineData("NaN")]
[InlineData("Infinity")]
[InlineData("-Infinity")]
[InlineData("1e309")]
public void SettingsAndVectorsRejectNonFiniteNumbers(string value)
{
var config = new P.Settings("[Flight]\nCruiseSpeed=" + value, "miner", "miner-1");
var exception = Assert.Throws<ArgumentException>(() => config.Number("Flight", "CruiseSpeed", 15, .5, 95));
Assert.Contains(P.Settings.SectionName("Flight")+"."+P.Settings.ParameterName("CruiseSpeed"), exception.Message);
Vector3D vector; Assert.False(P.Data.TryVector("1," + value + ",3", out vector));
}
[Fact]
public void ZeroWorldCoordinatesAndGpsAreValid()
{
Vector3D vector;
Assert.True(P.Data.TryVector("0,0,0", out vector)); Assert.Equal(Vector3D.Zero, vector);
Assert.True(P.Data.TryGps("GPS:origin:0:0:0:", out vector)); Assert.Equal(Vector3D.Zero, vector);
Assert.False(P.Data.TryGps("GPS:bad:0:NaN:0:", out vector));
Assert.False(P.Data.TryVector("0,0", out vector));
}
[Fact]
public void SerializationIsInvariantUnderCommaDecimalCulture()
{
var previous = Thread.CurrentThread.CurrentCulture;
try
{
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("fr-FR");
Assert.Equal("1.25,-2.5,0", P.Data.Vector(new Vector3D(1.25, -2.5, 0)));
Vector3D parsed; Assert.True(P.Data.TryVector("1.25,-2.5,0", out parsed));
Assert.Equal(1.25, parsed.X, 12); Assert.Equal(-2.5, parsed.Y, 12);
var job = new P.Job { Id = "job-1", Entry = parsed, Direction = Vector3D.Forward, Up = Vector3D.Up, Depth = 12.75, Progress = 2.5 };
var restored = P.Job.FromIni(job.ToIni());
Assert.Equal(job.Entry, restored.Entry); Assert.Equal(12.75, restored.Depth, 12);
var config = new P.Settings("[Flight]\nCruiseSpeed=1.25", "miner", "miner-1");
Assert.Equal(1.25, config.Number("Flight", "CruiseSpeed", 15, .5, 95), 12);
}
finally { Thread.CurrentThread.CurrentCulture = previous; }
}
[Fact]
public void SettingsReportTheExactInvalidFieldAndPreserveInput()
{
string source = "[Flight]\nCruiseSpeed=96\n[Watchdog]\nEnabled=yes\n[Fleet]\nMaxMiners=17";
var config = new P.Settings(source, "miner", "miner-1");
Assert.Contains(P.Settings.SectionName("Flight")+"."+P.Settings.ParameterName("CruiseSpeed"), Assert.Throws<ArgumentException>(() => config.Number("Flight", "CruiseSpeed", 15, .5, 95)).Message);
Assert.Contains(P.Settings.SectionName("Watchdog")+"."+P.Settings.ParameterName("Enabled"), Assert.Throws<ArgumentException>(() => config.Flag("Watchdog", "Enabled", false)).Message);
Assert.Contains(P.Settings.SectionName("Fleet")+"."+P.Settings.ParameterName("MaxMiners"), Assert.Throws<ArgumentException>(() => config.Integer("Fleet", "MaxMiners", 16, 1, 16)).Message);
Assert.Equal("96", config.Text("Flight", "CruiseSpeed", "fallback"));
Assert.Equal(15, config.Number("Missing", "Speed", 15, 1, 95));
Assert.False(config.Flag("Missing", "Enabled", false));
}
[Fact]
public void SettingsIgnoreLegacyRoleButRejectMalformedIniAndInvalidIdentity()
{
Assert.Equal("miner", new P.Settings("[System]\nRole=fleet", "miner", "miner-1").Role);
Assert.Throws<ArgumentException>(() => new P.Settings("[broken", "miner", "miner-1"));
Assert.Throws<ArgumentException>(() => new P.Settings("[System]\nId=", "miner", "miner-1"));
Assert.Throws<ArgumentException>(() => new P.Settings("[System]\nFleetId=" + new string('a', 65), "miner", "miner-1"));
}
[Fact]
public void DockPointVelocityIncludesRotationAroundCenterOfMass()
{
var dock = new P.DockFrame { Center = new Vector3D(10, 20, 30), Position = new Vector3D(12, 20, 30), Velocity = new Vector3D(1, 2, 3), AngularVelocity = new Vector3D(0, 0, .5) };
Assert.Equal(new Vector3D(1, 3, 3), dock.PointVelocity(dock.Position));
Assert.Equal(dock.Velocity, dock.PointVelocity(dock.Center));
}
[Fact]
public void DockWindowRejectsStaleFastRotatingAndAcceleratingBases()
{
var config = new P.Settings("", "miner", "miner-1");
var dock = new P.DockFrame { ReceivedAt = 10, Forward = Vector3D.Forward, Up = Vector3D.Up };
Assert.True(dock.IsWindow(config, 10.5)); Assert.False(dock.IsWindow(config, 10.5001));
dock.Velocity = new Vector3D(5.01, 0, 0); Assert.False(dock.IsWindow(config, 10));
dock.Velocity = Vector3D.Zero; dock.AngularVelocity = new Vector3D(0, .21 * Math.PI / 180, 0); Assert.False(dock.IsWindow(config, 10));
dock.AngularVelocity = Vector3D.Zero; dock.Acceleration = new Vector3D(.1001, 0, 0); Assert.False(dock.IsWindow(config, 10));
}
[Fact]
public void DockWindowUsesConnectorSpeedInsteadOfCenterSpeed()
{
var config = new P.Settings("", "miner", "miner-1");
var dock = new P.DockFrame { ReceivedAt = 10, Position = new Vector3D(2000, 0, 0), AngularVelocity = new Vector3D(0, 0, .003) };
Assert.True(dock.AngularVelocity.Length() < .2 * Math.PI / 180);
Assert.Equal(6, dock.PointVelocity(dock.Position).Length(), 10);
Assert.False(dock.IsWindow(config, 10));
}
[Fact]
public void DockPredictionClampsHorizonAndRotatesTheConnectorOffset()
{
var dock = new P.DockFrame { Center = new Vector3D(100, 200, 300), Position = new Vector3D(110, 200, 300), Velocity = new Vector3D(2, 0, 0), AngularVelocity = new Vector3D(0, 0, 1), ReceivedAt = 10 };
Assert.Equal(dock.Position, dock.Predicted(9).Translation);
var expected = dock.Center + new Vector3D(1, 0, 0) + new Vector3D(10 * Math.Cos(.5), 10 * Math.Sin(.5), 0);
Assert.True(Vector3D.Distance(expected, dock.Predicted(1000).Translation) < 1e-9);
Assert.Equal(dock.Predicted(10.5).Translation, dock.Predicted(1000).Translation);
var frame = dock.Predicted(10.25);
Assert.Equal(1, frame.Forward.Length(), 10); Assert.Equal(1, frame.Up.Length(), 10);
Assert.True(Math.Abs(Vector3D.Dot(frame.Forward, frame.Up)) < 1e-10);
Assert.True(Vector3D.Distance(frame.Translation + frame.Forward * 30, dock.GetPoint(30, 10.25)) < 1e-9);
}
[Fact]
public void DockRoundTripPreservesGeometryButDoesNotTrustArrivalTimeOrSource()
{
var original = new P.DockFrame { BaseId = "base", Token = "lease-1", Owner = "miner-1", ConnectorId = 42, GridId = 7, Position = new Vector3D(1, 2, 3), Center = Vector3D.Zero, Velocity = new Vector3D(3, 2, 1), AngularVelocity = new Vector3D(0, .1, 0), ReceivedAt = 1234, Source = 9876, Connected = true };
var restored = P.DockFrame.FromIni(original.ToIni());
Assert.Equal(original.Position, restored.Position); Assert.Equal(original.Velocity, restored.Velocity);
Assert.Equal(original.GridId, restored.GridId); Assert.Equal(original.ConnectorId, restored.ConnectorId);
Assert.Equal(original.Owner, restored.Owner); Assert.Equal(original.Token, restored.Token); Assert.True(restored.Connected);
Assert.Equal(0, restored.ReceivedAt); Assert.Equal(0, restored.Source);
}
}
}
using System;
using System.Globalization;
using System.Threading;
using VRage.Game.ModAPI.Ingame.Utilities;
using VRageMath;
using Xunit;
using P = AutoMiningScript.Program;
namespace AutoMiningScript.Tests
{
public class ProtocolTests
{
static P.Packet Message()
{
var body = new MyIni();
body.Set("job", "Id", "base-hole-1");
body.Set("job", "Entry", "0,0,0");
body.Set("job", "Note", "第一行\n[not an outer section]\nkey=value; retained");
return new P.Packet { Kind = "JOB", From = "base", Target = "miner-01", Id = "session-5", Session = "session", Seq = 5, Reliable = true, Source = 123, Body = body };
}
[Fact]
public void WaitingSlotRoundTripsAndLegacyTelemetryRequestsAnUnassignedSlot()
{
var body=new P.Telemetry {WaitingSlot=23}.ToIni();Assert.Equal(23,P.Telemetry.FromIni(body).WaitingSlot);
body.Delete("telemetry","WaitingSlot");Assert.Equal(-1,P.Telemetry.FromIni(body).WaitingSlot);
body.Set("telemetry","WaitingSlot",1000001);Assert.Throws<ArgumentException>(()=>P.Telemetry.FromIni(body));
}
[Fact]
public void CancellationWatermarkRoundTripsWithoutAcknowledgingLegacyTelemetry()
{
var telemetry=new P.Telemetry {CancelledThrough=100};var body=telemetry.ToIni();
Assert.Equal(100,P.Telemetry.FromIni(body).CancelledThrough);
body.Delete("telemetry","CancelledThrough");Assert.Equal(-1,P.Telemetry.FromIni(body).CancelledThrough);
body.Set("telemetry","CancelledThrough",-2);Assert.Throws<ArgumentException>(()=>P.Telemetry.FromIni(body));
}
[Fact]
public void PacketRoundTripKeepsAtomicMultilinePayloadAndRouting()
{
var input = Message(); P.Packet output;
Assert.True(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out output));
Assert.Equal(input.Kind, output.Kind); Assert.Equal(input.From, output.From);
Assert.Equal(input.Target, output.Target); Assert.Equal(input.Id, output.Id);
Assert.Equal(input.Session, output.Session); Assert.Equal(5, output.Seq);
Assert.True(output.Reliable);
Assert.Equal(input.Body.Get("job", "Note").ToString(), output.Body.Get("job", "Note").ToString());
Assert.Equal("0,0,0", output.Body.Get("job", "Entry").ToString());
// Transport identity comes from IGC, never from a sender-supplied payload.
Assert.Equal(0, output.Source);
}
[Fact]
public void PacketRejectsOtherFleetAndOtherProtocolVersion()
{
P.Packet parsed; string encoded = Message().Encode("mine-a");
Assert.False(P.Packet.TryDecode(encoded, "mine-b", out parsed));
var ini = new MyIni(); Assert.True(ini.TryParse(encoded)); ini.Set("wire", "Version", 1);
Assert.False(P.Packet.TryDecode(ini.ToString(), "mine-a", out parsed));
}
[Theory]
[InlineData("From", "")]
[InlineData("Kind", "")]
[InlineData("Session", "")]
[InlineData("Seq", "0")]
[InlineData("Seq", "-1")]
[InlineData("Seq", "NaN")]
[InlineData("Version", "Infinity")]
[InlineData("Payload", "[broken")]
public void PacketRejectsInvalidEnvelope(string field, string value)
{
var ini = new MyIni(); Assert.True(ini.TryParse(Message().Encode("mine-a")));
ini.Set("wire", field, value); P.Packet parsed;
Assert.False(P.Packet.TryDecode(ini.ToString(), "mine-a", out parsed));
Assert.Null(parsed);
}
[Fact]
public void PacketRejectsOversizeMalformedAndOverlongIdentities()
{
P.Packet parsed;
Assert.False(P.Packet.TryDecode(null, "mine-a", out parsed));
Assert.False(P.Packet.TryDecode(new string('x', 24001), "mine-a", out parsed));
Assert.False(P.Packet.TryDecode("[broken", "mine-a", out parsed));
var input = Message(); input.From = new string('a', 65);
Assert.False(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out parsed));
input = Message(); input.Kind = new string('x', 33);
Assert.False(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out parsed));
input = Message(); input.Id = new string('x', 161);
Assert.False(P.Packet.TryDecode(input.Encode("mine-a"), "mine-a", out parsed));
}
[Theory]
[InlineData("NaN")]
[InlineData("Infinity")]
[InlineData("-Infinity")]
[InlineData("1e309")]
public void SettingsAndVectorsRejectNonFiniteNumbers(string value)
{
var config = new P.Settings("[Flight]\nCruiseSpeed=" + value, "miner", "miner-1");
var exception = Assert.Throws<ArgumentException>(() => config.Number("Flight", "CruiseSpeed", 15, .5, 95));
Assert.Contains(P.Settings.SectionName("Flight")+"."+P.Settings.ParameterName("CruiseSpeed"), exception.Message);
Vector3D vector; Assert.False(P.Data.TryVector("1," + value + ",3", out vector));
}
[Fact]
public void ZeroWorldCoordinatesAndGpsAreValid()
{
Vector3D vector;
Assert.True(P.Data.TryVector("0,0,0", out vector)); Assert.Equal(Vector3D.Zero, vector);
Assert.True(P.Data.TryGps("GPS:origin:0:0:0:", out vector)); Assert.Equal(Vector3D.Zero, vector);
Assert.False(P.Data.TryGps("GPS:bad:0:NaN:0:", out vector));
Assert.False(P.Data.TryVector("0,0", out vector));
}
[Fact]
public void SerializationIsInvariantUnderCommaDecimalCulture()
{
var previous = Thread.CurrentThread.CurrentCulture;
try
{
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("fr-FR");
Assert.Equal("1.25,-2.5,0", P.Data.Vector(new Vector3D(1.25, -2.5, 0)));
Vector3D parsed; Assert.True(P.Data.TryVector("1.25,-2.5,0", out parsed));
Assert.Equal(1.25, parsed.X, 12); Assert.Equal(-2.5, parsed.Y, 12);
var job = new P.Job { Id = "job-1", Entry = parsed, Direction = Vector3D.Forward, Up = Vector3D.Up, Depth = 12.75, Progress = 2.5 };
var restored = P.Job.FromIni(job.ToIni());
Assert.Equal(job.Entry, restored.Entry); Assert.Equal(12.75, restored.Depth, 12);
var config = new P.Settings("[Flight]\nCruiseSpeed=1.25", "miner", "miner-1");
Assert.Equal(1.25, config.Number("Flight", "CruiseSpeed", 15, .5, 95), 12);
}
finally { Thread.CurrentThread.CurrentCulture = previous; }
}
[Fact]
public void SettingsReportTheExactInvalidFieldAndPreserveInput()
{
string source = "[Flight]\nCruiseSpeed=96\n[Watchdog]\nEnabled=yes\n[Fleet]\nMaxMiners=17";
var config = new P.Settings(source, "miner", "miner-1");
Assert.Contains(P.Settings.SectionName("Flight")+"."+P.Settings.ParameterName("CruiseSpeed"), Assert.Throws<ArgumentException>(() => config.Number("Flight", "CruiseSpeed", 15, .5, 95)).Message);
Assert.Contains(P.Settings.SectionName("Watchdog")+"."+P.Settings.ParameterName("Enabled"), Assert.Throws<ArgumentException>(() => config.Flag("Watchdog", "Enabled", false)).Message);
Assert.Contains(P.Settings.SectionName("Fleet")+"."+P.Settings.ParameterName("MaxMiners"), Assert.Throws<ArgumentException>(() => config.Integer("Fleet", "MaxMiners", 16, 1, 16)).Message);
Assert.Equal("96", config.Text("Flight", "CruiseSpeed", "fallback"));
Assert.Equal(15, config.Number("Missing", "Speed", 15, 1, 95));
Assert.False(config.Flag("Missing", "Enabled", false));
}
[Fact]
public void SettingsIgnoreLegacyRoleButRejectMalformedIniAndInvalidIdentity()
{
Assert.Equal("miner", new P.Settings("[System]\nRole=fleet", "miner", "miner-1").Role);
Assert.Throws<ArgumentException>(() => new P.Settings("[broken", "miner", "miner-1"));
Assert.Throws<ArgumentException>(() => new P.Settings("[System]\nId=", "miner", "miner-1"));
Assert.Throws<ArgumentException>(() => new P.Settings("[System]\nFleetId=" + new string('a', 65), "miner", "miner-1"));
}
[Fact]
public void DockPointVelocityIncludesRotationAroundCenterOfMass()
{
var dock = new P.DockFrame { Center = new Vector3D(10, 20, 30), Position = new Vector3D(12, 20, 30), Velocity = new Vector3D(1, 2, 3), AngularVelocity = new Vector3D(0, 0, .5) };
Assert.Equal(new Vector3D(1, 3, 3), dock.PointVelocity(dock.Position));
Assert.Equal(dock.Velocity, dock.PointVelocity(dock.Center));
}
[Fact]
public void DockWindowRejectsStaleFastRotatingAndAcceleratingBases()
{
var config = new P.Settings("", "miner", "miner-1");
var dock = new P.DockFrame { ReceivedAt = 10, Forward = Vector3D.Forward, Up = Vector3D.Up };
Assert.True(dock.IsWindow(config, 10.5)); Assert.False(dock.IsWindow(config, 10.5001));
dock.Velocity = new Vector3D(5.01, 0, 0); Assert.False(dock.IsWindow(config, 10));
dock.Velocity = Vector3D.Zero; dock.AngularVelocity = new Vector3D(0, .21 * Math.PI / 180, 0); Assert.False(dock.IsWindow(config, 10));
dock.AngularVelocity = Vector3D.Zero; dock.Acceleration = new Vector3D(.1001, 0, 0); Assert.False(dock.IsWindow(config, 10));
}
[Fact]
public void DockWindowUsesConnectorSpeedInsteadOfCenterSpeed()
{
var config = new P.Settings("", "miner", "miner-1");
var dock = new P.DockFrame { ReceivedAt = 10, Position = new Vector3D(2000, 0, 0), AngularVelocity = new Vector3D(0, 0, .003) };
Assert.True(dock.AngularVelocity.Length() < .2 * Math.PI / 180);
Assert.Equal(6, dock.PointVelocity(dock.Position).Length(), 10);
Assert.False(dock.IsWindow(config, 10));
}
[Fact]
public void DockPredictionClampsHorizonAndRotatesTheConnectorOffset()
{
var dock = new P.DockFrame { Center = new Vector3D(100, 200, 300), Position = new Vector3D(110, 200, 300), Velocity = new Vector3D(2, 0, 0), AngularVelocity = new Vector3D(0, 0, 1), ReceivedAt = 10 };
Assert.Equal(dock.Position, dock.Predicted(9).Translation);
var expected = dock.Center + new Vector3D(1, 0, 0) + new Vector3D(10 * Math.Cos(.5), 10 * Math.Sin(.5), 0);
Assert.True(Vector3D.Distance(expected, dock.Predicted(1000).Translation) < 1e-9);
Assert.Equal(dock.Predicted(10.5).Translation, dock.Predicted(1000).Translation);
var frame = dock.Predicted(10.25);
Assert.Equal(1, frame.Forward.Length(), 10); Assert.Equal(1, frame.Up.Length(), 10);
Assert.True(Math.Abs(Vector3D.Dot(frame.Forward, frame.Up)) < 1e-10);
Assert.True(Vector3D.Distance(frame.Translation + frame.Forward * 30, dock.GetPoint(30, 10.25)) < 1e-9);
}
[Fact]
public void DockRoundTripPreservesGeometryButDoesNotTrustArrivalTimeOrSource()
{
var original = new P.DockFrame { BaseId = "base", Token = "lease-1", Owner = "miner-1", ConnectorId = 42, GridId = 7, Position = new Vector3D(1, 2, 3), Center = Vector3D.Zero, Velocity = new Vector3D(3, 2, 1), AngularVelocity = new Vector3D(0, .1, 0), ReceivedAt = 1234, Source = 9876, Connected = true };
var restored = P.DockFrame.FromIni(original.ToIni());
Assert.Equal(original.Position, restored.Position); Assert.Equal(original.Velocity, restored.Velocity);
Assert.Equal(original.GridId, restored.GridId); Assert.Equal(original.ConnectorId, restored.ConnectorId);
Assert.Equal(original.Owner, restored.Owner); Assert.Equal(original.Token, restored.Token); Assert.True(restored.Connected);
Assert.Equal(0, restored.ReceivedAt); Assert.Equal(0, restored.Source);
}
}
}