Chunk

A 10 × 10 × 10 block cube stored as a local palette plus bit-packed palette indices, with optional sparse per-block metadata.

Location

Headerinclude/GalaxyEggbert/Worlds/Chunk.hpp (also declares ChunkBlockMetadataRecord)
Sourcesrc/GalaxyEggbert/Worlds/Chunk.cpp
Bit packinginclude/GalaxyEggbert/Worlds/BitPacking.hpp, src/GalaxyEggbert/Worlds/BitPacking.cpp
NamespaceGalaxyEggbert::Worlds

Purpose

A World is 1000 chunks. A chunk does not store 1000 raw 16-bit blocks; it stores the distinct block values it contains (its palette, up to 256 entries) and, for each cell, the index of its value in that palette, packed with as few bits as the palette size needs. Handcrafted worlds have few distinct blocks per 10³ region, so most chunks pack into 1-3 bits per cell. A chunk holds no mesh; meshes are runtime caches built by the TerrainRenderer.

The chunk also owns the sparse extra metadata of its cells: a list of ChunkBlockMetadataRecords, each a local cell index, a 16-bit type and a byte payload. This is where objects, BigDecor, tile turns, hint zones and player starts live in a saved world.

Palette and adaptive bit packing

Local cell index, shared by packing and metadata:

linearIndex(x, y, z) = x + y * 10 + z * 100      // 0..999

Public API

Verified in Chunk.hpp as of 2026-11-10.

MemberDescription
Size = 10, Volume = 1000From VoxelConfig.
Chunk()All-air, uniform, clean.
static createUniform(Block)Every cell the same value; one-entry palette.
static fromBlocks(const std::array<Block, Volume>&)Builds a minimal palette from dense data.
getBlock(lx, ly, lz) / setBlock(lx, ly, lz, Block)Local coordinates 0-9; std::out_of_range outside. setBlock marks the chunk dirty.
unpackBlocks()All 1000 values in linearIndex order.
isEmpty()All air and no extra metadata. World::saveToFile() skips empty chunks, so an object anchored in an otherwise empty chunk is still saved (a bug fixed on 2026-07-09).
isUniform()Palette has one entry.
isDirty() / clearDirty()Runtime-only flag set by every mutation; not saved.
palette(), packedIndices(), bitsPerBlock()Read-only views of the storage.
extraMetadata()The sparse records.
setExtraMetadata(localIndex, type, payload)Add or replace (one record per cell and type). Payload over 65 535 bytes throws.
removeExtraMetadata(localIndex, type)Returns true if removed.
clearExtraMetadata()Drops all records.
static linearIndex(lx, ly, lz)Public so World can address a cell's metadata.
write(std::ostream&) / static read(std::istream&)The VCH1 chunk payload (always written as chunk version 2; version 1 is still readable).

ChunkBlockMetadataRecord has localBlockIndex (uint32_t), metadataType (uint16_t) and payload (std::vector<std::uint8_t>), and compares with ==.

Serialized form

A chunk payload is a 24-byte header (VCH1, version 2, chunk size, bits per block, flags, palette count, block count, packed data size, metadata size), the palette as raw uint16_t values, the packed words, and, when flag bit 0 is set, a BMD1 metadata section. read() validates every field (bits must match the palette size, word count must match, no trailing bytes in the metadata section). Byte tables are on The .vwr Format.

Usage

Game code normally goes through World, which splits world coordinates into chunk and local ones:

// src/GalaxyEggbert/Worlds/World.cpp, World::setBlock()
auto& target = chunk(chunkX, chunkY, chunkZ);
const auto oldRaw = target.getBlock(localX, localY, localZ).rawValue();
target.setBlock(localX, localY, localZ, blockValue);

Direct use appears in the tests and in round-trips through a stream:

Chunk chunk;
chunk.setBlock(1, 2, 3, Block::make(10));
std::ostringstream out(std::ios::binary);
chunk.write(out);
std::istringstream in(out.str(), std::ios::binary);
Chunk copy = Chunk::read(in);

Tests

Related pages