The .vwr Format

Galaxy Eggbert's binary world file: a 40-byte header, a chunk table, and palette-compressed chunks with optional sparse metadata.

Every world the game plays or edits is a .vwr file: the 78 campaign worlds and the other files in worlds3d/, every custom slot and every galaxy world. The source of truth is the code: src/GalaxyEggbert/Worlds/World.cpp (header and table), Chunk.cpp (chunk payload), BitPacking.cpp, and the record codecs. The repository's World Format.md describes the same layout with its history. This page describes the format as of 2026-11-10: header version 2, chunk version 2.

All numbers are little-endian. Offsets are in bytes.

File layout

WORLD HEADER        40 bytes          magic "VWR1"
CHUNK TABLE         chunkCount * 20   one entry per chunk, empty ones included
CHUNK DATA          variable          one "VCH1" payload per non-empty chunk

An all-air default world is therefore 40 + 1000 × 20 = 20 040 bytes.

World header

OffsetSizeFieldValue / meaning
04magicASCII VWR1
41version2 (VoxelConfig::FormatVersion). Anything else is rejected; version 1 files (before 2026-07-09) no longer load.
51chunkSize10; checked.
61chunksPerAxis10 in every world the game makes.
71flagsbit 0: independent saw cells (below). Any other bit set is a load error.
84chunkCountMust equal chunksPerAxis³.
124chunkTableOffsetWhere the table starts; the writer puts it at 40. The reader seeks there.
164chunkDataOffsetStart of the chunk data. Written, but only informational for the reader (each table entry has its own offset).
204skyRegion wordLow byte: sky/background id 0-31 (Content/backgrounds/decorNNN.png). Second byte: music track 0-10 (0 none, 1-10 music000-009.ogg, since 2026-11-02). Upper two bytes 0.
244missionNumberThe mission this world is; 0 = none.
284spawnX + 1The explicit Blupi start cell (the editor's yellow start), each coordinate stored plus one. All three 0 = no explicit start (older worlds), and the game uses its historical default. Either all three are non-zero and inside the world, or the file is rejected.
324spawnY + 1
364spawnZ + 1

Flag bit 0: independent saw cells

Since 2026-11-08 a saw occupies its own cell and implies nothing about the cell under it; files written since then set bit 0. Files with bit 0 clear come from the time when a saw was drawn with an invented floor. The reader keeps their data as is and reports World::hasLegacySawFloors(); WorldRuntime converts them in memory before play or editing (see World Loading), and saving the converted world sets the bit. Loading alone never rewrites a file.

Chunk table

chunkCount entries of 20 bytes. The writer emits them with X fastest, then Y, then Z; the reader uses each entry's own coordinates.

OffsetSizeFieldMeaning
08offsetAbsolute file offset of the chunk payload (0 when empty)
84sizePayload size in bytes (0 when empty)
122xChunk X
142yChunk Y
162zChunk Z
182flags0x0001 = EMPTY

A chunk is written as EMPTY only when it is all air and has no sparse metadata (Chunk::isEmpty()); an object anchored in an otherwise empty chunk keeps its chunk. A non-empty entry with size 0, or coordinates outside the grid, is a load error.

Block encoding

raw (uint16) = (type << 4) | metadata
bits 15..4   type      0..4095   (the game uses 0..440: object-m.png icons)
bits  3..0   metadata  0..15     (unused by the shipped worlds)
raw 0        Air

See Block and BlockDefinitionRegistry.

Chunk payload VCH1

Header (24 bytes, chunk version 2)

OffsetSizeFieldValue / check
04magicASCII VCH1
41version2 (version 1 is still readable)
51chunkSize10
61bitsPerBlock1-8; must equal bitsNeededForPalette(paletteCount)
71flagsbit 0: has a metadata section; other bits rejected
82paletteCount1-256
102reservedmust be 0
124blockCountmust be 1000
164dataSizeBytespacked stream size; a multiple of 8 equal to the expected word count × 8
204extraMetaSizeBytessize of the metadata section; non-zero exactly when flag bit 0 is set

A version 1 chunk has the first 20 bytes only, no flags and no metadata. The writer always produces version 2.

Body

palette[paletteCount]           uint16 raw block values
packed[dataSizeBytes / 8]       uint64 words
metadata[extraMetaSizeBytes]    optional BMD1 section

Palette and bit packing

Each of the 1000 cells stores an index into the chunk's palette, in cell order i = x + y * 10 + z * 100 (local coordinates). Index i occupies bits [i * bits, (i + 1) * bits) of the word stream, counting from the least significant bit of word 0; an index may straddle two words. The width depends on the palette size:

Palette entries1-23-45-89-1617-3233-6465-128129-256
Bits per cell12345678
Packed bytes1282563765046327528801000

The word count is ceil(1000 × bits / 64). A chunk with four distinct blocks is 24 (header) + 8 (palette) + 256 (packed) = 288 bytes, against 2000 bytes of raw blocks. The palette is written as it is in memory; entries no longer used after editing are kept.

Sparse metadata section BMD1

SizeFieldMeaning
4magicASCII BMD1
2version1
2reservedmust be 0
4recordCountnumber of records

Then recordCount records:

SizeFieldMeaning
4localBlockIndex0-999, the anchor cell inside the chunk
2metadataTypewhat the payload is (table below)
2payloadSizeN
Npayloadtype-specific bytes

A cell holds at most one record per type. The section must contain exactly the declared bytes; trailing data is an error. Unknown types are kept and written back unchanged, which is why new types could be added without a version change.

Sparse metadata types

TypeName in codePayloadMeaning
1kMoveObjectMetadataType
MoveObjectRecord.hpp
47 bytes: type (u8), start X/Y/Z, end X/Y/Z, speed, advance, recede, stop-start, stop-end ticks (11 × float32), visual icon (u16). The 45-byte form without the icon is still read.One object (pickup, enemy, lift, crate, vehicle, exit...), anchored at floor(start). See MoveObjectRecord.
2kPlateRotationMetadataType
Game/PlateRotationMetadata.hpp
1 byte, 0-3A tile's turn. Plates, fans and wind tiles read only the odd bit: X and Z axes swapped (old worlds store 0 or 1).
3kBigDecorMetadataType
BigDecorRecord.hpp
2 bytes: icon (u16, non-zero)A BigDecor picture from explo.png standing in that cell; never a voxel.
4kHintZoneMetadataType
HintZoneRecord.hpp
4 bytes: recordIndex, sizeX, sizeY, sizeZ (u8 each, sizes ≥ 1)The box, from this cell, where training hint recordIndex of the mission applies (2026-10-25).
5kObjectTurnMetadataType
Game/PlateRotationMetadata.hpp
1 byte, 0-3Quarter turns of the still object, parked vehicle or BigDecor in that cell, and of front-facing terrain models and single-face cubes set in the editor. Separate from type 2, so turning a treasure lying in a wind cell does not turn the wind (2026-11-04).
6kLegacySwitchSawRowMetadataType
Game/LegacySawFloors.hpp
1 byte: {1}Written only by the legacy saw upgrade on a switch whose saw row moved up one cell; that switch drives its X ± 20 row at Y + 1.
7kPlayerStartMetadataType
PlayerStart.hpp
1 byte: bit 1 orange, bit 2 blue, bit 3 greenThe editor's extra coloured player starts at this cell (yellow is the header spawn). Editor data only; single-player uses yellow.
8kPlayerStartHeadingMetadataType
PlayerStart.hpp
1 byte: 2 bits per colour (yellow bits 0-1, orange 2-3, blue 4-5, green 6-7)Each start's facing in quarter turns: 0 −Z, 1 −X, 2 +Z, 3 +X. An all-zero record is omitted.

Types 1 to 4 are present in the shipped worlds. Counted across the 81 files in worlds3d/ on 2026-11-10: 4150 objects, 398 BigDecor, 87 plate turns and 40 hint zones; no file uses types 5-8 or a non-zero block metadata nibble.

Tools

python3 tools/vwr_inspect.py worlds3d/world999.vwr
python3 tools/vwr_inspect.py --summary worlds3d/*.vwr
Three implementations. The Python reader and writer are written by hand from the C++ code. A format change has to be made in World.cpp/Chunk.cpp, in both Python tools and, for a new record type, in its codec; then run VerifyShippedWorlds and the world tests.

Not implemented

World Format.md also sketches ideas that do not exist in the code: a header with separate X/Y/Z chunk counts for non-cubic worlds, RLE or LZ4/Zstd compression of chunks, global palettes and region files. None of these is read or written by any version of the game.

Tests

Related pages