Verification & Known Issues
Every count on this page is a count of test definitions, not a pass rate. The numbers come from counting GoogleTest macros in the source tree and test registrations in the CMake files. A static count cannot tell you how many of those tests pass — that needs a run. If this page ever disagrees with what ctest prints on your machine, ctest is right.
How CNA is verified
An XNA reimplementation can pass its own test suite perfectly and still be wrong, because the tests encode the author's belief about what XNA does. CNA works around that with five layers, three of which compare against something outside the project.
| Layer | What it checks | Scale |
|---|---|---|
| Unit tests (GoogleTest) | Math, geometry, curves, packed vectors, game-loop semantics, state objects, input, audio, networking, content readers. Runs headless. | 568 CNA-owned C++ files under test paths, excluding vendored third_party; 539 contain 8,263 static GoogleTest definition macros. The instantiated run depends on configuration. |
GPU pixel-readback tests (ctest) |
Renders a scene on a real device context, reads the framebuffer back to the CPU, and asserts on actual pixel values. This is what catches "the shader compiles but draws the wrong thing." | CTest registrations are generated from the selected renderer, platform, audio, feature flags and available dependencies. Inspect each configured tree with ctest -N. |
| XNA 4.0 oracle corpus | Scenes rendered by real XNA 4.0 and diffed against CNA's DIRECTX9 output at tolerance 0. Ground truth is the actual Microsoft runtime, not anybody's reading of it. |
39 scenes under tools/xna-oracle/, with reference PNGs captured from real XNA |
| Differential testing against FNA | A harness links a real, running FNA build and dumps reference values to JSON; a matching C++ tool dumps CNA's; a script diffs them key by key. Ground truth comes from executing FNA, not from reading its source. | tools/fna-reference/, tools/cna-reference/, scripts/compare-fna-reference.py |
| API purity, checked by the compiler | Every declaration that is not part of XNA 4.0 is tagged CNAEXT. The CNA_STRICT_XNA_API purity mode turns any use of a CNA extension into a compile error, and compile-time signature-freeze tests pin the public API so a signature cannot drift silently. |
CNA_STRICT_XNA_API, tools/devices/StrictXnaApiSurfaceCheck.cpp, StrictXnaApiSurfaceLeakCheck.cpp |
What the test counts actually count
Alpha.1 deliberately needs scoped test facts. Static source inventory, instantiated GoogleTest cases, CTest registrations and executed results answer different questions.
| Number | What it counts |
|---|---|
| 568 files / 8,263 macros | CNA-owned C++ files under test paths (vendored third_party excluded) and static GoogleTest-family macro definitions. Of the 568 sources, 539 contain at least one counted macro. Parameterization, conditional compilation and renderer-specific targets mean this is not an executable case count or pass result. |
| Build-local CTest inventory | ctest --test-dir <build> -N is authoritative for a configured tree. Single vs multi renderer, test/example options, platform, audio and detected dependencies all change the result. |
Neither number is a claim that the tests pass. They are static counts, reproducible with grep and a look at the CMake files — see Reproducing these numbers yourself. Deliberately absent from this page: per-renderer test counts, per-renderer line counts and per-renderer maturity scores. CNA keeps no machine-readable maturity registry, so any such table would be somebody's impression dressed up as data.
Renderer coverage is configuration-scoped
CNA exposes 50 renderer identities across 46 implementation families. Single-renderer builds remain the default, while CNA_GRAPHICS_RENDERERS can link a compatible set for runtime selection. Both shapes affect verification:
- A green single-renderer run describes the selected family, not the other 45 families.
- A multi-renderer run verifies the compiled set and runtime selection/fallback paths; it does not make every renderer share one capability contract.
- Only the default gets the project-wide
CNA_RENDERER_*definition. Renderer-present test definitions identify every family compiled into the test target. - 50 identities are not 50 equally complete renderers. Some are intentionally 2D-only or produce no pixels.
Read evidence at its real scope. Windows-only renderers have Linux/Wine evidence plus manual native-MSVC workflows, not one universal native-hardware claim. Capability reporting has improved per family, but the common default still returns true for legacy capabilities unless a renderer narrows them; MultiStreamVertexInput and CompiledEffects are explicit false-by-default gates.
The XNA 4.0 oracle corpus
This is the strongest verification claim CNA can make, so it is worth being exact about what it is — and about which renderer it applies to.
Under tools/xna-oracle/ sit 39 scenes together with 39 reference PNGs captured from real XNA 4.0 running under Wine + DXVK. CNA renders the same scenes and the images are diffed byte for byte.
| Renderer | Scenes matched at tolerance 0 |
|---|---|
DIRECTX9 | 39 / 39 Pixel-exact |
OPENGLES1 | 11 / 39 |
| EasyGL (the shared GL-profile implementation) | 10 / 39 |
FNA3D | 10 / 39 |
"Pixel-exact" is a statement about DIRECTX9 and nothing else. DIRECTX9 is deliberately held to a byte-identical authenticity bar because it is the renderer that targets XNA's own Direct3D 9 behaviour. Every other renderer is held to a "looks right" bar, and the oracle numbers above show the distance. CNA does not claim that all renderers are pixel-perfect, and neither should anyone quoting this page.
The corpus covers 39 scenes, not the API. It is ground truth for what it covers and silent about everything else.
Eight of the thirty-nine
Every image below was produced by the real Microsoft XNA 4.0 runtime, not by CNA and not by hand. CNA's DIRECTX9 renderer draws the same eight scenes and the diff must come back empty at --tolerance 0. They are reproduced throughout the tutorials where they illustrate the technique being taught.
Vertex-coloured triangle — see Tutorial 31.
Triangle strip — see Tutorial 40.
Unlit textured quad — see Tutorial 32.
The same quad with lighting on — see Tutorial 32.
Alpha test — all eight comparisons are in Tutorial 54.
Wrap texture addressing — see Graphics State.
Fresnel-weighted reflection — see Tutorial 56.
Four-bone skinning, per-pixel lit — see Tutorial 57.
These are deliberately austere scenes. A corpus like this is useful precisely because each frame isolates one piece of behaviour, so a diff that fails points at something specific. It is not a demonstration of what CNA can render — for that, see the demos.
What CI covers (and what it doesn't)
The alpha.1 tag contains 21 workflow files. They are not interchangeable: some run focused suites, some are source/coverage gates, some only build or link, and the native Windows graphics workflows require manual dispatch. File count is not a pass count: the intended unfiltered general job and two Input matrix rows still select the removed EASYGL identity and fail at configure time.
| Area | Evidence at the tag | Important boundary |
|---|---|---|
| General Linux | Focused input, devices, arithmetic, glTF and renderer workflows; an intended unfiltered CTest job | general-tests-ci.yml is broken at the tag by CNA_GRAPHICS_RENDERER=EASYGL. The same stale value breaks two of five Input rows; the other three use valid identities. |
| Runtime renderer selection | multi-renderer-ci.yml exercises native sets and selection/fallback contracts | Only compatible families can share a binary. |
| Emscripten | Browser renderer lanes plus emscripten-multi-renderer-ci.yml | The multi-renderer lane establishes configure/build/link; browser lanes establish selected runtime behavior. |
| Platform abstraction | platform-ci.yml covers SDL3, real SDL2, Headless, Terminal and audio combinations | Hardware-specific behavior is not implied by a conformance harness. |
| Apple | macOS/Metal plus iOS device final-link and simulator smoke lanes | No physical-iOS-device or pixel-correctness claim. |
| Native C API | Five workflows are declared for ABI baseline, compatibility, coverage, limitations and release readiness | An isolated tag build reproduced the final implementation failure: its C renderer map has 49 entries against 50 canonical identities. With networking off it fails earlier on an unconditional GamerServices include. Checked-in gate reports do not override either result. |
| Windows D3D/GDI | Native MSVC workflows exist | Both are manual dispatch, not automatic merge gates. |
Three distinctions prevent overclaiming:
- A declared workflow is not evidence when its configuration is rejected;
EASYGLis an implementation family, not one of the 50 selectable identities. - A configure or final-link lane does not prove runtime behavior.
- A simulator or headless-browser result does not prove physical-device or every-GPU behavior.
- Android has source and NDK support but no workflow in this tag.
Where coverage is thin or absent
These are not "lightly tested", and it is better that you hear it here.
Storage. The whole storage module carries five test macros, andStorageContainer— the type your save code actually touches — has none. The implementation is realstd::filesystemIO; nothing asserts on it.- Renderer evidence remains per family. The 21 workflows include focused renderer gates, but they do not execute every capability of all 46 implementation families on every supported target.
- Optional layers need their own configurations.
CNA_CNAEXT,CNA_DEVICESandCNA_BUILD_C_APIdefault to OFF; a default build says nothing about them. The alpha.1 C API cannot reach runtime verification because its final implementation fails its renderer-count assertion. - The XNA oracle diff. Covered above, and worth repeating: the project's most rigorous check is not automated.
Current limitations
What follows is the current list of user-facing limitations. Every entry is a declared boundary — a capability CNA does not claim, or a documented deviation — rather than an open defect report. Where an item once appeared here as a bug and has since been fixed or narrowed into a boundary, it has been removed rather than left standing as a warning.
Graphics and renderers
- Compiled effects are renderer-qualified. XNA/FNA D3D9 Effect Framework bytecode works on FNA3D and on opt-in EasyGL, SDL_GPU and Vulkan builds. HLSL
.fxsource and MGFX remain unsupported; other renderer families reportCompiledEffectsfalse. - Runtime renderer selection is opt-in. A normal build still contains one renderer. Multi-renderer builds must use compatible sets, fallback is disabled by default, and selection latches after the first device.
- Cube faces inside a multiple-render-target set are unimplemented on eight otherwise-capable 3D renderers:
DIRECTX9,DIRECTX11,DIRECTX12,BGFX, the EasyGL family,SDL_GPU,OPENGL2andOPENGL4. DIRECTX1is a declared stub — its entire 3D pipeline throws.LLGLis experimental, Linux/X11-only and OpenGL-only; its Vulkan module cannot be selected.OPENGLES2has no instancing.SupportsCapability()still has a permissive legacy default. Renderers must narrow unsupported entries. Multi-stream input and compiled effects are false-by-default because both require explicit implementation.
Platforms
- Web has no save persistence at all. Under Emscripten,
SDL_GetPrefPathlands in volatile MEMFS; CNA mounts no IDBFS and never callsFS.syncfs. Every save is silently discarded on page reload. - On Web,
Gamemust be heap-allocated. A stack-allocatedGameis silently corrupted — there is no diagnostic, so this one is worth reading twice. - Video is absent entirely on Windows, Emscripten and Android. The video translation units are excluded from those builds, so the headers still compile and the link then fails on missing
Video/VideoPlayersymbols. - FFmpeg is a hard requirement on Linux and macOS. No option disables it; configure fails without
libavcodec,libavformat,libavutilandlibswresampledevelopment packages. - Network discovery is SystemLink only, and nothing at all on Emscripten.
API members that always throw or no-op
- 15
Guide::Show*overlay entry points are documented no-ops. (TheBegin/Endmessage-box and keyboard-input pairs are not among them — those are real implementations.) - A few methods always throw:
Achievement::GetPicture(),PropertyDictionary::CopyTo(),NetworkMachine::RemoveFromSession(),ResourceContentManager::OpenStream, andBoundingFrustum::Intersects(Ray)in part. - 3D audio supports exactly one
AudioListener; adding a second throws.
Content and decoding
.xnbfiles using Intel E8 preprocessing are not decoded.- DDS cube maps are limited to DXT1, DXT3 and DXT5.
.m4aand.aacare unplayable — SDL3_mixer ships no AAC decoder.- An XACT wave bank containing XMA or WMA data does not throw: it logs to stderr and returns
nullptr, so the sound is simply missing. The XNBSoundEffectReaderpath does throw. - glTF retains unskinned rigid clips and factor-only PBR, validates required extensions and exposes structured import diagnostics. A mixed skinned model cannot also use
Tagfor extra rigid clips, so those tracks are omitted with a named diagnostic.
Build-time switches
CNA_CNAEXT(the CNAEXT engine layer, includingAsciiPostProcessEffect,CRTEffect,DepthEffect,PbrMaterialand theCNA::Graphicssurface) defaults to OFF.CNA_DEVICES(the CNAEXT device layer) also defaults to OFF.
Deliberate deviations from XNA
These are not bugs and are not on a roadmap to be "fixed" — they are design decisions with real consequences for porting, so plan around them.
| XNA does | CNA does | Why it matters |
|---|---|---|
Ships custom shaders as compiled .fx bytecode |
Loads compatible Effect Framework binaries on qualified builds; also offers CNAEXT ShaderEffect |
Already-compiled XNA/FNA effects can be retained when the active renderer exposes CompiledEffects. CNA still does not compile HLSL source or accept MGFX. |
Builds .xnb files with a design-time content pipeline |
Reads .xnb and never writes one |
CNA is read-side only: it consumes .xnb produced by XNA, MonoGame or FNA. There is no ContentImporter, ContentProcessor or ContentCompiler, and there will not be — that is explicitly out of scope. Call it an XNB loader, not a content pipeline. |
| Discovers XNB type readers by reflection | Requires one explicit RegisterAllBuiltInXnbReaders() call at startup |
C++ has no reflection. There are 50 built-in readers (49 in a build without FFmpeg, which drops VideoReader), and none of them are registered until you make that call. |
| Selects a graphics device at runtime | Defaults to a compile-time renderer, with opt-in compatible sets through CNA_GRAPHICS_RENDERERS |
A multi-renderer binary chooses before device creation and may use explicit fallback. The build default and the active renderer are deliberately separate answers. |
Exposes Begin*/End* pairs on Storage and GamerServices that look asynchronous |
Runs the callback inline, before Begin* returns |
Faithful to XNA's own fake-async, but do not expect it to keep a frame moving. The work happens on the calling thread. |
Runs GamerServices against Xbox LIVE |
Persists achievements and leaderboard entries as JSON on the local disk, under SDL_GetPrefPath |
Your achievements and leaderboards genuinely survive a restart on that machine. What does not exist is the online half: no sign-in against Microsoft's servers, no matchmaking, no cloud sync. |
Supports Xbox LIVE matchmaking (PlayerMatch, Ranked) |
Implements SystemLink for real — genuine UDP with LAN broadcast discovery and measured RTT for QoS |
LAN multiplayer works across real machines. Internet matchmaking does not exist, and the other session types no-op rather than pretending. |
Reproducing these numbers yourself
Nothing here requires special access. Run these from the exact tag checkout:
# CNA-owned C++ source files under test paths (vendored code excluded)
rg --files -g '*.cpp' -g '!third_party/**' |
awk '$0 ~ /(^|\/)(test|tests)(\/|$)/' | wc -l
# Static GoogleTest-family definition inventory (not a pass count)
rg -n '^\s*(TEST|TEST_F|TEST_P|TYPED_TEST|TYPED_TEST_P)\s*\(' \
-g '*.cpp' -g '!third_party/**' | wc -l
# What this configured build actually registers
ctest --test-dir build -N
Building and running the suite needs the sibling repositories checked out next to cna/. They are separate clones, not submodules:
git clone https://github.com/openeggbert/cna.git
git clone https://github.com/openeggbert/sharp-runtime.git # required by every build
git clone https://github.com/openeggbert/easy-gl.git # the GL-profile renderers
git clone https://github.com/openeggbert/meta-gl.git # needed by easy-gl
# Linux also needs the FFmpeg development packages, or configure fails:
# libavcodec-dev libavformat-dev libavutil-dev libswresample-dev
cd cna
git submodule update --init # non-recursive is correct, and much faster
cmake -S . -B build -DCNA_GRAPHICS_RENDERER=OPENGLES3
cmake --build build --target CnaTests
# everything registered with CTest (needs a GL context; use xvfb-run on a headless box)
ctest --test-dir build --output-on-failure
Swap OPENGLES3 for another identity to reproduce a different single-renderer slice, or configure a compatible CNA_GRAPHICS_RENDERERS set to exercise runtime selection. The resulting CTest inventory is the only count that applies to that configured build.
Command traps worth knowing before you file a bug against your own shell history.
cmake --build build --target CNAno longer works.CNAis anINTERFACEumbrella target with no sources; buildCnaTests, a demo target, or justcmake --build build.D3D9,D3D11,D3D12,EASYGL,DX3andASCIIare not valid renderer values today and stop configure with aFATAL_ERROR. The DirectX names areDIRECTX9,DIRECTX11,DIRECTX12; the oldDX3is nowFREEDIRECT; the GL profiles are selected individually asOPENGLES2,OPENGLES3,OPENGL33,WEBGL1orWEBGL2; and the ASCII renderer became a CNAEXT post-process effect.ctest -L D3D9andctest -R D3D11match zero tests. The labels areDIRECTX9andDIRECTX11.- Two equivalent selection forms exist and must not be mixed:
-DCNA_GRAPHICS_RENDERER=<NAME>or-DCNA_RENDERER_<NAME>=ONwith exactly one switched on. - The C++ framework has no general install/export/CPack rules, so C++ consumers
add_subdirectoryCNA. The experimental C layer declares its ownCNACApipackage, but alpha.1 cannot build the final implementation and therefore provides no verified consumable C package. - Configure presets available:
web,tests,devices-asan,devices-tsan,devices-ubsan.
Found a discrepancy between this page and what your build reports? That is worth an issue — the whole point of publishing these numbers is that they are checkable.