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:
Erik 2026-07-14 00:22:17 +02:00
parent d53fe30ffe
commit 8a5d77f7f4
50 changed files with 3809 additions and 649 deletions

View file

@ -27,8 +27,7 @@ namespace AcDream.Core.Net.Messages;
/// <item><b>Velocity</b> — 3xf32 if HasVelocity set</item>
/// <item><b>PlacementID</b> — u32 if HasPlacementID set</item>
/// <item><b>Four u16 sequence numbers</b> — instance, position, teleport,
/// forcePosition. We don't currently check these for freshness but
/// we must consume them to walk the buffer correctly.</item>
/// forcePosition. Runtime freshness gates consume all four.</item>
/// </list>
/// </summary>
public static class UpdatePosition
@ -66,6 +65,7 @@ public static class UpdatePosition
uint? PlacementId,
bool IsGrounded,
ushort InstanceSequence = 0,
ushort PositionSequence = 0,
ushort TeleportSequence = 0,
ushort ForcePositionSequence = 0);
@ -149,15 +149,12 @@ public static class UpdatePosition
}
// Four u16 sequence numbers: instance, position, teleport, forcePosition.
ushort instSeq = 0, teleSeq = 0, forceSeq = 0;
if (body.Length - pos >= 8)
{
instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos));
// pos+2 = positionSequence (not tracked by movement)
teleSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos + 4));
forceSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos + 6));
pos += 8;
}
if (body.Length - pos < 8) return null;
ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos));
ushort posSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos + 2));
ushort teleSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos + 4));
ushort forceSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(pos + 6));
pos += 8;
var serverPos = new CreateObject.ServerPosition(
LandblockId: cellId,
@ -166,7 +163,7 @@ public static class UpdatePosition
return new Parsed(guid, serverPos, velocity, placementId,
IsGrounded: (flags & PositionFlags.IsGrounded) != 0,
instSeq, teleSeq, forceSeq);
instSeq, posSeq, teleSeq, forceSeq);
}
catch
{