What a human maintainer inherits
Evidence basis: source-verified at the pinned commit. Claims on this page were checked by reading the CNA source at commit 3b43bd86; unless a sentence says otherwise, nothing here was built or executed. Counts are recomputed from the snapshot's CMake and tree (23 module directories, 8 renderer families, 10 identities, 3 platforms, 3 audio implementations); maturity per family is not claimed.
CNA is not one engine library with replaceable window wrappers. At snapshot 3b43bd86 the tree has 23 directories under modules/ (22 declared framework modules plus the renderers/ tree), a separately selected platform, audio transport and renderer identity, 8 physical renderer-family directories serving 10 public renderer identities, an experimental C ABI (0.50.0, 60 public headers), an external C# binding (CNA.NET) maintained in its own repository beside eight archived language bindings kept in CNA Cemetery, and a large conditional build and test graph (900 C++ test source files, 11,927 statically declared GoogleTest-family cases). Those counts describe the scope a reviewer has to cover, not code quality or feature completeness.
The main engineering families
The physical layout is enforced, not conventional. modules/CMakeLists.txt declares the framework modules (_cna_framework_modules) and renderer modules (_cna_renderer_modules) and fails the configure when a translation unit lies outside every declared module's src/, tests/, examples/ or benchmarks/ tree, so a file's directory is its CMake owner. The shared renderer helpers (common/mojoshader and the DirectX 9 effect sub-target inside directx9) are deliberate sharing inside their own directories, not exceptions. Each module is created by cna_add_module as a static library cna_<name> with an alias CNA::<Alias> and exposes only its own include/ root.
| Family | Directories at this snapshot | What it owns | Typical change boundary |
|---|---|---|---|
| Foundation | core, math, platform; also diagnostics | Shared types and value conventions, the renderer identity enum (CNA::GraphicsRendererType lives in core), host services, and native event / window / surface access behind CNA::Platform::IPlatform. | Header or contract edits propagate to many modules and to all three platform implementations. |
| Runtime and compatibility | runtime, graphics, input, audio, media, content, storage, devices, phone, plus the optional gamer-services, net and video-ffmpeg | XNA-facing Game, device, input, audio and content behaviour. Some static XNA APIs (for example Keyboard::GetState, StorageDevice) use the ambient platform because they cannot be handed a context. | Object construction, event semantics and resource disposal cross layers. |
| CNA extensions and tooling modules | graphics-ext, devices-ext, inspector, design | CNAEXT seams beyond XNA (the opt-in graphics extensions: retro post-process effects and DebugDraw; the device extensions), the inspector transport and the Framework.Design converters. | Extension APIs are public too; they reach the C ABI and bindings like any other surface. |
| Physical renderers | modules/renderers: 8 family directories with a src/, plus the shared helper common/mojoshader | Backend-specific resource translation, draws, presentation and synchronization behind IGraphicsRenderer. | One family may be local; a change to the renderer interface, the descriptor or shared state is not. |
| Build-time pipeline and tooling | content-pipeline, cmake/, tools/, scripts/ | Asset import, processing and writing (cna-content), generated registries, probes, source audits and gates. | A format change must be followed through the runtime reader and the test assets. The content pipeline is deliberately outside every game's link closure. |
| Interop | c-api in tree; the maintained C# binding (CNA.NET) in its own repository (the Common Lisp, Go, Java, Python, Ruby, Rust, Swift and TypeScript bindings are archived in CNA Cemetery) | The opaque-handle and error boundary (CNA_Handle, CNA_Result) and the foreign ownership rules on the other side. | A C++ change can become C ABI, lifetime and release-compatibility work. The C API is opt-in (CNA_BUILD_C_API). |
| Validation | module tests/ and examples/, root tests/, cmake/UnitTests.cmake, cmake/Harnesses.cmake, .github/workflows/ (18 workflow files) | Configured evidence, usually conditional on the selected host and backend. | "All tests" means all tests in one configured graph. |
Modules that look small or optional — design, diagnostics, media, net, phone and others — are not presumed simple. Each has an internals page in the Development area (for example design, phone, net), written from source reading at this snapshot; none of those pages establishes runtime behaviour on every host. The generated module index is a locator, and the repository map and source-ownership guide explain who owns what. The user-level view of the same layout is Architecture: module layout.
Understand the three backend selections before debugging
Three CMake selections decide which code actually runs, and they are composed by the root CMakeLists.txt from separate files:
| Selection | Answers | Values at this snapshot | Decided in |
|---|---|---|---|
CNA_PLATFORM | Who owns windows, events, input, timing and host services? | 3: SDL3 (default), HEADLESS, TERMINAL (POSIX only) | cmake/PlatformSelection.cmake |
CNA_AUDIO_PLATFORM | Who opens playback and recording devices, and (for two values) mixes? | 3: SDL3 (default), NULL, ALSA; SOUND_ENABLED only for SDL3 and ALSA | cmake/AudioPlatformSelection.cmake |
CNA_GRAPHICS_RENDERER / CNA_GRAPHICS_RENDERERS | Which renderer is the default, and which others are compiled into the same binary for runtime selection? | 10 public identities over 8 families | cmake/RendererIdentities.cmake, cmake/RendererSelection.cmake, cmake/RendererRegistry.cmake |
The user guides count four axes — these three plus the target operating system — and one switch, CNA_ENABLE_SDL (see Platform support: four separate axes and Architecture: the four build axes); the selection axes index lists every value. The selections are validated separately and have hard exclusions that fail at configure time with a named reason rather than falling back.
Platform selection is not graphics selection. SDL3 hosts the OpenGL contexts and the plain windows that the other GPU renderers draw into, through whichever video driver SDL chose; TERMINAL has no graphical window, so cmake/RendererSelection.cmake refuses every renderer except the CPU-frame identities SOFTWARE, HEADLESS and STUB before any renderer dependency is probed. A physical family may implement several public names: EasyGL serves three GL profiles (OPENGLES3, OPENGL33, WEBGL2) through separate descriptor accessors on one target, and every other family serves one identity. And in a multi-renderer build the default, the selected and the active renderer can differ (runtime renderer selection).
Before attributing a failure to "the renderer", record the exact CMake cache (the three selections, the toolchain, CNA_ENABLE_SDL) and the identity that was actually constructed: CNA::GraphicsRendererSelection::GetActive(), the CNA: graphics renderer: <name> line the device logs on first construction, and GetFallbackHistory() if a fallback chain was enabled.
Where small changes have broad effects
The broadest blast radius belongs to a handful of contracts:
CNA::Platform::IPlatform(IPlatform.hpp) — three implementations; Headless is compiled into every build and Terminal into every POSIX build, whateverCNA_PLATFORMsays, so thatPlatformConformanceTests.cppcan hold several implementations to one contract in one process;IGraphicsRendererandGraphicsRendererDescriptor(IGraphicsRenderer.hpp,GraphicsRendererDescriptor.hpp) — 8 families;GraphicsDeviceandGraphicsResource(GraphicsDevice.hpp,GraphicsResource.hpp) — every resource type and every renderer;- public headers in general, the compiled renderer selection, and the renderer identity table, which exists three times (the CMake list,
CNA::GraphicsRendererTypeinGraphicsRendererType.hpp, andCNA_GRAPHICS_RENDERER_*inCNA/C/graphics.h) and is held together byscripts/check_renderer_identities.py; - the C ABI's handle and ownership rules.
A one-line virtual signature or disposal-order change may demand edits in all backends, their tests, the fallback logic and the bindings. More local work can live in a specific terminal input decoder, an SDL3 event translation, a specialized content reader or one backend's shader translation — provided the common contract and the serialized format stay unchanged. "Local" must be proved by call search and tests, not inferred from a file name.
Lifecycle-sensitive areas deserve extra care; each of these is visible in the source at this snapshot:
Gameinstalls its owned platform from the initialiser of its first member,platform_(InstallPlatforminGame.cpp), so every later member constructor that reaches the ambient platform finds the game's instance; aPlatformInstallationguard undoes the installation if construction throws.GraphicsDevice_is a value member ofGame, and theGraphicsDeviceconstructor runsresolveRenderer(), so the renderer and its window exist before your derived constructor body — long beforeInitialize.- Resources register with the device (
AddResourceReference) as raw pointers and are not C++-owned by that registry; a resource that outlives its device checks a weak lifetime token (resourceDeviceLifetime_) rather than a dangling pointer. GraphicsDevice::Dispose(bool)disposes every registered resource beforedestroyNativeResources()resets the renderer, so the renderer is still alive while resources release native objects.- Audio device callbacks can overlap instance destruction; the SDL3-audio harnesses in
cmake/Harnesses.cmakeexist precisely to destroy a mixer while a voice is active. - C API handles outlive a single call;
HandleRegistrychecks generation, object kind and creating thread on every use (the one any-thread lookup servescna_sound_effect_playandcna_sound_effect_play_with_settings, and a game may opt in to queueing other threads' calls onto its own thread).
The ownership and thread and callback maps make these relationships explicit; the blast-radius page turns them into review questions.
What the repository layout signals
modules/<name>/includeis a module's public or cross-module header boundary; cross-module internal contracts live under some module'sinclude/CNA/Internal/.src/holds the implementation and module-private headers, and no module adds another module'ssrc/as an include root. Modules may also owntests/,examples/andbenchmarks/.modules/renderers/contains the physical backends and the shared renderer helpers.cmake/performs selection (PlatformSelection,AudioPlatformSelection,RendererIdentities,RendererSelection,RendererCombinations), registry generation (RendererRegistry), third-party discovery (ThirdParty*.cmake) and test registration (UnitTests,TestHelpers,Harnesses,TestDisplayPolicy).- Root
tests/holds cross-module suites, fixtures, reference data and assets (OracleCorpusTests.cpp,fixtures/,assets/,reference/,gltf-l7/,interop/); module-local tests belong to their physical owners. tools/andscripts/contain generators, probes, harness sources and developer checks, not runtime libraries..github/workflows/shows where a configuration is actually compiled or executed (18 workflow files, 25 jobs; see what CI actually covers).third_party/andvendor/are dependency boundaries (submodules and vendored code); treat them as external.- Planning and handoff material —
plans/,spikes/,docs/and the root-levelhandoff.md,CHECKLIST.md,CNA_ENGINE_REPAIR_HANDOFF.mdandCNA_DEMOS_REPAIR_MATRIX.json— can explain a migration, but current implementation and active CMake are authoritative. The audit, remediation, modularization and integration trees of earlier snapshots, the retiredmisc/notes and the closed repair handoffs survive only in Git history.
Start with CMakeLists.txt and modules/CMakeLists.txt to see composition (option defaults, selection order, the CNA umbrella target, the ownership gate), then use the generated module inventory to find a physical owner and the physical module dependency map for link edges. This page is deliberately about where responsibility lives; it does not claim that each optional family is mature or tested on every host — renderer coverage is configuration-scoped.
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- Architecture
- Architecture overview · Physical module dependency map
- Internals
- Internals · Renderer selection internals
- Maintainer workflow
- Repository map · Source ownership
- Tests and validation
- Test architecture · Renderer coverage is configuration-scoped
- Reference
- Module index · Selection axes index