Skip to content

1.21.9: Add lpvec3 data type - #1453

Merged
extremeheat merged 8 commits into
PrismarineJS:pc1_21_9from
SuperGamerTron:pc1_21_10
Jan 22, 2026
Merged

extremeheat merged 8 commits into
PrismarineJS:pc1_21_9from
SuperGamerTron:pc1_21_10

Conversation

@SuperGamerTron

Copy link
Copy Markdown
Contributor

@SuperGamerTron
SuperGamerTron changed the base branch from pc1_21_10 to pc1_21_9 January 17, 2026 11:57
@SuperGamerTron SuperGamerTron changed the title Pc 1.21.10 Pc 1.21.9 Jan 17, 2026
Comment thread src/datatypes/lpVec3.js
}

function unpack (bits) {
const masked = Number(bits & BigInt(DATA_BITS_MASK))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bigint ops are very slow in JS. Why is it needed here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how it's implemented in Java. If you mean the BigInt constructor, it can be moved into a constant variable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The protocol code should ideally be written optimally since it runs on each inbound packet. The code looks written by LLM, did you write it?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In JS, bigint when unbounded (not using BigInt.asIntN / BigInt.asUintN to construct) requires heap memory allocations and uses string based arithmetic. So it's much slower than using a raw integer.

From Gemini to optimize:

const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint

module.exports = {
  lpvec3: [readLpVec3, writeLpVec3, sizeOfLpVec3]
}

const DATA_BITS_MASK = 32767
const MAX_QUANTIZED_VALUE = 32766.0
const ABS_MIN_VALUE = 3.051944088384301e-5
const ABS_MAX_VALUE = 1.7179869183e10

function sanitize (value) {
  if (isNaN(value)) return 0.0
  return Math.max(-ABS_MAX_VALUE, Math.min(value, ABS_MAX_VALUE))
}

function pack (value) {
  return Math.round((value * 0.5 + 0.5) * MAX_QUANTIZED_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
}

function readLpVec3 (buffer, offset) {
  const a = buffer[offset]
  if (a === 0) {
    return { value: { x: 0, y: 0, z: 0 }, size: 1 }
  }

  const b = buffer[offset + 1]
  const c = buffer.readUInt32LE(offset + 2)

  // Combine into 48-bit safe integer (up to 2^53 is safe in JS)
  const packed = (c * 65536) + (b << 8) + a

  let scale = a & 3
  let size = 6

  if ((a & 4) === 4) {
    const { value: varIntVal, size: varIntSize } = readVarInt(buffer, offset + 6)
    scale = (varIntVal * 4) + scale
    size += varIntSize
  }

  return {
    value: {
      x: unpack(packed, 3) * scale,
      y: unpack(packed, 18) * scale,
      z: unpack(packed, 33) * scale
    },
    size
  }
}

function writeLpVec3 (value, buffer, offset) {
  const x = sanitize(value.x)
  const y = sanitize(value.y)
  const z = sanitize(value.z)

  const max = Math.max(Math.abs(x), Math.abs(y), Math.abs(z))

  if (max < ABS_MIN_VALUE) {
    buffer[offset] = 0
    return offset + 1
  }

  const scale = Math.ceil(max)
  const needsContinuation = (scale & 3) !== scale
  const scaleByte = needsContinuation ? ((scale & 3) | 4) : (scale & 3)

  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)

  if (needsContinuation) {
    const varIntSize = writeVarInt(Math.floor(scale / 4), buffer, offset + 6)
    return offset + 6 + varIntSize
  }

  return offset + 6
}

function sizeOfLpVec3 (value) {
  const max = Math.max(Math.abs(value.x), Math.abs(value.y), Math.abs(value.z))
  if (max < ABS_MIN_VALUE) return 1
  
  const scale = Math.ceil(max)
  if ((scale & 3) !== scale) {
    return 6 + sizeOfVarInt(Math.floor(scale / 4))
  }
  return 6
}

@extremeheat extremeheat changed the title Pc 1.21.9 1.21.9: Add lpvec3 data type Jan 22, 2026
@extremeheat
extremeheat merged commit a8b2641 into PrismarineJS:pc1_21_9 Jan 22, 2026
3 checks passed
extremeheat added a commit that referenced this pull request Jan 26, 2026
* Update to version 1.21.9

* Fix branch name in CI workflow for minecraft-data

* Update ci.yml

* 1.21.9: Add lpvec3 data type (#1453)

* Update to version 1.21.10

* Update ci.yml

* Update ci.yml

* Update packetTest.js types

* Handle code_of_conduct packets

* Implement lpVec3

* Update ci.yml

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>

* optimise lpvec3

* lint

* Update lpVec3.js

* typo

* Update lpVec3.js

* Update lpVec3.js

* ExplosionParticleEntry

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
Co-authored-by: extremeheat <extreme@protonmail.ch>
Co-authored-by: SuperGamerTron <45374546+SuperGamerTron@users.noreply.github.com>
@SuperGamerTron
SuperGamerTron deleted the pc1_21_10 branch February 4, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants