Skip to content

Decode schematic BlockData as varints - #41

Open
Qalipso wants to merge 1 commit into
stuffbydavid:masterfrom
Qalipso:fix/schematic-varint-blockdata
Open

Qalipso wants to merge 1 commit into
stuffbydavid:masterfrom
Qalipso:fix/schematic-varint-blockdata

Conversation

@Qalipso

@Qalipso Qalipso commented Aug 21, 2026

Copy link
Copy Markdown

Problem

BlockData in a Sponge schematic is an array of varints:

BlockData | varint[] — "Each entry is specified as a varint and refers to an index within the Palette."
Sponge Schematic Specification, version 1 (identical wording in version 2)

builder_read_schematic_blocks reads one raw byte per block instead, in both implementations:

  • GmProject/scripts/builder_read_file/builder_read_file.gml
    bindex = buffer_peek(buffer_current, sch_blockdata_array + b, buffer_u8)
  • CppProject/World/Builder.cpp
    paletteIndex = buffer->data[blockDataArray + bufferPos];

A palette index below 128 encodes as a single byte, so schematics with small palettes load correctly and the bug stays invisible. As soon as the palette passes 128 entries, the first multi-byte index desynchronises the stream and nearly every block after it is read from the wrong offset.

Measurements

Spec-conformant schematics decoded with the current reader and with a varint reader:

size blocks palette blocks wrong, current reader
16×4×16 1024 100 0 (0.0%)
16×4×16 1024 300 1018 (99.4%)
32×16×32 16384 512 16350 (99.8%)

Change

builder_read_schematic decodes the varints once into big endian integers appended to the file buffer, points sch_blockdata_array at them and sets sch_blockdata_ints, reusing the existing TAG_Int_Array code path. The block loop keeps its random access, so neither builder_read_schematic_blocks implementation had to change and no new instance variable was added.

The decode only runs when the palette has more than 128 entries, so every schematic that loads correctly today takes exactly the same path as before. A truncated BlockData array now logs a warning and leaves the remaining blocks empty instead of reading past the array.

Verification

  • The new GML was transliterated line for line and run against real gzipped NBT files. Palettes of 2, 127, 128, 129, 300, 4096 and 70000 entries decode with zero mismatches; two deliberate mutations of the decode loop were both caught.
  • CppGen was run over GmProject before and after and reports Success!; the semantic difference in the generated C++ is confined to builder_read_schematic.
  • I could not build and run the application itself, the Qt 5.15.9 static toolchain in BUILD.md is out of reach on my machine, so this has not been exercised through the UI. Loading a large WorldEdit schematic before merging would be worth it.

CppProject/Generated is left untouched and needs a CppGen run to pick this up.

🤖 Generated with Claude Code

The Sponge Schematic specification stores BlockData as an array of varints, but
builder_read_schematic_blocks reads one raw byte per block. Palette indices below
128 encode as a single byte so small schematics load correctly, while anything
with a larger palette desynchronises after the first multi-byte index and almost
every block comes out wrong.

builder_read_schematic now decodes the varints once into big endian integers
appended to the file buffer and points sch_blockdata_array at them, reusing the
existing TAG_Int_Array code path. Schematics with 128 or fewer palette entries
keep the original single byte path and are unaffected.

A truncated BlockData array logs a warning and leaves the remaining blocks empty
instead of aborting the load.
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.

1 participant