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 c1c316b9; 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 c1c316b9; 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 c1c316b9, 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, and location is ownership: a module's CMake globs exactly its own trees. |
| 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()); non-XNA additions 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 extension module has its own docs/Doxyfile.cnaext, which no CTest entry runs at this snapshot, 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 apple/m4-stabilization 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) 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 14, 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. 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. |
| CNAEXT extensions | 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 (begin, end, clear, addLine on DebugDraw), properties as getX()/setX() and isX() for booleans (for example setCurvature() on CRTEffect), UpperCamelCase types and enumerators (CRTMaskType, DepthEffectMode, DitherMode); the GLSL ES variants of the post_process shader package are written to GLSL ES 3.00. check_cnaext_naming.py still exists, but no CMake file registers it as a CTest entry at this snapshot, so these rules are kept by review. |
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 CNAEXT extensions are the opt-in CNA::Graphics module behind the CNA_CNAEXT CMake option (off by default): the retro effects AsciiPostProcessEffect, CRTEffect and DepthEffect, DebugDraw, and the shader-package types ShaderCodeEXT and ShaderPackageEXT. With the option off every header and source file in modules/graphics-ext is empty, because each is wrapped in #ifdef CNA_CNAEXT; no CTest entry checks that wrapping at this snapshot. The CNAEXT-marked members inside Microsoft::Xna::Framework::Graphics (PbrEffect, SkinnedPbrEffect, morph targets) are always compiled and are not affected by the option; only ShaderEffect's ShaderCodeEXT and ShaderPackageEXT constructors are compiled with it. The module's scope is described in CNA's cnaext-engine-layer.md; read it before reconstructing the module 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 renderer hands out a recursive-mutex context lease that the game thread holds for Initialize, BeginRun, every tick and EndRun, and that every content decode and every texture, buffer or render-target constructor, SetData, GetData and Dispose takes on whichever thread calls it, 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; the exceptions are stated per route (cna_sound_effect_play answers on any thread since ABI 0.43.0) or opted into per game (cna_game_set_foreign_thread_calls_ext, ABI 0.39.0, queues another thread's call to run on the game 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 c1c316b9, and its Ms-PL licence and FNA provenance.
- CNAEXT catalogue: extension surfaces by namespace — A catalogue of CNA's non-XNA surface at snapshot c1c316b9, 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: The HEADLESS native-SDK-free and VULKAN link-closure gates run in no CI job — Since AM4-167 the link-closure check also reads a Ninja tree's link line, so general-tests-ci.yml evaluates the gates of its OPENGLES3 tree; the NativeSdkFree_*, GraphicsNativeSdkFree and VulkanRendererClosure gates exis
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