I need to change public XNA behavior

CNA snapshot 009d40f5  ·  Development › Maintainer Handbook  ·  source links pinned to 009d40f5

✓

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 areaModule and header rootImplementation
Math and value types (Color, Vector2/3/4, Matrix, Rectangle, Curve, bounding volumes, MathHelper)mathmodules/math/src, one file per type
Game, GameTime, components, GraphicsDeviceManager, GameWindow, TitleContainerruntimemodules/runtime/src (flat)
Graphics: GraphicsDevice, resources, states, stock effects, SpriteBatch, packed vectorsgraphicsmodules/graphics/src/Xna; native work is in modules/renderers
Input, audio, media, storage, gamer services, net, contentthe 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::Phonedesign, content-pipeline, devices, phonedesign, 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.

EvidenceWhat it can settleWhat it cannotWhere it lives
Microsoft's XNA 4.0 documentation and the shipped assembly metadataThe 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 runtimeNumeric 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 sourceThe 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 deviationsDifferences 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 in GraphicsAdapter.cpp carries 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 the CNAEXT static GraphicsAdapter::IsWideScreenAspectRatioEXT, and GraphicsAdapterTest.IsWideScreenUsesXnasLimitOfOnePointSixExclusive asserts 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 Color constructors. The fix touched Color.cpp, the packed-vector headers that convert a float channel, and the shared helper PackedRounding.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 by run-framework-oracle.sh under Wine, and XnaFrameworkPackingTests.cpp reproduces 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

  1. 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.
  2. 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, ToString and hash consistency.
  3. 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.
  4. 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 Color is AABBGGRR), clamping, integer casts, operator behaviour, overloads, default values, and exception behaviour where practical. Errors XNA callers can observe should be the sharp-runtime System:: exception types, as the Reach checks in GraphicsDevice.cpp and IndexBuffer.cpp do with System::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 CNAEXT member or a CNA:: helper instead. Do not turn an XNA internal member 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

  1. 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).
  2. Run the owning module's focused target, for example cmake --build --preset unit-math or unit-graphics and ctest or the binary with --gtest_filter, then the aggregate CnaTests. The unit preset uses the STUB renderer, so behaviour visible only in pixels needs a rendering renderer (What to test after changing X).
  3. 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.
  4. 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

RippleWhat to check
C ABIFind 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.
BindingsThe 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 contentA 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 tutorialsThe 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 demosConsumer projects are evidence for their own pinned revision, not for this change; re-check them separately.
Coverage recordsThe 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 ToString on 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 the CNA namespace, 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)?

The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.