I need to change public XNA behavior
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. Read from the CNA source, CLAUDE.md and the committed oracle records at 009d40f5; no oracle, test or build was run, and the census inputs that live outside the repository were not available.
CNA's public surface under Microsoft::Xna::Framework is a promise about what a program written for XNA 4.0 will observe, so changing it is a decision about XNA, not about C++. This recipe covers where that surface is implemented, how to establish what real XNA does (and what to do when its reference implementations disagree), how to mark and record a deliberate difference, and how far a change ripples: tests, the C ABI, bindings, documentation and content output. Everything was read from the CNA source and its instructions at snapshot 009d40f5; nothing was built, run or measured for this page.
Representation is not behaviour. CNA's census reports 331 of 331 public XNA types and 3,627 of 3,627 documented runtime members represented (Verification: coverage reports); that says the symbols exist, not that their behaviour was compared with XNA. A change that "matches FNA" or "matches the documentation" is a claim that still needs its own evidence.
Find the owner
The XNA-shaped headers live under each owning module's include root, which reproduces the namespace path; the implementation sits in the module's src/Xna/ directory (or flat in a single-area module). Renderer implementations are hidden behind CNA::Internal contracts and are not part of the XNA surface (Architecture overview).
| XNA area | Module and header root | Implementation |
|---|---|---|
Math and value types (Color, Vector2/3/4, Matrix, Rectangle, Curve, bounding volumes, MathHelper) | math | modules/math/src, one file per type |
Game, GameTime, components, GraphicsDeviceManager, GameWindow, TitleContainer | runtime | modules/runtime/src (flat) |
Graphics: GraphicsDevice, resources, states, stock effects, SpriteBatch, packed vectors | graphics | modules/graphics/src/Xna; native work is in modules/renderers |
| Input, audio, media, storage, gamer services, net, content | the input, audio, media, storage, gamer-services, net and content modules under modules/<name>/include/Microsoft/Xna/Framework/ | input, audio, media, content and the corresponding directories |
Framework.Design converters; Content.Pipeline facade; Microsoft::Devices and Microsoft::Phone | design, content-pipeline, devices, phone | design, content-pipeline, devices, phone |
The framework's own rules for this layer are in the repository's CLAUDE.md: XNA types stay in their XNA namespace; class, method, enum and constant names match XNA exactly; C# properties are getXProperty() and setXProperty(); events use System::EventHandler; IDisposable maps to System::IDisposable; .NET concepts missing from CNA are added to sharp-runtime first; and no compatibility aliases are added to keep an outdated demo compiling. The CNA namespace is for extensions and internals.
Decide what real XNA does
Do not start from the C++ you would like. Establish the XNA behaviour first, from the strongest evidence available, and write down which kind you used.
| Evidence | What it can settle | What it cannot | Where it lives |
|---|---|---|---|
| Microsoft's XNA 4.0 documentation and the shipped assembly metadata | The public surface: which types, members, overloads and documented exceptions exist. CNA's census is built from the documentation XML plus DLL metadata. | Runtime behaviour; the documentation is silent or imprecise about ties, clamping and exception order. | tools/audit_xna_runtime_surface.py (its inputs sit outside the repository, so the census was not re-run here). |
| Measured XNA 4.0 runtime | Numeric rules, exceptions and rendered pixels, with a committed record. | Only what the harness case expresses; the graphics scenes were captured under Wine with DXVK, not on Windows. | tools/xna-oracle (scene-driven pixel diff, scripts/xna-diff.py); tools/xna-pipeline-oracle and its framework/ measurements; committed results in tests/reference/xna40. |
| FNA source | The day-to-day reference: readable, and identical to XNA almost everywhere. | It is a reimplementation with known divergences, several marked FIXME in FNA itself. Matching FNA is not a defence for a behaviour XNA does not have. | An external checkout named in CLAUDE.md; a numeric FNA-versus-CNA dump lives in tools/fna-reference, tools/cna-reference and scripts/compare-fna-reference.py (docs/fna-reference-harness.md). |
| CNA's deliberate deviations | Differences that are decisions, not bugs. | Anything not on the list. | Deliberate deviations from XNA: compiled effects instead of runtime .fx compilation, a separate build-time pipeline, an explicit RegisterAllBuiltInXnbReaders() call for a ContentManager used outside a Game, a compile-time renderer default, inline "asynchronous" Begin*/End* callbacks, local-disk GamerServices and a real SystemLink. |
The tie-break rule is CNA's own: where a measured Microsoft XNA behaviour and FNA disagree, CNA follows XNA (CLAUDE.md, decided 2026-09-04). It applies to measured behaviour. The upstream-defects plan plans/plan_bindings_upstream.md warns that its rows must be re-measured before they are worked on, because several rows a binding still carried as open had already been fixed; do not change a behaviour on the strength of prose alone. For pixels, read Using the XNA oracle as evidence before quoting any oracle result: CNA's records report a zero-tolerance corpus result for DIRECTX9 only, and that reference was captured under Wine and DXVK on Linux.
Mark what is not XNA
Anything a game can call that XNA 4.0 does not have must carry the CNAEXT marker from modules/core/include/CNA/CNAHelper.hpp. In a normal build it expands to nothing; with CNA_STRICT_XNA_API defined it expands to [[deprecated]], so a strict compile with -Werror=deprecated-declarations fails if code calls an extension. The strict check is wired for the Microsoft::Devices surface only: with tests on and a GNU or Clang compiler, Harnesses.cmake builds tools/devices/StrictXnaApiSurfaceCheck.cpp with -Werror=deprecated-declarations and registers StrictXnaApiSurfaceCheck_Compile_Run. Its negative twin, StrictXnaApiSurfaceLeakCheck_MustFailToCompile, builds an EXCLUDE_FROM_ALL target that deliberately calls an extension and is registered with WILL_FAIL, so the marker itself cannot silently stop working. The rest of the XNA surface is not compiled under strict mode, so a missing CNAEXT elsewhere is caught by review, not by a gate. The convention, with the EXT suffix for engine-layer additions, is stated on the contribution page; the CNAEXT engine layer has its own naming and guard gates (CNAEXT engine layer).
Two review questions follow. Is a new member really an extension, or is it an XNA member CNA is missing? (If FNA has it unchanged, it is XNA and takes no marker.) And does a signature change to an existing XNA member break an XNA-shaped caller? Input additionally has signature-freeze tests, PublicApiInputSignatureFreezeTests.cpp and PublicApiInputCompileTests.cpp, that fail on drift.
Record a deliberate difference
CNA's instructions ask that a divergence taken because XNA outranks FNA be recorded where it is made: a source comment that names the evidence, and a row in plans/plan_bindings_upstream.md. The same file elsewhere says an intentional deviation from an XNA interface mapping belongs in the change description rather than in source comments; the two existing fixes below carry the comment, so follow them and add the description note as well. Two completed cases show the shape, and both are historical, fixed changes rather than open bugs.
- Widescreen threshold.
GraphicsAdapter::getIsWideScreenProperty()once compared the aspect ratio against FNA's 4:3; XNA's IL uses 1.6, strictly greater, so 3:2, 14:9 and 16:10 are not widescreen. The fix inGraphicsAdapter.cppcarries a comment naming both sources and the disagreement. Because the property reads whatever display the host has, no test could pin the constant, so the rule was made reachable as theCNAEXTstaticGraphicsAdapter::IsWideScreenAspectRatioEXT, andGraphicsAdapterTest.IsWideScreenUsesXnasLimitOfOnePointSixExclusiveasserts both sides of 1.6. Expose a rule so a test can pin it independent of host state. - Float-to-integer packing. XNA rounds every float channel to nearest with ties to even, saturates, and packs NaN as 0; FNA truncated in the
Colorconstructors. The fix touchedColor.cpp, the packed-vector headers that convert a float channel, and the shared helperPackedRounding.hpp; it also closed a C++ undefined-behaviour hazard that a C# cast merely leaves unspecified. The evidence is a committed measurement,framework-packing-oracle.json, generated byrun-framework-oracle.shunder Wine, andXnaFrameworkPackingTests.cppreproduces each case and fails if a measured case gains no reproduction. The README says to regenerate that file rather than edit it.
A deviation that is intended to stay belongs on the site's deliberate-deviation list too, because a porter plans around it; that page is updated when the documentation pin advances (Update libcna.com after CNA changes).
Read first
- The public header and its Doxygen block (every public member must have one, and the block is where XNA's documented behaviour is copied in rephrased form), then the implementation in the owning module.
- The owning module's existing tests under
modules/<name>/tests/, mirroring the namespace path, to see which overloads and edge cases are already pinned. CNA's rules require at least one test per public method, per overload, per out-ref variant, per static factory, and both sides of equality,ToStringand hash consistency. - The audit notes for the area, when they exist:
docs/graphicsdevice-fna-audit.md,docs/graphicsresource-fna-audit.md,docs/input-fna-fidelity.md,docs/input-member-parity-matrix.md. They record what was already compared and against which reference. - The consumer side: who calls this member inside CNA (a renderer, the content readers, the C API), found by search before editing.
Make the smallest change
- Preserve what XNA preserves: packed layouts (for example
Coloris AABBGGRR), clamping, integer casts, operator behaviour, overloads, default values, and exception behaviour where practical. Errors XNA callers can observe should be the sharp-runtimeSystem::exception types, as the Reach checks inGraphicsDevice.cppandIndexBuffer.cppdo withSystem::NotSupportedException. - Keep neutral behaviour neutral. If a rule is XNA's, put it in the XNA-shaped class so every renderer inherits it; a renderer-only fix hides a public-API bug on the others (Fix a renderer bug).
- Keep the surface honest. Do not widen a signature "for convenience" in the XNA namespace; add a
CNAEXTmember or aCNA::helper instead. Do not turn an XNAinternalmember into a public C++ one. - No new global state or platform calls in value types and hot paths; platform access goes through the narrow contracts.
Prove it
- Write the test first, from the evidence: an oracle-derived case (a committed measurement under
tests/reference/xna40, an oracle scene, or an FNA dump), not a value produced by running CNA. Include the boundary cases that distinguish XNA from the reference you are moving away from (ties, exact limits, NaN, negative and out-of-range inputs). - Run the owning module's focused target, for example
cmake --build --preset unit-mathorunit-graphicsandctestor the binary with--gtest_filter, then the aggregateCnaTests. The unit preset uses the STUB renderer, so behaviour visible only in pixels needs a rendering renderer (What to test after changing X). - For a visible rendering change, the oracle path: the scene diff for a family that has a reference, or a renderer-neutral parity fixture (second backend). Do not raise a tolerance to make a diff pass; the oracle scripts state that rule.
- For a content-affecting change (math values, packing, readers), the content and pipeline suites, since output bytes and converter tables can move with it.
Check the blast radius
| Ripple | What to check |
|---|---|
| C ABI | Find the route that reaches the member in modules/c-api/src. The color rounding change above reaches C through cna_color_init_vector4 (CnaCApiColor.cpp) with no signature change, yet a binding's expected values can shift. A layout, enum ordinal, error rule or ownership rule change is an ABI question: under the 0.x policy an incompatible change needs a minor increment, release notes and a regenerated baseline (ABI_VERSIONING.md; gates CApiAbiHeaderBaseline, CApiAbiBaseline, CApiDeclaredExports). See Update the C API. |
| Bindings | The maintained bindings pin an ABI (0.21.x) that this snapshot's 0.29.0 does not match, so none of them is evidence for this change; note the behaviour change for their maintainers (C API and bindings architecture). |
| Renderers and content | A value type used by a renderer or a content reader changes what they emit; run the family and content suites that consume it, and the pipeline parity gates if pipeline tables use it. |
| Documentation and tutorials | The per-feature notes under docs/ in the CNA repository, the API comments, the website's guide pages and the tutorials that show the old behaviour, and the deliberate-deviation list when the difference is intended. |
| Samples and demos | Consumer projects are evidence for their own pinned revision, not for this change; re-check them separately. |
| Coverage records | The repository's AUDIT.md and CHECKLIST.md: an API is not marked complete unless its tests are. |
Traps
- Matching FNA and stopping. FNA is the day-to-day reference and the wrong tie-break where measurement disagrees with it.
- Trusting a stale note. Re-measure before fixing anything the upstream-defects plan lists, and check the current source first: its text about the ABI and about a missing
ToStringon the packed-vector types predates this snapshot (the headers now declare it). - A test that reads the host. A property computed from the display, the clock or the environment cannot pin a constant; extract the rule.
- Silent widening. A new overload or optional parameter in the XNA namespace is a public-surface change even when every existing caller still compiles.
- Value produced by CNA as its own oracle. A golden generated from the current implementation proves only that it did not change.
Review checklist
- Which evidence settled the behaviour (documentation, measurement, IL, FNA), and is that recorded in a comment and in the change description?
- Is every non-XNA member
CNAEXT-marked or in theCNAnamespace, with Doxygen on every public member? - Does a test fail before the change for the right reason, with the distinguishing boundary cases?
- Were the C ABI route, ABI baseline and bindings considered, and is the answer stated?
- Were renderers, content output and pipeline tables that consume the value re-tested?
- If the difference is deliberate, is it on the deviation list, or is its absence justified?
- Does the note say what was not run (native Windows, real GPU, the oracle, bindings)?
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Auditing the XNA API surface: the census, xna4-spec and two worked audits — How CNA's 331/331 and 3,627/3,627 API census is produced and read, which reference settles which question, and worked surface audits of Ray and Viewport against XNA's own IL.
- 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.
- Coordinate and composition conventions: handedness, row vectors, depth and clip space — CNA's right-handed basis, row-vector matrices applied in reading order, quaternion products that run the other way, the [0,1] depth range derived three times, clip-space W and the ToColumnMajor bridge.
- 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.
- GraphicsDevice: the shared device contract — Exact device-level behaviour of CNA's GraphicsDevice: construction and windows, bound state objects, viewport and scissor, Clear and Present, bindings, draw calls, readback and extensions.
- Math value types in C++: object layout, equality, hashing and API shape — Which CNA math types carry a vtable, why Color is 24 bytes on 64-bit hosts, the internal vertex stream structs, output-reference aliasing, exact equality, hash and ToString differences, and the split argument exceptions.
- Rectangle, Point and Color: integer geometry and packed colour semantics — Rectangle's half-open edges, touching and empty rules, Point's missing Vector2 bridge and rounding, Color's AABBGGRR word, the 141/140/139 named-colour counts, construction rounding and premultiplied alpha.
- State objects: identity, binding and what reaches the renderer — BlendState, DepthStencilState, RasterizerState and SamplerState in CNA: shared identity, XNA's freeze-on-bind rule, what each renderer hook receives, profile checks and per-family support.
- The Game class: contract, run modes and extension points — Exact semantics of CNA's Game base class: event types, property guards, override points, Run versus RunOneFrame versus Tick, the browser loop, reserved debug keys and the exception boundary.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-001: Plane::Transform(plane, matrix) transposes the inverse in place through a non-aliasing-safe Matrix::Transpose — Plane::Transform with a Matrix passes one object as both source and destination of Matrix::Transpose, which is not aliasing-safe, so any transform whose inverse is not symmetric (a translation, most rotations) yields a w
- CNA-BUG-006: SpriteEffect caches a MatrixTransform parameter that never exists, so OnApply() never sets the sprite projection — SpriteEffect looks up a MatrixTransform parameter its base Effect never creates, so Parameters["MatrixTransform"] is null and OnApply() returns before computing the orthographic projection and half-pixel offset.
- CNA-BUG-027: Vector Clamp, Min and Max resolve inverted ranges and NaN operands differently from XNA — Vector2/3/4::Clamp use std::min(std::max(v, min), max), so max wins when min > max, whereas XNA's vector Clamp and CNA's MathHelper::Clamp let min win; vector and scalar Min/Max also return a different operand from XNA f
- CNA-BUG-064: Curve::Evaluate reads the wrong key for Step continuity away from position 1 and for a Linear post-loop — A Step segment returns the next key's value whenever the evaluated position is at least 1.0 (an absolute constant) instead of at the segment's end, and a Linear post-loop extrapolates with the first key's TangentOut inst
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- User guide
- Verification: deliberate deviations from XNA · Verification: the XNA 4.0 oracle corpus · Contributing: the CNAEXT marker · Migration: known gaps and limitations
- Architecture
- Architecture overview · C API and bindings architecture
- Internals
- Math module internals · GraphicsDevice internals · C API internals
- Maintainer workflow
- Using the XNA oracle as evidence · What to test after changing X · Architectural invariants · Update the C API · Update libcna.com after CNA changes
- Tests and validation
- Test architecture and change recipes
- Reference
- Public header index · Module index