fix(net): VectorUpdate parser was reading guid from opcode bytes — remote jumps invisible

User report: "in ACdream client, when other client is jumping,
nothing happens at all".

Diagnostic [VU.recv] revealed the parser was reading
guid = 0x0000F74E (= the opcode itself) and velocity values in
the billions:

  [VU.recv] guid=0x0000F74E vel=(8589944832.00,0.00,0.00)
            isLocal=False hasRemote=False

WorldSession.ProcessDatagram passes the FULL reassembled body
including the 4-byte opcode at offset 0 — every other parser
in src/AcDream.Core.Net/Messages/ verifies the opcode word
before reading payload (UpdateMotion.TryParse:77,
UpdatePosition.TryParse, etc.). VectorUpdate.TryParse skipped
that step and read every field shifted four bytes early,
making the guid the opcode bytes and the velocities random
floats from later in the buffer. With guid=0xF74E never
matching any tracked entity, OnLiveVectorUpdated returned
early and remote jumps rendered nothing.

Fix: read + verify opcode at offset 0 in TryParse, then read
guid at offset 4, velocity at 8/12/16, omega at 20/24/28,
sequences at 32/34. Body length now 4 (opcode) + 32 (payload).

Tests stay 1222 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-26 21:51:36 +02:00
parent 68d521df4f
commit 0ebf0cad09

View file

@ -39,27 +39,38 @@ public static class VectorUpdate
ushort VectorSequence);
/// <summary>
/// Parse a 0xF74E body. Returns null if the buffer is truncated or
/// malformed (sequence-number mismatch is not checked here — the
/// session-level handler decides what to do).
/// Parse a 0xF74E body. <paramref name="body"/> must start with the
/// 4-byte opcode (matches the convention used by UpdateMotion /
/// UpdatePosition / etc.). Returns null on truncation or opcode
/// mismatch.
/// </summary>
public static Parsed? TryParse(ReadOnlySpan<byte> body)
{
if (body.Length < 32) return null;
// K-fix16 (2026-04-26): body convention includes the opcode at
// offset 0 — every other parser in this folder verifies the
// opcode word before reading payload fields. The previous
// version of this method skipped that, reading guid from the
// opcode bytes (and shifting every subsequent field by 4),
// which is why VU.recv lines showed guid=0xF74E and gigantic
// garbage velocity values.
if (body.Length < 4 + 32) return null;
try
{
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body[..4]);
uint opcode = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(0, 4));
if (opcode != Opcode) return null;
float vx = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(4, 4)));
float vy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(8, 4)));
float vz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(12, 4)));
uint guid = BinaryPrimitives.ReadUInt32LittleEndian(body.Slice(4, 4));
float ox = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(16, 4)));
float oy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(20, 4)));
float oz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(24, 4)));
float vx = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(8, 4)));
float vy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(12, 4)));
float vz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(16, 4)));
ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(28, 2));
ushort vecSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(30, 2));
float ox = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(20, 4)));
float oy = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(24, 4)));
float oz = BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(body.Slice(28, 4)));
ushort instSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(32, 2));
ushort vecSeq = BinaryPrimitives.ReadUInt16LittleEndian(body.Slice(34, 2));
return new Parsed(guid, new Vector3(vx, vy, vz), new Vector3(ox, oy, oz), instSeq, vecSeq);
}