using System.Buffers.Binary;
using AcDream.Core.Net.Messages;
namespace AcDream.Core.Net.Tests.Messages;
///
/// Synthetic, non-DAT packet fixtures captured by the Step 0 retail wire
/// audit. Parser conformance tests consume these from Step 1 onward.
///
internal static class ProjectileVfxPacketFixtures
{
internal static readonly byte[] DirectF754 =
[
0x54, 0xF7, 0x00, 0x00,
0x42, 0x00, 0x00, 0x50,
0x99, 0x00, 0x00, 0x33,
];
internal static readonly byte[] TypedF755UnknownNaN =
[
0x55, 0xF7, 0x00, 0x00,
0x42, 0x00, 0x00, 0x50,
0xA5, 0xA5, 0xA5, 0xA5,
0x34, 0x12, 0xC0, 0x7F,
];
internal static readonly byte[] GoldenCreateObjectProjectilePhysicsFields = BuildGoldenCreateObject();
internal static readonly byte[] CreateObjectMovementPresentEmpty = BuildBranchFixture(
CreateObject.PhysicsDescriptionFlag.Movement, 0x50000051u);
internal static readonly byte[] CreateObjectAnimationFramePresentZero = BuildBranchFixture(
CreateObject.PhysicsDescriptionFlag.AnimationFrame, 0x50000052u);
internal static readonly byte[] CreateObjectPeTablePresentZero = BuildBranchFixture(
CreateObject.PhysicsDescriptionFlag.PeTable, 0x50000053u);
internal static readonly byte[] CreateObjectPeTableAbsent = BuildBranchFixture(
CreateObject.PhysicsDescriptionFlag.None, 0x50000054u);
private static byte[] BuildGoldenCreateObject()
{
var bytes = new List();
uint flags =
(uint)(CreateObject.PhysicsDescriptionFlag.CSetup
| CreateObject.PhysicsDescriptionFlag.MTable
| CreateObject.PhysicsDescriptionFlag.Velocity
| CreateObject.PhysicsDescriptionFlag.Acceleration
| CreateObject.PhysicsDescriptionFlag.Omega
| CreateObject.PhysicsDescriptionFlag.Parent
| CreateObject.PhysicsDescriptionFlag.Children
| CreateObject.PhysicsDescriptionFlag.ObjScale
| CreateObject.PhysicsDescriptionFlag.Friction
| CreateObject.PhysicsDescriptionFlag.Elasticity
| CreateObject.PhysicsDescriptionFlag.STable
| CreateObject.PhysicsDescriptionFlag.PeTable
| CreateObject.PhysicsDescriptionFlag.DefaultScript
| CreateObject.PhysicsDescriptionFlag.DefaultScriptIntensity
| CreateObject.PhysicsDescriptionFlag.Position
| CreateObject.PhysicsDescriptionFlag.Translucency);
WriteU32(CreateObject.Opcode);
WriteU32(0x50000042u);
bytes.AddRange([0x11, 0, 0, 0]); // empty ModelData
WriteU32(flags);
WriteU32(0x00000748u); // Missile | ReportCollisions | AlignPath | PathClipped | Gravity
WriteU32(0x7F010123u); // full cell
WriteF32(12.5f); WriteF32(25.0f); WriteF32(3.75f);
WriteF32(1.0f); WriteF32(0.0f); WriteF32(0.0f); WriteF32(0.0f); // WXYZ
WriteU32(0x09000001u);
WriteU32(0x20000014u);
WriteU32(0x34000005u);
WriteU32(0x0200040Du);
WriteU32(0x50000010u); WriteU32(3u);
WriteU32(1u); WriteU32(0x50000011u); WriteU32(4u);
WriteF32(1.25f);
WriteF32(0.125f);
WriteF32(0.05f);
WriteF32(0.25f);
WriteF32(10f); WriteF32(20f); WriteF32(30f);
WriteF32(1f); WriteF32(2f); WriteF32(3f);
WriteF32(0.1f); WriteF32(0.2f); WriteF32(0.3f);
WriteU32(0xA5A5A5A5u);
WriteU32(0x7FC01234u); // NaN intensity, preserving payload bits
for (ushort i = 0; i < 9; i++) WriteU16((ushort)(0xFFF8u + i));
Align4();
WriteU32(0u); // no optional WeenieHeader fields
WriteString16L("Golden Projectile");
WritePackedDword(7277u);
WritePackedDword(0u);
WriteU32(0x00000100u); // ammunition ItemType
WriteU32(0u);
Align4();
return bytes.ToArray();
void WriteU16(ushort value)
{
Span buffer = stackalloc byte[2];
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
bytes.AddRange(buffer.ToArray());
}
void WriteU32(uint value)
{
Span buffer = stackalloc byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
bytes.AddRange(buffer.ToArray());
}
void WriteF32(float value) => WriteU32(BitConverter.SingleToUInt32Bits(value));
void WritePackedDword(uint value)
{
if (value <= 0x7FFFu) WriteU16((ushort)value);
else
{
WriteU16((ushort)(((value >> 16) & 0x7FFFu) | 0x8000u));
WriteU16((ushort)value);
}
}
void WriteString16L(string value)
{
byte[] encoded = System.Text.Encoding.GetEncoding(1252).GetBytes(value);
WriteU16(checked((ushort)encoded.Length));
bytes.AddRange(encoded);
Align4();
}
void Align4()
{
while ((bytes.Count & 3) != 0) bytes.Add(0);
}
}
///
/// ACE SerializePhysicsData-shaped fixture with synthetic values. These
/// small packets isolate mutually-exclusive and present-zero branches that
/// a single broad CreateObject cannot cover.
///
private static byte[] BuildBranchFixture(CreateObject.PhysicsDescriptionFlag flags, uint guid)
{
var bytes = new List();
WriteU32(CreateObject.Opcode);
WriteU32(guid);
bytes.AddRange([0x11, 0, 0, 0]); // empty ModelData
WriteU32((uint)flags);
WriteU32(0); // explicit zero PhysicsState
if ((flags & CreateObject.PhysicsDescriptionFlag.Movement) != 0)
WriteU32(0); // present Movement with an empty byte blob; no autonomous dword
else if ((flags & CreateObject.PhysicsDescriptionFlag.AnimationFrame) != 0)
WriteU32(0); // present placement/frame with value zero
if ((flags & CreateObject.PhysicsDescriptionFlag.PeTable) != 0)
WriteU32(0); // present PhysicsScriptTable DID with value zero
for (ushort i = 0; i < 9; i++) WriteU16((ushort)(0x1100 + i));
Align4();
WriteU32(0); // no optional WeenieHeader fields
WriteString16L("Physics Branch Fixture");
WritePackedDword(7277);
WritePackedDword(0);
WriteU32(0x00000100u);
WriteU32(0);
Align4();
return bytes.ToArray();
void WriteU16(ushort value)
{
Span buffer = stackalloc byte[2];
BinaryPrimitives.WriteUInt16LittleEndian(buffer, value);
bytes.AddRange(buffer.ToArray());
}
void WriteU32(uint value)
{
Span buffer = stackalloc byte[4];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
bytes.AddRange(buffer.ToArray());
}
void WritePackedDword(uint value)
{
if (value <= 0x7FFFu) WriteU16((ushort)value);
else
{
WriteU16((ushort)(((value >> 16) & 0x7FFFu) | 0x8000u));
WriteU16((ushort)value);
}
}
void WriteString16L(string value)
{
byte[] encoded = System.Text.Encoding.GetEncoding(1252).GetBytes(value);
WriteU16(checked((ushort)encoded.Length));
bytes.AddRange(encoded);
Align4();
}
void Align4()
{
while ((bytes.Count & 3) != 0) bytes.Add(0);
}
}
}
public sealed class ProjectileVfxPacketFixtureTests
{
[Fact]
public void DirectF754_PreservesExactTwelveByteWireShape()
{
ReadOnlySpan packet = ProjectileVfxPacketFixtures.DirectF754;
Assert.Equal(12, packet.Length);
Assert.Equal(0xF754u, BinaryPrimitives.ReadUInt32LittleEndian(packet));
Assert.Equal(0x50000042u, BinaryPrimitives.ReadUInt32LittleEndian(packet[4..]));
Assert.Equal(0x33000099u, BinaryPrimitives.ReadUInt32LittleEndian(packet[8..]));
}
[Fact]
public void TypedF755_PreservesUnknownTypeAndNaNPayloadBits()
{
ReadOnlySpan packet = ProjectileVfxPacketFixtures.TypedF755UnknownNaN;
Assert.Equal(16, packet.Length);
Assert.Equal(0xF755u, BinaryPrimitives.ReadUInt32LittleEndian(packet));
Assert.Equal(0x50000042u, BinaryPrimitives.ReadUInt32LittleEndian(packet[4..]));
Assert.Equal(0xA5A5A5A5u, BinaryPrimitives.ReadUInt32LittleEndian(packet[8..]));
Assert.Equal(0x7FC01234u, BinaryPrimitives.ReadUInt32LittleEndian(packet[12..]));
Assert.True(float.IsNaN(BinaryPrimitives.ReadSingleLittleEndian(packet[12..])));
}
[Fact]
public void GoldenCreateObject_ExercisesBroadProjectilePhysicsFieldsWithoutLosingAlignment()
{
byte[] packet = ProjectileVfxPacketFixtures.GoldenCreateObjectProjectilePhysicsFields;
var parsed = CreateObject.TryParse(packet);
Assert.NotNull(parsed);
Assert.Equal(0x50000042u, parsed.Value.Guid);
Assert.Equal("Golden Projectile", parsed.Value.Name);
Assert.Equal(0x0200040Du, parsed.Value.SetupTableId);
Assert.Equal(0x09000001u, parsed.Value.MotionTableId);
Assert.Equal(1.25f, parsed.Value.ObjScale);
Assert.Equal(0.125f, parsed.Value.Friction);
Assert.Equal(0.05f, parsed.Value.Elasticity);
Assert.Equal(0x50000010u, parsed.Value.ParentGuid);
Assert.Equal((ushort)0xFFF8, parsed.Value.PositionSequence);
Assert.Equal((ushort)0xFFF9, parsed.Value.MovementSequence);
Assert.Equal((ushort)0x0000, parsed.Value.InstanceSequence);
}
[Fact]
public void MutuallyExclusiveMovementAndAnimationFrameFixturesRemainAligned()
{
var movement = CreateObject.TryParse(ProjectileVfxPacketFixtures.CreateObjectMovementPresentEmpty);
var animation = CreateObject.TryParse(ProjectileVfxPacketFixtures.CreateObjectAnimationFramePresentZero);
Assert.NotNull(movement);
Assert.NotNull(animation);
Assert.Equal(0x50000051u, movement.Value.Guid);
Assert.Equal(0x50000052u, animation.Value.Guid);
Assert.Null(movement.Value.PlacementId);
Assert.Equal(0u, animation.Value.PlacementId);
Assert.Equal((ushort)0x1108, movement.Value.InstanceSequence);
Assert.Equal((ushort)0x1108, animation.Value.InstanceSequence);
}
[Fact]
public void PeTableFixturesPinAbsentVersusPresentZeroWireShapes()
{
ReadOnlySpan present = ProjectileVfxPacketFixtures.CreateObjectPeTablePresentZero;
ReadOnlySpan absent = ProjectileVfxPacketFixtures.CreateObjectPeTableAbsent;
Assert.Equal((uint)CreateObject.PhysicsDescriptionFlag.PeTable,
BinaryPrimitives.ReadUInt32LittleEndian(present[12..]));
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(present[20..]));
Assert.Equal(0u, BinaryPrimitives.ReadUInt32LittleEndian(absent[12..]));
Assert.Equal(present.Length, absent.Length + sizeof(uint));
Assert.NotNull(CreateObject.TryParse(present));
Assert.NotNull(CreateObject.TryParse(absent));
}
}