Architecture

The layers of the game, the rules that keep them apart, and how the parts talk to each other (as of 2026-11-10).

Galaxy Eggbert has exactly one game target, the CMake target GalaxyEggbertCNA (the executable is called GalaxyEggbert). It is written directly against CNA, a C++ implementation of the XNA 4.0 API, and uses two strict modules of cna-engine as helpers. Above them the code is layered so that most of the game's rules can be compiled and tested without a window, a GPU or CNA at all.

The layers

+--------------------------------------------------------------+
| CNA: the XNA 4.0 API in C++ (Game loop, GraphicsDevice,      |  ../cna
|      Input, Audio, Media, Storage; SDL3 underneath)          |  (sibling repo)
| cna-engine strict modules: CnaEngine::Core, ::Graphics       |  ../cna-engine
|      (Camera, MeshBuilder, GpuMesh, UvRect)                  |  (sibling repo)
+--------------------------------------------------------------+
                              |
+--------------------------------------------------------------+
| GalaxyEggbertCNA host               src/GalaxyEggbertCNA/    |
|   GalaxyEggbertGame : Microsoft::Xna::Framework::Game        |
|   phases, input capture, the tick order, SceneFrame building,|
|   the frame, resume log and demos, platform glue             |
+--------------------------------------------------------------+
               |                                |
+-----------------------------+  +-----------------------------+
| Game                        |  | Editor                      |
| src/GalaxyEggbert/Game/     |  | src/GalaxyEggbert/Editor/   |
| BlupiController,            |  | WorldEditor, palette,       |
| InteractionSystem,          |  | undo/redo, clipboard,       |
| WorldRuntime, Hud, InputPad,|  | CustomWorldStorage, the     |
| Sound, WorldRenderer, ...   |  | world and galaxy screens    |
+-----------------------------+  +-----------------------------+
               |                                |
+--------------------------------------------------------------+
| Engine-independent model   include/GalaxyEggbert/,           |
|   src/GalaxyEggbert/Worlds/, src/GalaxyEggbert/*.cpp         |
|   World, Chunk, Block, records, HubDoors,                    |
|   BlockDefinitionRegistry, Def/ (the original's enums)       |
+--------------------------------------------------------------+
LayerWhereNamespaceMay use
Engine and helperssibling repositories cna, cna-engine Microsoft::Xna::Framework, CnaEngine::GraphicsSDL3, SDL3_mixer, GL/WebGL
Hostsrc/GalaxyEggbertCNA/GalaxyEggbert::CNA everything below it; the only place with platform code (Android assets, Emscripten paths, executable hashing)
Gamesrc/GalaxyEggbert/Game/GalaxyEggbert::Game the model; CNA and cna-engine where it draws, plays sound or reads devices
Editorsrc/GalaxyEggbert/Editor/GalaxyEggbert::Editor the model and the game's registries and drawing helpers
Modelinclude/GalaxyEggbert/, src/GalaxyEggbert/Worlds/, src/GalaxyEggbert/*.cpp GalaxyEggbert, GalaxyEggbert::Worlds, GalaxyEggbert::Def the C++ standard library only

The game layer is mixed on purpose: about 75 of its files include nothing from CNA or cna-engine (BlupiController, WorldRuntime, SaveData, ResumeLog, DemoReplay, Autoplay, ObjectDefinitionRegistry, ...), while the renderers, Hud, InputPad and Sound do. The file names of the host are listed on Project Structure; the engine side is described on Engine Integration.

The rules that shape the code

These come from the project's own rules (CURRENT.md "Non-negotiable boundaries", docs/handbook/01-overview.md). Most defects in the project's history came from breaking one of them.

  1. Faithful remake. Every gameplay feature must trace to confirmed behaviour of Mobile Eggbert / Speedy Blupi. Where a 2D rule has no single 3D reading, the chosen reading is recorded as an intentional 3D adaptation (plans/audit/mechanics_3d_decisions.md, rows M3D-NNNN). Numeric IDs (object types, block icons, sound channels) are never renumbered: they are stored in world files.
  2. The editor exception. The in-game 3D world editor is the one approved exception: its user interface may be invented, but it must not add gameplay mechanics.
  3. An engine-independent model. include/GalaxyEggbert/ and src/GalaxyEggbert/Worlds/ include nothing from CNA or cna-engine. CMake compiles them as GE_SHARED_SOURCES into the game, the tools and the GoogleTest suite alike.
  4. InteractionSystem has no controller, camera or graphics dependency. It includes WorldRuntime.hpp, Sound.hpp, ObjectiveHint.hpp and Def/ headers only, takes Blupi's position and state as plain values, and reports what happened as events and requests (below). Sound.hpp brings in CNA's audio types, so the test of it links CNA, but it never opens a window or an audio device.
  5. Two registries are the single sources of truth. BlockDefinitionRegistry holds one entry per block icon (0-440) and ObjectDefinitionRegistry one per Def::ObjectType (0-203). Rendering, collision, editor validation, animation and interactions read them and keep no parallel lists. A new behaviour starts with the registry entry (tutorial).
  6. Determinism. The simulation steps a fixed 1/60 s, uses a recorded random seed and never reads the wall clock. Golden traces, autoplay playthroughs, mission resume and the main-menu demos all depend on it (Game Loop).
  7. No backend branches in game logic. A defect found in CNA or cna-engine is fixed there and recorded in upstream_bugs.md, not worked around here.

Main components

ComponentSourceRole
GalaxyEggbertGamesrc/GalaxyEggbertCNA/GalaxyEggbertGame*.cpp The XNA Game: owns every system below, runs the phases, the tick order and the frame.
World, Chunksrc/GalaxyEggbert/Worlds/ The 100×100×100 voxel grid in 10×10×10 chunks, with a block revision counter and a change journal.
WorldRuntimesrc/GalaxyEggbert/Game/WorldRuntime.cpp A loaded .vwr: the world, its moving objects (MoveObjects), the 20 Hz animation clock.
BlupiControllersrc/GalaxyEggbert/Game/BlupiController.cpp Blupi's movement and collision model and animation state; engine-independent.
InteractionSystemsrc/GalaxyEggbert/Game/InteractionSystem.cpp Lifts, enemies, pickups, hazards, projectiles, dynamite, crates, lives and counters.
HubDoorssrc/GalaxyEggbert/HubDoors.cpp The hubs' door bars, gold bars and signs, from the saved progress.
TerrainRenderer, TileAtlassrc/GalaxyEggbert/Game/ The terrain meshes built from the world and patched from its change journal.
WorldRenderersrc/GalaxyEggbert/Game/WorldRenderer.cpp Draws the 3D world from a SceneFrame (below).
Hud, InputPadsrc/GalaxyEggbert/Game/ The HUD; the menus, Setup, Pause, Win/Lost screens, the on-screen pad and typed cheats.
Soundsrc/GalaxyEggbert/Game/Sound.cpp The 93 sound channels with the original's volume and pitch table, heard from Blupi's place.
SaveData, ResumeLog, DemoReplaysrc/GalaxyEggbert/Game/ Settings and progress, the interrupted-mission log, the demo files (Save Data & Resume).
Autoplaysrc/GalaxyEggbert/Game/Autoplay.cpp The QA harness: a script decides the keys to hold each tick.
WorldEditor, CustomWorldStoragesrc/GalaxyEggbert/Editor/ The in-game editor and the player's world files, galaxies and their sharing.

How the components talk

The host owns the systems as plain members and calls them in a fixed order every tick (the full order is on Game Loop). The systems never call each other's owners back; they hand results upward:

// src/GalaxyEggbertCNA/GalaxyEggbertGameUpdate.cpp, UpdateTickEvents()
if (FindInteractionEvent(InteractionSystem::EventKind::SmallShakeTriggered))
{
    cameraShake_.Trigger(CameraShakeType::Small);
}
...
if (FindInteractionEvent(InteractionSystem::EventKind::TankFired))
{
    blupi_.TriggerOneShotAnim(BlupiController::AnimState::FireTank,
                               BlupiController::kFireTankDuration);
}

The editor follows the same pattern: WorldEditor::UpdateBrowsing() returns a request (open a world, return to the menu, a galaxy was imported) and the host acts on it.

The render seam: SceneFrame → WorldRenderer

Since 2026-11-09 the 3D world is drawn in two steps, the seam described in renderers.md:

simulation state (WorldRuntime, BlupiController, InteractionSystem, camera)
        |
        |  GalaxyEggbertGame::BuildSceneFrame()      src/GalaxyEggbertCNA/GalaxyEggbertGameScene.cpp
        v
SceneFrame  -- a draw list: camera, cubes, models and poses, decals,
        |      sparks, billboard batches, effects (shadows, lights, particles)
        |      src/GalaxyEggbert/Game/SceneFrame.hpp
        |
        |  WorldRenderer::Draw(device, frame, assets)
        v
the 3D world on screen                         src/GalaxyEggbert/Game/WorldRenderer.cpp
        then HUD, menus, editor overlays       src/GalaxyEggbertCNA/GalaxyEggbertGameDraw.cpp

Details of the passes are on Rendering.

What the layering buys

See Testing for the whole set.

Older notes in the code. Long comments in GalaxyEggbertGame.hpp and InteractionSystem.hpp are historical and sometimes describe states that no longer exist (an invisible Blupi, a single-world save). Trust the code, CURRENT.md and the ledgers in plans/audit/ over them.

Related pages