using System.Text;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class NativeSystemPlayerPoseTests
{
private sealed class Memory:IReadOnlyGameMemory
{
public ulong ImageBase=>0x100000000;
public ulong ImageSize=>0x7000000;
private readonly Dictionary<ulong,byte> bytes=[];
internal Action<ulong>? BeforeRead;
public byte[] Read(ulong address,int length){BeforeRead?.Invoke(address);return Enumerable.Range(0,length).Select(index=>bytes.GetValueOrDefault(address+(ulong)index)).ToArray();}
internal void Put(ulong at,byte[] data){for(var index=0;index<data.Length;index++)bytes[at+(ulong)index]=data[index];}
internal void Q(ulong at,ulong value)=>Put(at,BitConverter.GetBytes(value));
internal void U(ulong at,uint value)=>Put(at,BitConverter.GetBytes(value));
internal void V(ulong at,Vector3d value){Put(at,BitConverter.GetBytes(value.X));Put(at+8,BitConverter.GetBytes(value.Y));Put(at+16,BitConverter.GetBytes(value.Z));}
internal void Affine(ulong at,Vector3d right,Vector3d up,Vector3d forward,Vector3d position)
{V(at,right);V(at+0x20,up);V(at+0x40,forward);V(at+0x60,position);}
}
private const ulong World=(3UL<<56)|100;
private const ulong Pose=0x52000,Record=0x90000;
private static Memory Scene()
{
var m=new Memory();var image=m.ImageBase;const ulong block=0x20000,life=0x24000,root=0x28000,owner=0x2A000,nav=0x30000,service=0x40058;
var fsd=block+0x80;
m.Q(image+0x5F2F040+0x68,0xEC0);m.Q(image+0x5F2F040+0x30,block);m.Q(block+0x38,1);
m.Q(fsd,image+0x5316CC8);m.Q(fsd+0x58,image+0x5316DF8);m.Q(fsd+0x20,owner);m.Q(owner,image+0x1000);
m.Q(image+0x1020,image+0x1100);m.Put(image+0x1100,[0x48,0x8D,0x41,0x10,0xC3]);m.Q(owner+0x10,life);
m.U(life+0xC0,8);m.Q(life+0xB0,42);m.Q(life+0xC8,root+0x30);
m.Q(fsd+0x338,nav);m.Q(nav,image+0x1800);m.Q(image+0x1820,image+0x2EC8100);m.Q(image+0x18B8,image+0x2EC8D60);
var getter=new byte[]{0x8B,0x05,0,0,0,0,0x89,0x02,0x48,0x8B,0xC2,0xC3};
BitConverter.GetBytes(unchecked((int)(0x2000-0x8AE696))).CopyTo(getter,2);m.Put(image+0x8AE690,getter);m.U(image+0x2000,123);
m.Q(root+0x10,0x48000);m.Q(0x48018,1);m.Q(0x48030,0x50000);m.Q(0x50000+123*16,0);m.Q(root+0x18,0x51000);m.Q(0x51000,Pose);
m.Q(Pose,image+0x2100);m.Q(image+0x2128,image+0x3CBBFE0);m.Q(image+0x2160,image+0x2D9C200);m.Q(Pose+0xE8,World);
m.Affine(Pose+0xF8,new(1,0,0),new(0,1,0),new(0,0,1),new(10,20,30));
m.Q(fsd+0x6A0,service);m.Q(service,image+0x3000);m.Q(image+0x3060,image+0x3CEB360);
m.Q(service-0x58+0x278,0x60000);m.Q(service-0x58+0x280,1);m.Q(0x60000,0x62000);m.Q(0x62000,0x60000);m.Q(0x62008,100);m.Q(0x62018,0x64000);
m.U(0x64004,5);m.U(0x64090,1);m.Q(0x64140,0x66000);m.Q(0x64148,1);m.Q(0x66000,0x67000);m.Q(0x67000,0x66000);m.Q(0x67008,World);m.Q(0x67010,0x68000);
m.Q(0x68378,0x64000);m.U(0x68384,0);m.Q(0x64380,1);m.Q(0x64388,0x70000);
// A90-degree body rotation distinguishes shared system coordinates
// from either an arbitrary ship-centred origin or local body offsets.
m.Affine(0x70100,new(0,1,0),new(-1,0,0),new(0,0,1),new(1000,2000,3000));
m.Q(nav+0x2C0,1);m.Q(nav+0x2C8,8);m.Q(nav+0x2D0,0x80000);m.U(nav+0x38C,7);m.Put(0x80000,[1]);m.U(0x80008,7);m.Q(0x80010,Record);
m.Q(Record+0x20,World);m.Affine(Record+0x30,new(1,0,0),new(0,1,0),new(0,0,1),new(40,50,60));
m.U(Record+0x184,3);m.U(Record+0x188,1);m.U(Record+0x130,uint.MaxValue);m.Q(Record+0x140,0x91000);m.Q(0x91000,4);m.Put(0x91014,Encoding.UTF8.GetBytes("Body"));
return m;
}
[Test]public void ShipAndDestinationShareTheVerifiedRotatedSystemCoordinates()
{
var snapshot=NativeSystemLocationReader.Read(Scene(),42,1,DateTimeOffset.UtcNow,"Fixture");
Check.Equal(new Vector3d(980,2010,3030),snapshot.PlayerPosition);Check.Equal(new Vector3d(0,0,1),snapshot.PlayerForward);
var place=snapshot.Locations.Single();Check.Equal(new Vector3d(950,2040,3060),place.Position);
Check.That(Math.Abs(place.DistanceMetres!.Value-Math.Sqrt(2700))<1e-10);
}
[Test]public void APlaceWithoutLoadedGeometryDoesNotReceiveAnInventedPosition()
{
var memory=Scene();memory.Q(Record+0x20,(4UL<<56)|100);
var snapshot=NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture");
Check.That(snapshot.PlayerPosition is {Finite:true}&&snapshot.Locations.Single().Position is null&&snapshot.Locations.Single().DistanceMetres is null);
}
[Test]public void InvalidActualShipPoseCannotBecomeAZeroOrigin()
{
var memory=Scene();memory.V(Pose+0xF8+0x60,new(double.NaN,20,30));
Check.Throws<InvalidDataException>(()=>NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture"));
}
[Test]public void AllPlacesAndShipUseOneCapturedFrameEvenIfTheLiveReferenceRotatesDuringNameReads()
{
var memory=Scene();
memory.BeforeRead=at=>{if(at==0x91000)memory.Affine(0x70100,new(1,0,0),new(0,0,1),new(0,-1,0),new(9000,8000,7000));};
var snapshot=NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture");
Check.Equal(new Vector3d(980,2010,3030),snapshot.PlayerPosition);
Check.Equal(new Vector3d(950,2040,3060),snapshot.Locations.Single().Position);
}
[Test]public void ATransformBufferThatChangesBetweenBothCopiesNeverBecomesASnapshot()
{
var memory=Scene();var change=0;
memory.BeforeRead=at=>{if(at==0x70000)memory.V(0x70160,new(1000+ ++change,2000,3000));};
Check.Throws<InvalidDataException>(()=>NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture"));
Check.Equal(6,change);
}
}
using System.Text;
using XFEEDToolBox.Core.Native;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class NativeSystemPlayerPoseTests
{
private sealed class Memory:IReadOnlyGameMemory
{
public ulong ImageBase=>0x100000000;
public ulong ImageSize=>0x7000000;
private readonly Dictionary<ulong,byte> bytes=[];
internal Action<ulong>? BeforeRead;
public byte[] Read(ulong address,int length){BeforeRead?.Invoke(address);return Enumerable.Range(0,length).Select(index=>bytes.GetValueOrDefault(address+(ulong)index)).ToArray();}
internal void Put(ulong at,byte[] data){for(var index=0;index<data.Length;index++)bytes[at+(ulong)index]=data[index];}
internal void Q(ulong at,ulong value)=>Put(at,BitConverter.GetBytes(value));
internal void U(ulong at,uint value)=>Put(at,BitConverter.GetBytes(value));
internal void V(ulong at,Vector3d value){Put(at,BitConverter.GetBytes(value.X));Put(at+8,BitConverter.GetBytes(value.Y));Put(at+16,BitConverter.GetBytes(value.Z));}
internal void Affine(ulong at,Vector3d right,Vector3d up,Vector3d forward,Vector3d position)
{V(at,right);V(at+0x20,up);V(at+0x40,forward);V(at+0x60,position);}
}
private const ulong World=(3UL<<56)|100;
private const ulong Pose=0x52000,Record=0x90000;
private static Memory Scene()
{
var m=new Memory();var image=m.ImageBase;const ulong block=0x20000,life=0x24000,root=0x28000,owner=0x2A000,nav=0x30000,service=0x40058;
var fsd=block+0x80;
m.Q(image+0x5F2F040+0x68,0xEC0);m.Q(image+0x5F2F040+0x30,block);m.Q(block+0x38,1);
m.Q(fsd,image+0x5316CC8);m.Q(fsd+0x58,image+0x5316DF8);m.Q(fsd+0x20,owner);m.Q(owner,image+0x1000);
m.Q(image+0x1020,image+0x1100);m.Put(image+0x1100,[0x48,0x8D,0x41,0x10,0xC3]);m.Q(owner+0x10,life);
m.U(life+0xC0,8);m.Q(life+0xB0,42);m.Q(life+0xC8,root+0x30);
m.Q(fsd+0x338,nav);m.Q(nav,image+0x1800);m.Q(image+0x1820,image+0x2EC8100);m.Q(image+0x18B8,image+0x2EC8D60);
var getter=new byte[]{0x8B,0x05,0,0,0,0,0x89,0x02,0x48,0x8B,0xC2,0xC3};
BitConverter.GetBytes(unchecked((int)(0x2000-0x8AE696))).CopyTo(getter,2);m.Put(image+0x8AE690,getter);m.U(image+0x2000,123);
m.Q(root+0x10,0x48000);m.Q(0x48018,1);m.Q(0x48030,0x50000);m.Q(0x50000+123*16,0);m.Q(root+0x18,0x51000);m.Q(0x51000,Pose);
m.Q(Pose,image+0x2100);m.Q(image+0x2128,image+0x3CBBFE0);m.Q(image+0x2160,image+0x2D9C200);m.Q(Pose+0xE8,World);
m.Affine(Pose+0xF8,new(1,0,0),new(0,1,0),new(0,0,1),new(10,20,30));
m.Q(fsd+0x6A0,service);m.Q(service,image+0x3000);m.Q(image+0x3060,image+0x3CEB360);
m.Q(service-0x58+0x278,0x60000);m.Q(service-0x58+0x280,1);m.Q(0x60000,0x62000);m.Q(0x62000,0x60000);m.Q(0x62008,100);m.Q(0x62018,0x64000);
m.U(0x64004,5);m.U(0x64090,1);m.Q(0x64140,0x66000);m.Q(0x64148,1);m.Q(0x66000,0x67000);m.Q(0x67000,0x66000);m.Q(0x67008,World);m.Q(0x67010,0x68000);
m.Q(0x68378,0x64000);m.U(0x68384,0);m.Q(0x64380,1);m.Q(0x64388,0x70000);
// A90-degree body rotation distinguishes shared system coordinates
// from either an arbitrary ship-centred origin or local body offsets.
m.Affine(0x70100,new(0,1,0),new(-1,0,0),new(0,0,1),new(1000,2000,3000));
m.Q(nav+0x2C0,1);m.Q(nav+0x2C8,8);m.Q(nav+0x2D0,0x80000);m.U(nav+0x38C,7);m.Put(0x80000,[1]);m.U(0x80008,7);m.Q(0x80010,Record);
m.Q(Record+0x20,World);m.Affine(Record+0x30,new(1,0,0),new(0,1,0),new(0,0,1),new(40,50,60));
m.U(Record+0x184,3);m.U(Record+0x188,1);m.U(Record+0x130,uint.MaxValue);m.Q(Record+0x140,0x91000);m.Q(0x91000,4);m.Put(0x91014,Encoding.UTF8.GetBytes("Body"));
return m;
}
[Test]public void ShipAndDestinationShareTheVerifiedRotatedSystemCoordinates()
{
var snapshot=NativeSystemLocationReader.Read(Scene(),42,1,DateTimeOffset.UtcNow,"Fixture");
Check.Equal(new Vector3d(980,2010,3030),snapshot.PlayerPosition);Check.Equal(new Vector3d(0,0,1),snapshot.PlayerForward);
var place=snapshot.Locations.Single();Check.Equal(new Vector3d(950,2040,3060),place.Position);
Check.That(Math.Abs(place.DistanceMetres!.Value-Math.Sqrt(2700))<1e-10);
}
[Test]public void APlaceWithoutLoadedGeometryDoesNotReceiveAnInventedPosition()
{
var memory=Scene();memory.Q(Record+0x20,(4UL<<56)|100);
var snapshot=NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture");
Check.That(snapshot.PlayerPosition is {Finite:true}&&snapshot.Locations.Single().Position is null&&snapshot.Locations.Single().DistanceMetres is null);
}
[Test]public void InvalidActualShipPoseCannotBecomeAZeroOrigin()
{
var memory=Scene();memory.V(Pose+0xF8+0x60,new(double.NaN,20,30));
Check.Throws<InvalidDataException>(()=>NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture"));
}
[Test]public void AllPlacesAndShipUseOneCapturedFrameEvenIfTheLiveReferenceRotatesDuringNameReads()
{
var memory=Scene();
memory.BeforeRead=at=>{if(at==0x91000)memory.Affine(0x70100,new(1,0,0),new(0,0,1),new(0,-1,0),new(9000,8000,7000));};
var snapshot=NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture");
Check.Equal(new Vector3d(980,2010,3030),snapshot.PlayerPosition);
Check.Equal(new Vector3d(950,2040,3060),snapshot.Locations.Single().Position);
}
[Test]public void ATransformBufferThatChangesBetweenBothCopiesNeverBecomesASnapshot()
{
var memory=Scene();var change=0;
memory.BeforeRead=at=>{if(at==0x70000)memory.V(0x70160,new(1000+ ++change,2000,3000));};
Check.Throws<InvalidDataException>(()=>NativeSystemLocationReader.Read(memory,42,1,DateTimeOffset.UtcNow,"Fixture"));
Check.Equal(6,change);
}
}