feat(net): port retail physics spawn and event timestamps
Parse the complete PhysicsDesc plus F754/F755 packets, correct every PhysicsState bit, and gate all nine retail update channels with generation-safe immutable snapshots. Preserve ForcePosition, teleport, placement, velocity, parent, pickup, delete, and same-generation CreateObject ordering from the named client. Separate accepted logical lifecycle notifications from retained UI qualities, make GUID replacement and session reset clear every projection exactly once, and add packet, wraparound, malformed-input, parent FIFO, canonical-position, reconnect, and GUID-reuse conformance coverage. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
d53fe30ffe
commit
8a5d77f7f4
50 changed files with 3809 additions and 649 deletions
|
|
@ -214,6 +214,11 @@ public sealed class ProjectileVfxPacketFixtureTests
|
|||
Assert.Equal(0xF754u, BinaryPrimitives.ReadUInt32LittleEndian(packet));
|
||||
Assert.Equal(0x50000042u, BinaryPrimitives.ReadUInt32LittleEndian(packet[4..]));
|
||||
Assert.Equal(0x33000099u, BinaryPrimitives.ReadUInt32LittleEndian(packet[8..]));
|
||||
|
||||
var parsed = PlayPhysicsScript.TryParse(packet);
|
||||
Assert.Equal(new PlayPhysicsScript(0x50000042u, 0x33000099u), parsed);
|
||||
Assert.Null(PlayPhysicsScript.TryParse(packet[..^1]));
|
||||
Assert.Null(PlayPhysicsScript.TryParse([.. packet.ToArray(), 0]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -227,6 +232,28 @@ public sealed class ProjectileVfxPacketFixtureTests
|
|||
Assert.Equal(0xA5A5A5A5u, BinaryPrimitives.ReadUInt32LittleEndian(packet[8..]));
|
||||
Assert.Equal(0x7FC01234u, BinaryPrimitives.ReadUInt32LittleEndian(packet[12..]));
|
||||
Assert.True(float.IsNaN(BinaryPrimitives.ReadSingleLittleEndian(packet[12..])));
|
||||
|
||||
var parsed = PlayPhysicsScriptType.TryParse(packet);
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x50000042u, parsed.Value.Guid);
|
||||
Assert.Equal(0xA5A5A5A5u, parsed.Value.RawScriptType);
|
||||
Assert.Equal(0x7FC01234u, BitConverter.SingleToUInt32Bits(parsed.Value.Intensity));
|
||||
Assert.Null(PlayPhysicsScriptType.TryParse(packet[..^1]));
|
||||
Assert.Null(PlayPhysicsScriptType.TryParse([.. packet.ToArray(), 0]));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x7F800000u)]
|
||||
[InlineData(0xFF800000u)]
|
||||
public void TypedF755_PreservesInfiniteIntensityBits(uint intensityBits)
|
||||
{
|
||||
byte[] packet = ProjectileVfxPacketFixtures.TypedF755UnknownNaN.ToArray();
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(packet.AsSpan(12), intensityBits);
|
||||
|
||||
var parsed = PlayPhysicsScriptType.TryParse(packet);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(intensityBits, BitConverter.SingleToUInt32Bits(parsed.Value.Intensity));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -248,6 +275,25 @@ public sealed class ProjectileVfxPacketFixtureTests
|
|||
Assert.Equal((ushort)0xFFF8, parsed.Value.PositionSequence);
|
||||
Assert.Equal((ushort)0xFFF9, parsed.Value.MovementSequence);
|
||||
Assert.Equal((ushort)0x0000, parsed.Value.InstanceSequence);
|
||||
|
||||
var physics = Assert.IsType<PhysicsSpawnData>(parsed.Value.Physics);
|
||||
Assert.Equal(0x00000748u, physics.RawState);
|
||||
Assert.True(physics.State.HasFlag(AcDream.Core.Physics.PhysicsStateFlags.Missile));
|
||||
Assert.Equal(0x20000014u, physics.SoundTableId);
|
||||
Assert.Equal(0x34000005u, physics.PhysicsScriptTableId);
|
||||
Assert.Equal(new PhysicsAttachment(0x50000010u, 3u), physics.Parent);
|
||||
Assert.Equal([new PhysicsAttachment(0x50000011u, 4u)],
|
||||
physics.Children!.Value.ToArray());
|
||||
Assert.Equal(0.25f, physics.Translucency);
|
||||
Assert.Equal(new System.Numerics.Vector3(10f, 20f, 30f), physics.Velocity);
|
||||
Assert.Equal(new System.Numerics.Vector3(1f, 2f, 3f), physics.Acceleration);
|
||||
Assert.Equal(new System.Numerics.Vector3(0.1f, 0.2f, 0.3f), physics.AngularVelocity);
|
||||
Assert.Equal(0xA5A5A5A5u, physics.DefaultScriptType);
|
||||
Assert.Equal(0x7FC01234u,
|
||||
BitConverter.SingleToUInt32Bits(physics.DefaultScriptIntensity!.Value));
|
||||
Assert.Equal(new PhysicsTimestamps(
|
||||
0xFFF8, 0xFFF9, 0xFFFA, 0xFFFB, 0xFFFC,
|
||||
0xFFFD, 0xFFFE, 0xFFFF, 0x0000), physics.Timestamps);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -264,6 +310,10 @@ public sealed class ProjectileVfxPacketFixtureTests
|
|||
Assert.Equal(0u, animation.Value.PlacementId);
|
||||
Assert.Equal((ushort)0x1108, movement.Value.InstanceSequence);
|
||||
Assert.Equal((ushort)0x1108, animation.Value.InstanceSequence);
|
||||
Assert.NotNull(movement.Value.Physics!.Value.Movement);
|
||||
Assert.True(movement.Value.Physics.Value.Movement!.Value.RawData.IsEmpty);
|
||||
Assert.Null(movement.Value.Physics.Value.Movement!.Value.IsAutonomous);
|
||||
Assert.Equal(0u, animation.Value.Physics!.Value.AnimationFrame);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -277,7 +327,24 @@ public sealed class ProjectileVfxPacketFixtureTests
|
|||
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));
|
||||
var parsedPresent = CreateObject.TryParse(present);
|
||||
var parsedAbsent = CreateObject.TryParse(absent);
|
||||
Assert.NotNull(parsedPresent);
|
||||
Assert.NotNull(parsedAbsent);
|
||||
Assert.Equal(0u, parsedPresent.Value.Physics!.Value.PhysicsScriptTableId);
|
||||
Assert.Null(parsedAbsent.Value.Physics!.Value.PhysicsScriptTableId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GoldenCreateObject_RejectsEveryTruncationInsidePhysicsDesc()
|
||||
{
|
||||
byte[] packet = ProjectileVfxPacketFixtures.GoldenCreateObjectProjectilePhysicsFields;
|
||||
|
||||
// This fixture's PhysicsDesc ends at byte 168. Every shorter prefix
|
||||
// cuts either a gated field or the mandatory nine-timestamp tail.
|
||||
for (int length = 20; length < 168; length++)
|
||||
Assert.Null(CreateObject.TryParse(packet.AsSpan(0, length)));
|
||||
|
||||
Assert.NotNull(CreateObject.TryParse(packet.AsSpan(0, 168)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue