From e3424f6308677fb3910d6b846394ec7055159445 Mon Sep 17 00:00:00 2001 From: DallasCarraher Date: Mon, 14 Sep 2026 01:00:16 -0700 Subject: [PATCH 1/3] Add PC 26.2 chunk implementation mapping Minecraft 26.2 (protocol 776) uses the same chunk section format as 26.1, so it maps to the existing 1.18-family implementation rather than needing a new one. Without this, prismarine-chunk throws "No chunk implementation for pc 26.2 found" for any consumer (mineflayer, etc.) as soon as minecraft-data has data for 26.2. Tested against PrismarineJS/minecraft-data#1298 (26.2 support, not yet merged): full test suite passes (328/328), standard lint clean. Co-Authored-By: Claude Sonnet 5 --- src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index b4a2abe4..ca4181ce 100644 --- a/src/index.js +++ b/src/index.js @@ -14,7 +14,8 @@ const chunkImplementations = { 1.19: require('./pc/1.18/chunk'), '1.20': require('./pc/1.18/chunk'), 1.21: require('./pc/1.18/chunk'), - 26.1: require('./pc/1.18/chunk') + 26.1: require('./pc/1.18/chunk'), + 26.2: require('./pc/1.18/chunk') }, bedrock: { 0.14: require('./bedrock/0.14/chunk'), From c3869a7592487f2572b08d4c9bd4d728d1d7ce48 Mon Sep 17 00:00:00 2001 From: DallasCarraher Date: Tue, 15 Sep 2026 21:38:12 -0700 Subject: [PATCH 2/3] Add 26.3 chunk implementation (same as 26.1/26.2) --- src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index ca4181ce..33ce465b 100644 --- a/src/index.js +++ b/src/index.js @@ -15,7 +15,8 @@ const chunkImplementations = { '1.20': require('./pc/1.18/chunk'), 1.21: require('./pc/1.18/chunk'), 26.1: require('./pc/1.18/chunk'), - 26.2: require('./pc/1.18/chunk') + 26.2: require('./pc/1.18/chunk'), + 26.3: require('./pc/1.18/chunk') }, bedrock: { 0.14: require('./bedrock/0.14/chunk'), From 7382b4fef1c56290524665e7b9af58b7d1f34791 Mon Sep 17 00:00:00 2001 From: DallasCarraher Date: Wed, 16 Sep 2026 00:35:55 -0700 Subject: [PATCH 3/3] pc 26.3: read/write light-section masks as byte arrays, not long arrays Found live-testing against a 26.3 Realm: loadParsedLight threw "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined" from readSection, because the light-section byte-array data (skyLight/blockLight) came up short versus what the corresponding mask (skyLightMask/etc.) claimed. Root cause is on the wire, not in this parsing logic: Mojang's BIT_SET codec for these four mask fields switched from a varint-count-of-longs encoding to a varint-count-of-raw-bytes encoding starting at 26.3 (protocol 777) - confirmed by decompiling ByteBufCodecs.BIT_SET from the official server jar, and cross-checked against the still-long-array-based 1.21.9/773 schema. Companion minecraft-data fix (skyLightMask etc. now typed ByteArray for 26.3) is in the pc-26.3-support branch there. Feeding the new byte arrays through the existing BitArray.fromLongArray silently produced impossible bit positions (up to 120, for a chunk with far fewer than 120 sections), which is what caused the eventual short-array crash a few fields downstream. Added BitArray.fromByteArray/toByteArray (matching java.util.BitSet's little-endian-within-byte convention) and gated their use to 26.3+ so nothing changes for older, already-working protocol versions. Co-Authored-By: Claude Sonnet 5 --- src/pc/1.18/ChunkColumn.js | 22 ++++++++++++++----- src/pc/common/BitArrayNoSpan.js | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/pc/1.18/ChunkColumn.js b/src/pc/1.18/ChunkColumn.js index 5f50220a..8bfb3b4a 100644 --- a/src/pc/1.18/ChunkColumn.js +++ b/src/pc/1.18/ChunkColumn.js @@ -13,6 +13,16 @@ module.exports = (Block, mcData) => { // 1.21.5+ writes no size prefix before chunk containers, it's computed dynamically to save 1 byte const noSizePrefix = mcData.version['>=']('1.21.5') const hasFluidCount = mcData.version['>=']('26.1') + // 26.3+ (protocol 777): the light-mask BIT_SET codec switched from a + // varint-count-of-longs encoding to a varint-count-of-raw-bytes encoding. + // Found live-testing against a 26.3 Realm: loadParsedLight threw + // "The first argument must be of type string or an instance of Buffer... + // Received undefined" because the sky/block light mask arrays minecraft-data + // decoded (now byte arrays, per the corresponding pc-26.3-support + // minecraft-data schema fix) were being fed through fromLongArray, which + // interpreted them as ~8x fewer/wrong bits than the real mask - confirmed + // by decompiling ByteBufCodecs.BIT_SET from the official 26.3 server jar. + const lightMasksAreByteArrays = mcData.version['>=']('26.3') const ChunkSection = require('../common/PaletteChunkSection')(Block) return class ChunkColumn extends CommonChunkColumn { static get section () { return ChunkSection } @@ -261,8 +271,8 @@ module.exports = (Block, mcData) => { loadParsedLight (skyLight, blockLight, skyLightMask, blockLightMask, emptySkyLightMask, emptyBlockLightMask) { function readSection (sections, data, lightMask, pLightMask, emptyMask, pEmptyMask) { let currentSectionIndex = 0 - const incomingLightMask = BitArray.fromLongArray(pLightMask, 1) - const incomingEmptyMask = BitArray.fromLongArray(pEmptyMask, 1) + const incomingLightMask = lightMasksAreByteArrays ? BitArray.fromByteArray(pLightMask, 1) : BitArray.fromLongArray(pLightMask, 1) + const incomingEmptyMask = lightMasksAreByteArrays ? BitArray.fromByteArray(pEmptyMask, 1) : BitArray.fromLongArray(pEmptyMask, 1) for (let y = 0; y < sections.length; y++) { const isEmpty = incomingEmptyMask.get(y) @@ -360,10 +370,10 @@ module.exports = (Block, mcData) => { return { skyLight, blockLight, - skyLightMask: this.skyLightMask.toLongArray(), - blockLightMask: this.blockLightMask.toLongArray(), - emptySkyLightMask: this.emptySkyLightMask.toLongArray(), - emptyBlockLightMask: this.emptyBlockLightMask.toLongArray() + skyLightMask: lightMasksAreByteArrays ? this.skyLightMask.toByteArray() : this.skyLightMask.toLongArray(), + blockLightMask: lightMasksAreByteArrays ? this.blockLightMask.toByteArray() : this.blockLightMask.toLongArray(), + emptySkyLightMask: lightMasksAreByteArrays ? this.emptySkyLightMask.toByteArray() : this.emptySkyLightMask.toLongArray(), + emptyBlockLightMask: lightMasksAreByteArrays ? this.emptyBlockLightMask.toByteArray() : this.emptyBlockLightMask.toLongArray() } } } diff --git a/src/pc/common/BitArrayNoSpan.js b/src/pc/common/BitArrayNoSpan.js index 8d40c9d3..9134ea80 100644 --- a/src/pc/common/BitArrayNoSpan.js +++ b/src/pc/common/BitArrayNoSpan.js @@ -62,6 +62,22 @@ class BitArray { return array } + // 26.3+ (protocol 777) light-mask wire format - see fromByteArray. Trims + // trailing all-zero bytes to match java.util.BitSet.toByteArray()/ + // FriendlyByteBuf.writeByteArray semantics (the real server does the same). + toByteArray () { + const bytes = [] + for (let i = 0; i < this.capacity; i++) { + const byteIndex = i >> 3 + const bitIndex = i & 7 + if (!bytes[byteIndex]) bytes[byteIndex] = 0 + if (this.get(i)) bytes[byteIndex] |= (1 << bitIndex) + } + let end = bytes.length + while (end > 0 && !bytes[end - 1]) end-- + return bytes.slice(0, end) + } + static fromLongArray (array, bitsPerValue) { const bitArray = new BitArray({ capacity: Math.floor(64 / bitsPerValue) * array.length, @@ -75,6 +91,29 @@ class BitArray { return bitArray } + // 26.3+ (protocol 777): Mojang's light-mask BIT_SET codec switched from + // FriendlyByteBuf.writeLongArray (varint count of longs, java.util.BitSet + // valueOf(long[])) to FriendlyByteBuf.writeByteArray (varint count of raw + // bytes, java.util.BitSet.valueOf(byte[])) - confirmed by decompiling + // ByteBufCodecs.BIT_SET from the official 26.3 server jar. Java's + // BitSet.valueOf(byte[]) is little-endian within each byte: bit i comes + // from bit (i % 8) of bytes[i / 8]. + static fromByteArray (bytes, bitsPerValue) { + const bitArray = new BitArray({ + capacity: Math.floor(8 / bitsPerValue) * bytes.length, + bitsPerValue + }) + for (let i = 0; i < bytes.length; i++) { + const byte = bytes[i] + for (let b = 0; b < 8; b++) { + if ((byte >> b) & 1) { + bitArray.set(i * 8 + b, 1) + } + } + } + return bitArray + } + static or (a, b) { const long = a.data.length > b.data.length ? a.data : b.data const short = a.data.length > b.data.length ? b.data : a.data