World
The voxel world: a cubic grid of palette-compressed chunks, sparse per-block metadata, a block change journal, and the .vwr file reader and writer.
Location
| Header | include/GalaxyEggbert/Worlds/World.hpp |
|---|---|
| Source | src/GalaxyEggbert/Worlds/World.cpp |
| Namespace | GalaxyEggbert::Worlds |
| Depends on | Chunk, Block, VoxelConfig, BinaryIO.hpp (little-endian helpers) |
Purpose
World is the single data model of a Galaxy Eggbert level. It has no CNA or
cna-engine dependency: the game, the in-game editor, the renderers, the command-line
verifiers and the unit tests all use the same class. Every campaign world in
worlds3d/, every player's custom world and every galaxy world is one
World saved as a .vwr file.
- Grid.
chunksPerAxis³ chunks of 10 × 10 × 10 blocks. The default (and the only size the game uses) is 10 chunks per axis: 100 × 100 × 100 blocks, 1000 chunks. Y is up. - Blocks. Each cell holds a 16-bit Block (12-bit type = the
object-m.pngicon number, 4-bit metadata). Air is 0. - Sparse metadata. Any cell can carry extra typed byte payloads (objects, BigDecor, tile turns, hint zones, player starts). They are stored per chunk and survive save/load; see The .vwr Format for the type list.
- World-level header fields. Sky region (background picture), music track, mission number, an optional Blupi start cell, and the "legacy saw floors" flag.
- Change tracking. A revision counter and a bounded journal of the last 256 block changes, so the terrain renderer can patch a few cells instead of rebuilding the whole mesh.
Coordinates are always raw grid cells, 0 to blocksPerAxis() - 1. The
game's render space is shifted by 50 on X and Z (WorldRuntime::kWorldCenterX/Z);
World never applies that shift.
Public API
All members verified in World.hpp as of 2026-11-10.
| Member | Description |
|---|---|
explicit World(std::uint8_t chunksPerAxis = VoxelConfig::WorldChunksPerAxis) | An all-air cubic world. Throws std::out_of_range for 0. |
chunksPerAxis(), blocksPerAxis(), chunkCount() | Grid dimensions (10, 100 and 1000 by default). |
getBlock(x, y, z) / setBlock(x, y, z, Block) | Read or write one cell. Both throw std::out_of_range outside the world. setBlock bumps the revision and journals the change only when the raw value actually changes. |
chunk(cx, cy, cz) (const and mutable) | Direct access to one Chunk by chunk-grid coordinates. |
skyRegion() / setSkyRegion(r) | Background id 0-31: selects Content/backgrounds/decorNNN.png. |
musicTrack() / setMusicTrack(t), kMusicTrackCount = 10 | 0 = no music, 1-10 = the original tracks Content/music/music000-009.ogg. Out-of-range values are stored as 0. |
missionNumber() / setMissionNumber(n) | The mission this file is (1 the main hub; 10, 20, ... 120 the world hubs; 11, 12, ... their missions; 199; 998/999 the showcase worlds; 0 = none). |
hasSpawnPoint(), spawnX/Y/Z(), setSpawnPoint(x, y, z), clearSpawnPoint() | The optional explicit start cell (the yellow player start). setSpawnPoint throws outside the world. |
hasLegacySawFloors() / setLegacySawFloors(bool) | True for files whose header flag bit 0 is clear: saws there were drawn with an implied floor. WorldRuntime upgrades such worlds in memory (see World Loading). |
blockRevision() | Counts setBlock() calls that changed a block, plus metadata additions/removals. |
struct BlockChange { x, y, z, oldRaw, newRaw } | One journaled change. |
struct BlockChanges { bool known; std::vector<BlockChange> cells; } | Result of blockChangesSince; known == false means "history lost, rebuild everything". |
BlockChangeCapacity = 256 | Journal length; the oldest entries drop off. |
blockChangesSince(revision) | Non-destructive: every consumer keeps its own last-seen revision. Returns known = false when more changes happened than the journal holds, or after any extra-metadata edit (which clears the journal). The journal is never saved. |
struct BlockExtraMetadataRecord { x, y, z, metadataType, payload } | A sparse record resolved to world coordinates. |
setBlockExtraMetadata(x, y, z, type, payload) | Adds or replaces the record of that type at that cell (one per cell and type). Payload must fit in 65 535 bytes. |
removeBlockExtraMetadata(x, y, z, type) | Returns true if a record was removed. |
findBlockExtraMetadata(x, y, z, type) | Pointer to the payload bytes, or nullptr. Used for per-cell lookups such as a turned fan. |
collectExtraMetadata(type) | Every record of one type in the whole world, chunk by chunk. |
saveToFile(path) | Writes the .vwr file; throws std::runtime_error on failure. Empty chunks (all air and no metadata) are written as table entries only. |
static loadFromFile(path) | Reads and validates a .vwr file; throws std::runtime_error on any mismatch. |
static loadFromStream(std::istream&) | The same validation from a seekable stream starting at byte 0. The game uses it to decode the exact byte snapshot it hashed for the resume log. |
How it is used
Loading and saving
The minimal round trip (from World Format.md):
#include "GalaxyEggbert/Worlds/World.hpp"
using namespace GalaxyEggbert::Worlds;
World world;
const Block grass = Block::make(1, 0);
world.setBlock(10, 5, 10, grass);
world.saveToFile("level.vwr");
World loaded = World::loadFromFile("level.vwr");
return loaded.getBlock(10, 5, 10) == grass ? 0 : 1;
In the game, WorldRuntime::LoadFromVwrStream()
(src/GalaxyEggbert/Game/WorldRuntime.cpp) wraps the loaded world in a
std::unique_ptr, runs the legacy saw-floor upgrade and then derives objects,
BigDecor and hint zones from the sparse metadata. See WorldRuntime.
Patching the terrain from the change journal
When a door opens, a bridge crumbles or a switch flips the saws, the game changes blocks through
setBlock(). Each frame GalaxyEggbertGame::RefreshTerrainMeshIfChanged()
(src/GalaxyEggbertCNA/GalaxyEggbertGameLoad.cpp) compares revisions and asks for the
changes since its last build:
if (terrainRenderer_ && (worldRuntime_.GetWorld().blockRevision() != terrainRevision_ || ...))
{
const auto& world = worldRuntime_.GetWorld();
const auto changes = world.blockChangesSince(terrainRevision_);
if (... && terrainRenderer_->PatchModelCells(world, changes, StillModelIcons(), ...))
{
terrainRevision_ = world.blockRevision();
return; // only the changed model cells were redrawn
}
... // otherwise: full TerrainRenderer rebuild
}
Who uses it
- WorldRuntime owns the live world during play and in the editor.
- BlupiController reads blocks for collision (see Collision).
- TerrainRenderer builds the meshes from it; InteractionSystem opens doors and changes tiles through it.
- WorldEditor edits it in place; CustomWorldStorage saves it.
- The record codecs (MoveObjectRecord, BigDecor, hint zones, player starts) and HubDoors are free functions over a
World. tools/vwr_inspect.pyandtools/worldkit.pyre-implement the reader and writer in Python; keep them in step withWorld.cpp.
Behaviour worth knowing
- The header
versionbyte must be exactly 2 (VoxelConfig::FormatVersion). Version 1 files are rejected, not upgraded. - Unknown header flag bits, a wrong chunk size, a chunk count that is not
chunksPerAxis³, or a half-set spawn (one or two of the three spawn fields zero) are all load errors. - Loading never rewrites a file. A legacy file (flag bit 0 clear) round-trips byte-identically until something saves the upgraded world.
- Changing the world-level fields (mission, sky, music, spawn) does not bump
blockRevision(); metadata edits do, and they clear the journal.
Tests
tests/GalaxyEggbert/Worlds/WorldTests.cpp(GoogleTest, targetGalaxyEggbertWorldsTests): dimensions, get/set across chunk boundaries, bounds checks, sparse metadata across chunks, save/load of blocks, metadata, sky region, music track, mission number and spawn point, legacy all-zero spawn slots, rejection of v1 files, bad magic, unknown flags and a wrong chunk count,BlockRevisionCountsBlockChanges, andIndependentSawFlagIsPreservedWithoutRewritingLegacyData.tools/VerifyShippedWorlds.cpp(ctestVerifyShippedWorlds): every world inworlds3d/loads, has the mission number its file name says, givesWorldRuntimeits objects and survives load → save → load with identical metadata.tools/VerifyTileFaces.cpp: drivesblockChangesSince()through the terrain patch path and checks incremental and full rebuilds agree.- The editor verifiers (
VerifyGEWorldEditor,VerifyGEEditorStorage,VerifyGEEditorObjects) save and reload worlds through it.