Conventions
Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit 009d40f5; unless a sentence says otherwise, nothing here was built or executed. Each rule was matched to the file, gate or test that holds it by reading the source at 009d40f5; nothing was configured, built or run, and rules held only by review are labelled as such.
These rules separate what CNA's repository enforces or states outright, patterns that recur in the code, and cautious recommendations inferred from it. They are not a generic C++ style guide: each is tied to a file, a gate or a test at snapshot 009d40f5, and each says how it is held, because a rule held only by review behaves differently from one that fails the configure step. The instruction files themselves are AGENTS.md and CLAUDE.md; the per-file porting checklist is CHECKLIST.md. Nothing was built or run for this page.
Enforced or explicitly documented
| Rule | Stated in | How it is held |
|---|---|---|
Physical modules own their CMakeLists.txt, include/, src/, tests/ and examples/ (and benchmarks/); every production translation unit lives under a declared module. | physical-modules.md, CLAUDE.md | Configure error. The source-partition validator in modules/CMakeLists.txt fails on a translation unit outside every declared module tree and on a legacy root src/ or include/ tree; its declared module lists are the authority for what a module is. The one documented exception is the GDI family compiling eight software-module 2D translation units. |
| Module dependency direction is fixed: a module links only its declared closure. | physical-modules.md | CTest. The minimal-link probes in tests/modules and the ModuleLinkClosure_* entries in ModuleProbes.cmake fail when a forbidden archive appears on a probe's link line (for example nothing but math and sharp-runtime for probe_math). |
Public CNA APIs use XNA-compatible naming and the Microsoft::Xna::Framework namespaces where that compatibility surface applies; the CNA namespace is for extensions, helpers and internals. | AGENTS.md, CLAUDE.md | Review, plus signature tests. Names, method signatures and behaviour are XNA's; freeze tests exist for some surfaces (PublicApiInputSignatureFreezeTests), and a representation audit compares the public surface with the Microsoft reference data (audit_xna_runtime_surface.py). |
A member that is not part of XNA 4.0 is marked CNAEXT (types, members and GetTypeName()); engine-layer names carry the EXT suffix where they extend an XNA concept. | AGENTS.md, CHECKLIST.md | Compile-time, for one surface. Under CNA_STRICT_XNA_API the marker expands to [[deprecated]]; StrictXnaApiSurfaceCheck_Compile_Run compiles a file with -Werror=deprecated-declarations that must not call a CNAEXT member of the Devices and Sensors surface, and StrictXnaApiSurfaceLeakCheck_MustFailToCompile proves the check can fail. Elsewhere it is a review rule. See two things named CNAEXT. |
Exported public API declarations have Doxygen documentation in the /** @brief ... */ block form (never a bare ///), with @param and @return where they apply. | AGENTS.md, CHECKLIST.md | Review, with partial gates. The root Doxyfile sets WARN_IF_UNDOCUMENTED = YES but WARN_AS_ERROR = NO, and no default CTest fails on a missing comment. The CNAEXT engine layer has CNAEXT_DoxygenGroup (it skips with code 77 when doxygen is absent), and the C API coverage generator drives doxygen over the public C++ headers to build its inventory. |
| Repository-specific SharpRuntime aliases and containers are used where public compatibility requires them; a missing .NET type is added to sharp-runtime first, not inlined as a raw C++ type. | AGENTS.md | Review. The alias table (bytecs, intcs, longcs, String, charcs, ...) and System::EventHandler<T>, System::IDisposable and the isDisposed_ guard are observed throughout, for example in Game.hpp. The sibling repository must be its next branch. |
Platform and backend source selection is done by CMake and audited scripts; new production code uses IPlatform and its narrow services and does not scatter OS detection or SDL calls. | CLAUDE.md (Platform Boundary) | Configure error by default. The SDL ratchet (sdl_ratchet.py, budget zero, strict, with the native X11 and Wayland backends explicitly counted) and the hot-path lint (hot_path_lint.py) run from PlatformRatchet.cmake and PlatformHotPathLint.cmake (skipped without Python 3); see the SDL invariant. |
Renderer-specific behaviour reaches the XNA layer through an IGraphicsRenderer virtual or a descriptor hook, never through #ifdef CNA_RENDERER_*; a family's own macro is private to its target. | CHECKLIST.md, CLAUDE.md | CTest. RuntimeRendererDiscipline runs check_runtime_renderer_discipline.py: a CNA_RENDERER_* macro in modules/graphics/src fails (the allowed table is empty), every family owns exactly one descriptor unit and a registry entry, and only the default identity's macro is project-wide. RendererTargetDiscipline and RendererCombinationRegistry guard the neighbouring rules. |
| The public renderer identities are a curated set of 25, with fixed C ABI values that are never reassigned; a name outside the set is refused at configure time. | CLAUDE.md, RendererIdentities.cmake | Configure error and CTest. check_renderer_identities.py and RendererIdentityRegistry pin the list, the C++ enumerators and the ABI values; a retired selector gets its own refusal. |
| C API headers stay valid pure C with no C++ exceptions or types; the C targets compile as C17 and the headers' floor is C99 (they are also compiled as C++11, 14 and 17). | AGENTS.md, COMPATIBILITY.md | CTest. CApiHeaderCompatibility compiles every public header on its own in every declared mode of every installed toolchain and runs in the ordinary build; CApiAbiHeaderBaseline and CApiCoverageMatrix guard layout and mapping. The C targets set C_STANDARD 17 with extensions off. |
| Window and GPU tests never touch the maintainer's live desktop. | AGENTS.md, CLAUDE.md | CTest and configure. TestDisplayPolicy.cmake (CNA_TEST_DISPLAY empty by default), CnaTestDisplayPolicy, CnaTestDisplayIsolation, and the private runner refusing a tree that forces a display. |
Each public XNA method, constructor, operator and constant has at least one unit test, added in the same task as the code; an API is not marked complete in AUDIT.md before its tests are. | AGENTS.md | Review. No CTest counts per-member test coverage of the C++ surface (the C API has a route-test coverage gate); the representation audit counts presence, not behaviour. |
Three further conventions were followed in every file read for this page and are gated nowhere: every .hpp and .cpp starts with // SPDX-License-Identifier: MS-PL; concrete classes that derive from System::Object override GetTypeName() with CNAEXT and return the fully qualified .NET name; and comments explain only the non-obvious why (never “taken from FNA”, and an intentional deviation from FNA logic gets a // comment at the code). The accepted C++ deviations from FNA and XNA (for example GetHashCode() returning std::size_t, ref and out parameters as value-reference pairs, lifetime-safe wrapper aliases for graphics-state properties) are tabulated in CHECKLIST.md.
The instruction files are layered and can lag the code. CLAUDE.md records a dated owner decision of 2026-09-04 that where measured Microsoft XNA behaviour and FNA disagree, CNA follows XNA (with a comment naming the XNA IL and a row in plan_bindings_upstream.md), while one line of CHECKLIST.md still says FNA wins on semantics; API surface is checked against the XNA reference assemblies, not only FNA. CLAUDE.md's platform paragraph also lists WIN32 among reserved identifiers, but PlatformSelection.cmake implements it (offered when the target is Windows) and reserves TERMINAL there instead. When a rule and the CMake or source disagree, the CMake or source is right and the dated decision wins over an older line.
Strongly established patterns
| Area | Observed pattern | Examples at the snapshot |
|---|---|---|
| Files and namespaces | Include roots mirror the public namespace path, so the include spelling is identical across modules; internal contracts live under CNA/Internal/.... A module's src/ uses Xna/ for the XNA implementation, Internal/ for engine parts and CnaExt/ for extension surfaces; single-area modules stay flat. | Texture2D.hpp with Texture2D.cpp; renderer contracts under CNA/Internal/Renderers/Common; SdlInputBridge.cpp beside Xna/ and CnaExt/. |
| Ownership | RAII and value ownership at coordinators; public graphics resources register with their device; raw pointers usually express a non-owning or native boundary reference; ownership is stated where it is not obvious. | Game holds its device, content manager and window as value members and its platform as a unique_ptr; GraphicsDevice::resources_ tracks but does not own; the ownership vocabulary is tabulated in the ownership map. |
| Errors | The mechanism depends on the layer, and native and ABI boundaries translate rather than leak implementation failures. | XNA layer: XNA-shaped exceptions (InvalidOperationException, ObjectDisposedException, NotSupportedException, ContentLoadException). Platform: PlatformException and PlatformNotSupportedException naming the capability. Internal device contracts: standard exceptions (IAudioDevice throws std::invalid_argument and std::logic_error). C ABI: result codes and a thread-local last error, with callbacks run inside an exception barrier. |
| Factories | Platform, renderer and audio-device construction is registry or factory driven after CMake selection; an unknown or uncompiled name refuses with the list of what is available. | PlatformFactory::Create(name); the generated GraphicsRendererRegistry built from RendererRegistry.cmake; CreateSelectedAudioDevice() (compile-time, in AudioDeviceFactory.cpp); CnbLoaderRegistry. |
| Conditionals | Prefer target and source selection, descriptors and virtuals to broad preprocessor blocks in neutral modules; per-family behaviour is data (a descriptor field) or a virtual. | The window kind, high-DPI request and video-subsystem need are descriptor data, not #if chains (GraphicsRendererDescriptor.hpp); the discipline gate above. |
| Tests | Focused tests beside modules; conformance and cross-module behaviour in broader roots; parameterised suites run once per implementation. | modules/<module>/tests/ mirroring the namespace path; top-level tests/assets and tests/modules; PlatformConformance, AudioDeviceConformanceTests, the parity fixtures. |
Naming quick reference
| Layer | Namespace and location | Convention |
|---|---|---|
| XNA public API | Microsoft::Xna::Framework::... in modules/<module>/include/Microsoft/Xna/Framework/... | Exact XNA and FNA names; C# properties as getXProperty() and setXProperty(...); events as System::EventHandler<T> fields with Raise or Invoke; IDisposable as System::IDisposable; C# static readonly as static const defined in the .cpp. C# internal becomes private, protected, a detail namespace or nothing. |
| CNA extension in an XNA type | The same header, marked CNAEXT | Suffix EXT for a member that extends an XNA concept (for example GetRendererCapabilityProfileEXT()); the marker also tags members that carry no suffix, such as SetContextRecoveryEnabled. |
| CNA contracts and helpers | CNA::... (for example CNA::Platform, CNA::GraphicsRendererSelection); internal contracts in CNA/Internal/... | PascalCase types and methods; interfaces prefixed I; services named for what they serve. |
| Engine layer | CNA::Graphics in modules/graphics-ext, behind CNA_CNAEXT | House rules that differ from the XNA layer because there is no XNA name to keep: lowerCamelCase verbs (apply, resize, begin), properties as getX()/setX() and isX() for booleans (for example getExposure(), isHDREnabled()), UpperCamelCase types and enumerators; shaders written to GLSL ES 3.00 (compute to 3.10) with ShaderEffect owning the #version line. check_cnaext_naming.py and check_cnaext_nodiscard.py are registered as CTest entries by the module's example CMake. |
Two things named CNAEXT
The word names two different mechanisms, and conflating them is a common mistake. The marker CNAEXT (CNAHelper.hpp) tags a member or type that is not part of XNA 4.0; it is always compiled, expands to nothing in a normal build and to [[deprecated]] under CNA_STRICT_XNA_API. The engine layer is the opt-in CNA::Graphics module behind the CNA_CNAEXT CMake option (off by default): with the option off every file in modules/graphics-ext is empty, and the CNAEXT_GuardDiscipline test (a text check that runs in every configuration with tests) fails if a file is not wrapped in #ifdef CNA_CNAEXT. The CNAEXT-marked members inside Microsoft::Xna::Framework::Graphics (PbrEffect, ShaderEffect, morph targets) are always compiled and are not affected by the option. The design lives in CNA's CNAEXT.md and plan_modern.md; read them before reconstructing the layer from its code.
Recommendations inferred from code
- Make ownership explicit in names, types and comments at async, callback and native-handle boundaries. The best examples explain the order and the reason at the declaration, as the
platform_comment inGame.hppand the borrowed-versus-owned window wrapper inGraphicsDevicedo. - Keep neutral types above backends and translate once at the implementation edge. The renderer surface hand-off (
RendererSurfaceInfo) and the descriptor's data fields replaced raw windowing-library flag bitmasks for exactly this reason. - When changing a lifecycle, write the destruction order in the test or a design note.
GamePlatformOwnershipTestsand the teardown harnesses record theirs in comments; the order inGame.hppitself is not asserted by any test (invariant). - Prefer capability queries that remain stable over trial-and-error native calls throughout shared code. Callers cache the capability set, and
PlatformConformance.CapabilitiesAreStableAcrossCallsholds implementations to it.
These are recommendations, not necessarily lint-enforced project law. A contrary local pattern with tests may be intentional; investigate before normalizing it.
Threading and affinity
The game loop, platform events and input snapshots are treated as one-thread-owned: the thread that runs Game::Run owns Game state, and input state is written from Game::PollEvents and read from Update and Draw on the same thread. Graphics context and device work may require backend-specific thread leases or affinity: the EasyGL and OpenGL4 renderers hand out a recursive-mutex context lease that frame rendering and every content decode hold, while other families use different or no CPU-side locking. Audio callbacks operate under real-time restrictions: they must not block, allocate or throw. The C API additionally binds every handle to its creation thread. No absence of mutexes should be read as universal thread safety: a lock that guards one list, registry or callback path is a proven barrier for that path only. The full map, with the barriers the source establishes and the tests for them, is the Thread and callback map; the callback rule is an invariant.
Related: Architectural invariants, the change map, commit and task conventions and the generated Module index.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- CNA and XNA 4.0: what the compatibility promise covers — What CNA translates and cannot load, which reference settles a disputed XNA question, what CNA deliberately is not at snapshot 009d40f5, and its Ms-PL licence and FNA provenance.
- CNAEXT catalogue: extension surfaces by namespace — A catalogue of CNA's non-XNA surface at snapshot 009d40f5, namespace by namespace, with member names, reasons and boundaries, how much the CNAEXT marker covers, and what the strict check proves.
- From C# to C++: CNA's translation conventions — How CNA represents C# XNA concepts in C++ so that code stays diffable against the reference: names, properties, aliases, events, interfaces, disposal, visibility, layout and the porting checklist.
- Module boundaries: cycles, umbrellas and the gates that enforce them — Why CNA keeps three static-archive cycles, what the CNA, CnaExt and BuildConfig compositions carry, what each boundary gate and the fifteen module probes check, when they run and what a violation looks like.
- The sharp-runtime object model: Object, strings, exceptions, delegates and collections — What System::Object, String, Exception, IDisposable, GC, Delegate, MulticastAction, EventHandler, Type, TimeSpan and the fail-fast collections really are in sharp-runtime, and how CNA builds on them.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-231: CHECKLIST.md's per-file port checklist still says 'FNA still wins on semantics', contradicting the rule in CLAUDE.md that XNA wins where the two disagree — The checklist every ported file is reviewed against keeps the old tie-break, while CNA's contributor rules record the owner decision of 2026-09-04 that measured XNA behaviour wins over FNA's.
- CNA-BUG-232: docs/physical-modules.md has no row for modules/platform, omits the platform and diagnostics edges of runtime, input, audio and devices-ext, and says top-level examples/ holds only examples/golden — CNA's physical-module map, which check_renderer_identities.py keeps under count control, never mentions the platform module that input, runtime, audio and devices-ext link, and misdescribes the top-level examples directo
- CNA-BUG-273: CLAUDE.md and AGENTS.md tell contributors to throw std::runtime_error after disposal, while the public layer throws System::ObjectDisposedException at 51 sites in the graphics module alone — CNA's contributor instruction files give one rule for a disposed object (throw std::runtime_error), while the graphics, audio, media and net modules throw System::ObjectDisposedException, XNA's exception for that case.
- CNA-VGAP-019: ModuleLinkClosure_* link-closure gates report CTest SKIP in every Ninja tree, so no Ninja-preset run and no CI job ever evaluates them — check_module_link_closure.py reads the make-only CMakeFiles/<target>.dir/link.txt and exits 77 when it is missing; the Ninja presets and every CI job either use Ninja or never run these tests, so CNA's module dependency
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- Contribute · XNA compatibility · Experimental C API
- Architecture
- Architecture overview · Physical module dependency map
- Maintainer workflow
- Architectural invariants · Working on CNA · Change public XNA behavior · Thread and callback map
- Tests and validation
- What to test after changing X
- Reference
- Module index · Public header index