From 6080e193bab0da2176bf8969193f174da951801a Mon Sep 17 00:00:00 2001 From: GenerelSchwerz Date: Sun, 12 Jul 2026 19:38:52 -0400 Subject: [PATCH 1/2] Fix lpVec3 packet endianness --- src/datatypes/lpVec3.js | 34 ++++++++++------------------------ test/lpVec3Test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 test/lpVec3Test.js diff --git a/src/datatypes/lpVec3.js b/src/datatypes/lpVec3.js index 9e0eb140..68351271 100644 --- a/src/datatypes/lpVec3.js +++ b/src/datatypes/lpVec3.js @@ -1,6 +1,5 @@ const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint -const DATA_BITS_MASK = 32767 const MAX_QUANTIZED_VALUE = 32766.0 const ABS_MIN_VALUE = 3.051944088384301e-5 const ABS_MAX_VALUE = 1.7179869183e10 @@ -15,10 +14,8 @@ function pack (value) { } function unpack (packed, shift) { - // We use division by power of 2 to simulate a 64-bit right shift - const val = Math.floor(packed / Math.pow(2, shift)) & DATA_BITS_MASK - const clamped = val > 32766 ? 32766 : val - return (clamped * 2.0) / 32766.0 - 1.0 + const quantized = Math.min(Math.floor(packed / Math.pow(2, shift)) % 0x8000, MAX_QUANTIZED_VALUE) + return (quantized * 2.0) / MAX_QUANTIZED_VALUE - 1.0 } function readLpVec3 (buffer, offset) { @@ -28,7 +25,7 @@ function readLpVec3 (buffer, offset) { } const b = buffer[offset + 1] - const c = buffer.readUInt32LE(offset + 2) + const c = buffer.readUInt32BE(offset + 2) // Combine into 48-bit safe integer (up to 2^53 is safe in JS) const packed = (c * 65536) + (b << 8) + a @@ -65,24 +62,13 @@ function writeLpVec3 (value, buffer, offset) { } const scale = Math.ceil(max) - const needsContinuation = (scale & 3) !== scale - const scaleByte = needsContinuation ? ((scale & 3) | 4) : (scale & 3) + const needsContinuation = scale > 3 + const markers = needsContinuation ? ((scale % 4) | 4) : scale + const packed = markers + pack(x / scale) * 0x8 + pack(y / scale) * 0x40000 + pack(z / scale) * 0x200000000 - const pX = pack(x / scale) - const pY = pack(y / scale) - const pZ = pack(z / scale) - - // Layout: - // [Z (15)] [Y (15)] [X (15)] [Flags (3)] - - // low32 contains Flags(3), X(15), and the first 14 bits of Y (3+15+14 = 32) - const low32 = (scaleByte | (pX << 3) | (pY << 18)) >>> 0 - - // high16 contains the 15th bit of Y and all 15 bits of Z - const high16 = ((pY >> 14) & 0x01) | (pZ << 1) - - buffer.writeUInt32LE(low32, offset) - buffer.writeUInt16LE(high16, offset + 4) + buffer.writeUInt8(packed % 0x100, offset) + buffer.writeUInt8(Math.floor(packed / 0x100) % 0x100, offset + 1) + buffer.writeUInt32BE(Math.floor(packed / 0x10000) % 0x100000000, offset + 2) if (needsContinuation) { return writeVarInt(Math.floor(scale / 4), buffer, offset + 6) @@ -96,7 +82,7 @@ function sizeOfLpVec3 (value) { if (max < ABS_MIN_VALUE) return 1 const scale = Math.ceil(max) - if ((scale & 3) !== scale) { + if (scale > 3) { return 6 + sizeOfVarInt(Math.floor(scale / 4)) } return 6 diff --git a/test/lpVec3Test.js b/test/lpVec3Test.js new file mode 100644 index 00000000..d029da1c --- /dev/null +++ b/test/lpVec3Test.js @@ -0,0 +1,30 @@ +/* eslint-env mocha */ +const assert = require('assert') +const [readLpVec3, writeLpVec3, sizeOfLpVec3] = require('../src/datatypes/lpVec3') + +const REAL_PAYLOADS = ['f9ff7ffeebed', '59e7800cebed', '51e880011541', '09e98000d8fd'] + +describe('lpVec3', () => { + it('decodes the zero vector as a single byte', () => { + const result = readLpVec3(Buffer.from('00', 'hex'), 0) + assert.deepStrictEqual(result.value, { x: 0, y: 0, z: 0 }) + assert.strictEqual(result.size, 1) + }) + + it('decodes real 1.21.11 server velocity bytes in blocks per tick', () => { + const result = readLpVec3(Buffer.from('f9ff7ffeebed', 'hex'), 0) + assert.strictEqual(result.size, 6) + assert.ok(Math.abs(result.value.y - (-0.0784)) < 0.001, 'unexpected y: ' + result.value.y) + assert.ok(Math.abs(result.value.x) < 1 && Math.abs(result.value.z) < 1, 'velocity out of range') + }) + + it('round-trips real server velocity bytes exactly', () => { + for (const hex of REAL_PAYLOADS) { + const value = readLpVec3(Buffer.from(hex, 'hex'), 0).value + const buffer = Buffer.alloc(16) + const end = writeLpVec3(value, buffer, 0) + assert.strictEqual(buffer.subarray(0, end).toString('hex'), hex, 'round-trip mismatch for ' + hex) + assert.strictEqual(sizeOfLpVec3(value), end, 'sizeOf mismatch for ' + hex) + } + }) +}) From 62cbb8cbade918ac17ab2c6e21cbc6238a1c4582 Mon Sep 17 00:00:00 2001 From: GenerelSchwerz Date: Wed, 15 Jul 2026 15:19:46 -0400 Subject: [PATCH 2/2] Keep lpVec3 sizing consistent with writes --- src/datatypes/lpVec3.js | 2 +- test/lpVec3Test.js | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/datatypes/lpVec3.js b/src/datatypes/lpVec3.js index 68351271..00667938 100644 --- a/src/datatypes/lpVec3.js +++ b/src/datatypes/lpVec3.js @@ -78,7 +78,7 @@ function writeLpVec3 (value, buffer, offset) { } function sizeOfLpVec3 (value) { - const max = Math.max(Math.abs(value.x), Math.abs(value.y), Math.abs(value.z)) + const max = Math.max(Math.abs(sanitize(value.x)), Math.abs(sanitize(value.y)), Math.abs(sanitize(value.z))) if (max < ABS_MIN_VALUE) return 1 const scale = Math.ceil(max) diff --git a/test/lpVec3Test.js b/test/lpVec3Test.js index d029da1c..1b5e8baf 100644 --- a/test/lpVec3Test.js +++ b/test/lpVec3Test.js @@ -27,4 +27,12 @@ describe('lpVec3', () => { assert.strictEqual(sizeOfLpVec3(value), end, 'sizeOf mismatch for ' + hex) } }) + + it('sizes sanitized values consistently', () => { + for (const x of [NaN, Infinity]) { + const value = { x, y: 0, z: 0 } + const size = sizeOfLpVec3(value) + assert.strictEqual(writeLpVec3(value, Buffer.alloc(size), 0), size) + } + }) })