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:
parent
9e5258152d
commit
11974c2099
4 changed files with 74 additions and 18 deletions
|
|
@ -116,6 +116,12 @@ public sealed class WorldSession : IDisposable
|
|||
public event Action<State>? StateChanged;
|
||||
|
||||
public State CurrentState { get; private set; } = State.Disconnected;
|
||||
|
||||
/// <summary>Movement sequence counters for outbound MoveToState/AutonomousPosition.</summary>
|
||||
public ushort InstanceSequence => _instanceSequence;
|
||||
public ushort ServerControlSequence => _serverControlSequence;
|
||||
public ushort TeleportSequence => _teleportSequence;
|
||||
public ushort ForcePositionSequence => _forcePositionSequence;
|
||||
public CharacterList.Parsed? Characters { get; private set; }
|
||||
|
||||
private readonly NetClient _net;
|
||||
|
|
@ -129,6 +135,16 @@ public sealed class WorldSession : IDisposable
|
|||
private uint _clientPacketSequence;
|
||||
private uint _fragmentSequence = 1;
|
||||
|
||||
// Movement sequence counters — echoed back in every MoveToState and
|
||||
// AutonomousPosition so the server can detect stale/reordered packets.
|
||||
// Initialized from CreateObject PhysicsData timestamps, updated by
|
||||
// UpdatePosition/UpdateMotion/PlayerTeleport. Per holtburger:
|
||||
// instance=slot 8, teleport=slot 4, serverControl=slot 5, forcePosition=slot 6.
|
||||
private ushort _instanceSequence;
|
||||
private ushort _serverControlSequence;
|
||||
private ushort _teleportSequence;
|
||||
private ushort _forcePositionSequence;
|
||||
|
||||
// Phase A.3: background receive thread buffers raw UDP datagrams into
|
||||
// a channel so the render thread never blocks on socket I/O.
|
||||
private readonly Channel<byte[]> _inboundQueue =
|
||||
|
|
@ -400,6 +416,15 @@ public sealed class WorldSession : IDisposable
|
|||
var parsed = CreateObject.TryParse(body);
|
||||
if (parsed is not null)
|
||||
{
|
||||
// Initialize sequence counters from the player's own CreateObject.
|
||||
if (parsed.Value.Guid == Characters?.Characters.FirstOrDefault().Id)
|
||||
{
|
||||
_instanceSequence = parsed.Value.InstanceSequence;
|
||||
_teleportSequence = parsed.Value.TeleportSequence;
|
||||
_serverControlSequence = parsed.Value.ServerControlSequence;
|
||||
_forcePositionSequence = parsed.Value.ForcePositionSequence;
|
||||
}
|
||||
|
||||
EntitySpawned?.Invoke(new EntitySpawn(
|
||||
parsed.Value.Guid,
|
||||
parsed.Value.Position,
|
||||
|
|
@ -440,6 +465,14 @@ public sealed class WorldSession : IDisposable
|
|||
var posUpdate = UpdatePosition.TryParse(body);
|
||||
if (posUpdate is not null)
|
||||
{
|
||||
// Update sequence counters from the player's own position updates.
|
||||
if (posUpdate.Value.Guid == Characters?.Characters.FirstOrDefault().Id)
|
||||
{
|
||||
_instanceSequence = posUpdate.Value.InstanceSequence;
|
||||
_teleportSequence = posUpdate.Value.TeleportSequence;
|
||||
_forcePositionSequence = posUpdate.Value.ForcePositionSequence;
|
||||
}
|
||||
|
||||
PositionUpdated?.Invoke(new EntityPositionUpdate(
|
||||
posUpdate.Value.Guid,
|
||||
posUpdate.Value.Position,
|
||||
|
|
@ -457,10 +490,11 @@ public sealed class WorldSession : IDisposable
|
|||
// movement; the LoginComplete is sent from GameWindow once
|
||||
// the destination UpdatePosition is received and the player
|
||||
// has been snapped to the new cell.
|
||||
uint sequence = 0;
|
||||
ushort sequence = 0;
|
||||
if (body.Length >= 6)
|
||||
sequence = System.Buffers.Binary.BinaryPrimitives.ReadUInt16LittleEndian(
|
||||
body.AsSpan(4, 2));
|
||||
_teleportSequence = sequence; // track for outbound movement messages
|
||||
TeleportStarted?.Invoke(sequence);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue