Engine Integration (CNA, cna-engine)
The game is written against CNA's XNA 4.0 API, with two strict cna-engine modules as helpers (as of 2026-11-10).
Galaxy Eggbert has no engine of its own. Its host class GalaxyEggbertGame derives from
Microsoft::Xna::Framework::Game and the game code calls CNA directly: devices, effects, textures,
input, audio and storage are XNA types. cna-engine adds a camera and mesh building on top;
it does not hide CNA. Both are sibling repositories built from source with the game (CMake
add_subdirectory), looked for at ../cna and ../cna-engine or at
-DCNA_HOME / -DCNA_ENGINE_HOME.
CNA
CNA is a C++ implementation of the XNA 4.0 API. It brings its own SDL3 and SDL3_mixer (vendored, built on
the first configure) and needs three further siblings next to it: easy-gl, meta-gl
and sharp-runtime (the .NET-style System types). The game links the umbrella targets
CNA and SHARP_RUNTIME; CNA is built as a static library because cna-engine links its
module libraries too, and a shared CNA would put a second copy into the process.
What the game uses, by namespace
| Namespace | Types used | Where |
|---|---|---|
Microsoft::Xna::Framework | Game, GameTime, GraphicsDeviceManager,
Vector2/3/4, Matrix, Color, Rectangle, BoundingFrustum,
BoundingSphere | the host, the renderers, the camera code |
...::Graphics | GraphicsDevice, BasicEffect, AlphaTestEffect,
SkinnedEffect, ShaderEffect, Texture2D, RenderTarget2D,
VertexBuffer, IndexBuffer, the state objects, SpriteBatch, SkinnedModelEXT |
TerrainRenderer, WorldRenderer, Hud, InputPad, the effects classes, the editor |
...::Input | Keyboard (and KeyDownEXT), Mouse, GamePad,
Touch::TouchPanel | GalaxyEggbertGame.cpp, GameHelpers.hpp (Input) |
...::Audio | SoundEffect, SoundEffectInstance | Sound (Audio) |
...::Media | Song, MediaPlayer | the music (UpdateMusic()) |
...::Storage | StorageDevice | the player's folder (main.cpp) |
Microsoft::Devices::Sensors | Accelerometer | tilt steering (AccelTurnInput()) |
System | TimeSpan, Object, EventArgs | frame timing, OnDeactivated/OnExiting |
::CNA | ShaderDialectEXT, GraphicsCapability, Platform/Entrypoint.hpp |
the shader preamble, the editor's wireframe fallback, Android's SDL_main |
Note the two namespaces called CNA: ::CNA is the engine's own, GalaxyEggbert::CNA is
the game's host.
CNA extensions the game depends on
CNA marks what XNA 4.0 does not have with an EXT suffix. The game needs these:
SkinnedModelEXT: a skinned model read from a directory with a.skinnedmodel.jsonmanifest, drawn with the stockSkinnedEffect. Blupi and every converted object inavatars3d/use it (ComputeBoneTransformsEXT,PartEXT).Keyboard::KeyDownEXT: an event of physical key presses in order, including presses released between two updates. Typed cheat words, the editor's command keys and typed names use it; it needs no text-input mode or on-screen keyboard.ShaderEffectwith GLSL source,GraphicsDevice::GetShaderDialectEXT()andGetCompileErrorEXT(): the 3D-effects shaders. Where the device has no GLSL dialect the shaders are left out and the game draws without them.StorageDevice::SetAppNameEXT()/GetStorageRootEXT(): the system's per-user folder for "Galaxy Eggbert" (Save Data & Resume).
// src/GalaxyEggbertCNA/GalaxyEggbertGameLoad.cpp
content.setRootDirectoryProperty("avatars3d");
blupiModel_ = content.Load<std::shared_ptr<Microsoft::Xna::Framework::Graphics::SkinnedModelEXT>>(
blupiModelIsReal_ ? "blupi/avatar" : "blupi_placeholder/avatar");
// src/GalaxyEggbert/Game/SurfaceShaders.cpp
switch (device.GetShaderDialectEXT())
{
case CNA::ShaderDialectEXT::GlslDesktop: return "#version 330 core\n";
case CNA::ShaderDialectEXT::GlslEs: return "#version 300 es\nprecision highp float;\n";
default: return {};
}
The game asks for the HiDef graphics profile in its constructor: the screenshot and golden-frame
captures use GetBackBufferData, which XNA allows only there.
Renderer identities
CNA picks its graphics backend at configure time from CNA_GRAPHICS_RENDERER. The game never names
a renderer in its code; CMake passes the identity to CNA and links whatever CNA selected.
| Identity | Used for | Status |
|---|---|---|
OPENGLES3 | the native default when nothing is set; Android | Linux: builds and tests (CI uses the default). Android: built and run in the emulator. |
OPENGL33 | desktop OpenGL 3.3 core; the configuration the maintainer plays and verifies on Linux | Verified |
WEBGL2 | the Emscripten build (required there) | Verified manually in headless Chrome; not in CI |
SDLGPU | the Windows (MinGW) cross-build (spelled SDL_GPU before CNA renamed it) | Not verified no fresh build and live 3D run yet |
The three GL identities share one implementation inside CNA (EasyGL). CNA retired its VULKAN
identity on 2026-10-13; Vulkan is reached through SDLGPU now. CNA's
cmake/RendererSelection.cmake is the authority for the list. See Platform Support
and Configuration.
The web build
- The game calls the blocking
Game::Run(), so it links CNA'sCNA::EmscriptenAsyncify. - The assets (
Content/icons,backgrounds,sounds,demos,worlds3d/,textures3d/,avatars3d/) are packed with--preload-file; the music is served beside the page and fetched when a world first plays it. - The player's files live in
/save, an IndexedDB-backed file system (-lidbfs.js) that CNA's storage start-up hook mounts and persists.
cna-engine
cna-engine is a library of helper modules over CNA. The game builds only its strict modules,
CnaEngine::Core and CnaEngine::Graphics, which use plain XNA 4.0 types; the modules that need
CNA's extensions are switched off (CNA_ENGINE_WITH_EXTENDED=OFF,
CNA_ENGINE_WITH_ENHANCED_GRAPHICS=OFF), as are its tests and examples. The game links
CnaEngine::Graphics and includes only its Graphics headers:
| Type | Header | Used for |
|---|---|---|
Camera | CnaEngine/Graphics/Camera.hpp | the game's camera (camera_: SetLookAt, Position, View, Projection), which the editor also flies and draws its markers with |
MeshBuilder, MeshData, BuilderVertex, VertexAttribute | MeshBuilder.hpp, MeshData.hpp | building every CPU-side mesh: terrain, cubes, billboards, HUD and editor quads |
BoxFaces, BoxFace, BoxFaceStyle, PlateAxis | MeshBuilder.hpp | boxes with a texture region per face, plates |
GpuMesh, VertexLayout | GpuMesh.hpp, VertexLayouts.hpp | uploading a mesh and drawing it with the caller's effect |
UvRect | TextureAtlas.hpp | texture regions: tiles in object-m.png, sprite icons, scene quads |
DebugDraw | DebugDraw.hpp | lines: the editor's highlight edges (where the device has no wireframe mode) and the position axes |
Every Galaxy mesh is positions plus one texture coordinate (with normals where it is lit). The helpers that
wrap cna-engine for this are in src/GalaxyEggbert/Game/TexturedMesh.hpp:
// src/GalaxyEggbert/Game/TexturedMesh.hpp
[[nodiscard]] inline CnaEngine::Graphics::MeshBuilder MakeTexturedMeshBuilder()
{
return CnaEngine::Graphics::MeshBuilder(
{CnaEngine::Graphics::VertexAttribute::Position, CnaEngine::Graphics::VertexAttribute::TexCoord0});
}
...
[[nodiscard]] inline std::unique_ptr<CnaEngine::Graphics::GpuMesh> UploadTexturedMesh(
Microsoft::Xna::Framework::Graphics::GraphicsDevice& device, const CnaEngine::Graphics::MeshData& mesh)
{
return std::make_unique<CnaEngine::Graphics::GpuMesh>(
CnaEngine::Graphics::GpuMesh::Upload(device, mesh, CnaEngine::Graphics::VertexLayout::PositionTexture()));
}
The SceneFrame draw list uses cna-engine's CPU-side UvRect and box faces too, so it
holds no GPU object (Architecture).
Which revisions
From CURRENT.md's "Verification baseline": the current source and CI use CNA
594eb28dc (with the optional KeyDownEXT), which includes ee3235984
(texture mip levels restored after a lost context, UB-009). The handbook states that the source needs
594eb28dc or newer.
.github/workflows/cna-ci.yml pins all siblings to exact commits: cna,
cna-engine, easy-gl, sharp-runtime, meta-gl, and the
worlds/ directory of mobile-eggbert (read by two verifiers).
python3 tools/ci_pin_siblings.py moves the pins to the local siblings' HEADs, and
--check reports drift.
Upstream bugs
The game does not patch its siblings. A defect found in CNA, cna-engine or another sibling is written down in
upstream_bugs.md as UB-NNN: what was observed, how to reproduce it, how Galaxy copes until it
is fixed, and the fixing commit. A workaround is removed only after the fix is re-verified. As of 2026-11-10 every
entry is marked fixed upstream:
| ID | Where | Problem |
|---|---|---|
| UB-001 - UB-004 | CNA avatar converter and loader | mesh primitives overwriting each other, material colours lost, inside-out winding, unescaped part names |
| UB-005 | cna-engine | AddPlate/AddCrossedPlates drew their region upside down |
| UB-006 | CNA on Android | every missed content name listed the APK's assets through JNI |
| UB-007 | CNA EasyGL | the skinned shader needed more vertex uniforms than OpenGL ES 3.0 guarantees |
| UB-008 | CNA audio | a missing #include <algorithm> broke the web build |
| UB-009 | CNA EasyGL | only a texture's base level came back after a context loss (F9) |
| UB-010 | CNA working tree | sampler collections bound before a device member existed |
The file also indexes problems recorded elsewhere (an easy-gl test failure, the retired Vulkan backend).
History
The project chose direct CNA on 2026-07-05, replacing an earlier plan to move through other engines. The
earlier target GalaxyEggbertSimple3D was retired once CNA reached parity and removed from the tree on
2026-07-25 (recoverable at commit 4afd53e). Until 2026-10-20 the CNA target used a helper library called
easy-3d (Easy3D:: camera and cube meshes); it was then replaced by cna-engine's Camera,
MeshBuilder and GpuMesh, with golden frames and traces byte-identical before and after.
Comments that still mention Easy3D describe that past.