Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 11 additions & 25 deletions src/datatypes/lpVec3.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -92,11 +78,11 @@ 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)
if ((scale & 3) !== scale) {
if (scale > 3) {
return 6 + sizeOfVarInt(Math.floor(scale / 4))
}
return 6
Expand Down
38 changes: 38 additions & 0 deletions test/lpVec3Test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* 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)
}
})

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)
}
})
})
Loading