feat(net): track + echo movement sequence counters

Sprint 1a of the audit remediation plan.

Extracts the 4 movement sequence counters from inbound server messages
and echoes them in outbound MoveToState + AutonomousPosition instead
of hardcoded zeros:

- instanceSequence (slot 8 in CreateObject PhysicsData timestamps)
- teleportSequence (slot 4, also from PlayerTeleport 0xF751)
- serverControlSequence (slot 5)
- forcePositionSequence (slot 6, also from UpdatePosition 0xF748)

Source: holtburger player/types.rs:237-245, mutations.rs:182-706.
The server uses these to detect stale/reordered movement packets.
Previously all zeros → server couldn't distinguish epoch boundaries.

Changes:
- CreateObject.Parsed: +4 sequence fields extracted from timestamps
- UpdatePosition.Parsed: +3 sequence fields from trailing u16s
- WorldSession: tracks 4 counters, updates from CreateObject/
  UpdatePosition/PlayerTeleport for the player's own GUID
- GameWindow: passes tracked values to MoveToState.Build and
  AutonomousPosition.Build

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-13 13:45:39 +02:00
parent 9e5258152d
commit 11974c2099
4 changed files with 74 additions and 18 deletions

View file

@ -63,7 +63,10 @@ public static class UpdatePosition
uint Guid,
CreateObject.ServerPosition Position,
System.Numerics.Vector3? Velocity,
uint? PlacementId);
uint? PlacementId,
ushort InstanceSequence = 0,
ushort TeleportSequence = 0,
ushort ForcePositionSequence = 0);
/// <summary>
/// Parse a reassembled UpdatePosition body. <paramref name="body"/>
@ -144,17 +147,24 @@ public static class UpdatePosition
pos += 4;
}
// We deliberately skip the four u16 sequence numbers that
// follow; subscribers don't need them for simple rendering,
// and if the message is a deliberate out-of-order teleport the
// server always follows up with a fresher update anyway.
// 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;
}
var serverPos = new CreateObject.ServerPosition(
LandblockId: cellId,
PositionX: px, PositionY: py, PositionZ: pz,
RotationW: rw, RotationX: rx, RotationY: ry, RotationZ: rz);
return new Parsed(guid, serverPos, velocity, placementId);
return new Parsed(guid, serverPos, velocity, placementId,
instSeq, teleSeq, forceSeq);
}
catch
{