Choose one subsystem and own it
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. Module sizes come from the generated module index and from line and test-definition counts taken from the snapshot tree; commands were read from CMakePresets.json and cmake/UnitTests.cmake and were not executed. Oracle-backed math tests are described from their own headers, which record comparisons with recorded XNA 4.0 reference data; nothing was re-run.
CNA has 22 framework modules, 21 renderer families, 7 platform backends and 4 audio implementations, so a maintainer cannot own all of it at once. This page gives a procedure for choosing one subsystem and becoming the person who can answer for it, with a check that can fail at every step. It covers how to choose from CNA's own numbers, which subsystems are good first choices and why, the dossier to build, a seven-rung ownership ladder with a concrete proof at each rung, and the checks that move work from an AI assistant to the human who signs the change.
Every number below was computed from the snapshot tree (the module index, plus line counts and a line-start scan for TEST, TEST_F and TEST_P definitions in each module's tests/ directory; over modules/ and tests/ the three macros give 12,610, the site's canonical figure). They describe size and test presence, not quality or pass results. Commands were read from CMakePresets.json and UnitTests.cmake; none was executed. Use Source ownership to look up who owns a behaviour; this page is about becoming that owner.
Selection criteria
Pick on evidence, not on interest. The criteria below are all answerable from pages and files that already exist; the third column says where.
| Criterion | Question | Where CNA records the answer | Prefer for a first subsystem |
|---|---|---|---|
| Blast radius | How far does a one-line change travel? | the dependency matrix and the link edges | A leaf, or a value-like contract whose consumers one search can enumerate. |
| Readiness | Can you answer the construction, normal-path, failure and teardown questions? | readiness questions | One you can trace end to end in the first week. |
| Size and complexity | How many headers, sources and test files? How many lines? | module index and a line count of your own (generated headers such as spirv_shaders.hpp inflate a family) | A directory you can list on one screen; single-digit source files. |
| Test density and roles | How many test definitions, and which roles: focused executable, link-closure probe, CTest name, CI cell? | test target index, the group table in UnitTests.cmake, test roles | A focused executable and tests that assert behaviour. Density is not correctness. |
| Host requirements | Display, GPU, compositor, network, Windows or Wine, submodules, siblings? | platform backends, CMakePresets.json | Nothing beyond the default configure's submodules and the sharp-runtime sibling. |
| Reach | Does the C ABI or a binding expose it? | the C API coverage summary (its out-of-scope table) and the CnaCApi*.cpp files | Know the answer before choosing; either is fine. |
| Risk class | Destructive operations, threads and callbacks, sockets, generated files? | ownership map, thread map | One class, chosen deliberately. |
| Existing documentation | Is there an internals page and a case study? | internals map | Yes: the page is the reference to check your own trace against. |
- List two or three candidates and fill one row of the table below for each.
- Find each in the blast-radius matrix and copy its consumer set and minimum cross-check.
- Answer its readiness questions on paper. Any answer that needs the source is a reading assignment, not a reason to reject.
- Reject a candidate whose host requirement you cannot meet this month, or that carries more than one risk class.
- Commit to one for a fixed period. A second subsystem starts only after rung 5 on the first.
Suggested first subsystems, with reasons
Size is headers, sources and test files from the module index, then lines in include/ and src/ (comments included) and test definitions found by a scan of tests/. Renderer families keep tests as example programs, so their definition counts understate them.
| Candidate | Size | Why it is a good first choice (read from source) | Host and build needs | Watch for |
|---|---|---|---|---|
storageStorage internals | 3 / 3 / 1; 1,087 lines; 14 test definitions | A small module (three source files) with a real authority boundary: one process-static root, and a recursive delete (fs::remove_all in StorageDevice.cpp) that must stay below it. No platform edge (cna_core_headers plus Sharp Runtime only), a link-closure probe (probe_storage.cpp), the focused target CnaStorageTests, C routes in CnaCApiStorage.cpp with CApi_StorageSmoke, and the storage containment case study. | None: a filesystem. The fixture removes <data root>/CnaTestsContent002StorageDevice on teardown, so point XDG_DATA_HOME at a disposable directory first. | One test file is thin evidence for a destructive call. Path containment is shared with content and media, so its reach exceeds the module. |
mathMath internals | 24 / 17 / 27; 13,969 lines; 873 test definitions | The highest test density among these candidates (873 definitions in about 14,000 lines), pure values, no host. MatrixOracleTests.cpp and BoundingSphereOracleTests.cpp compare against recorded XNA 4.0 reference data (matrix-oracle.txt, produced by run-matrix-oracle.sh), so the oracle habit can be practised on a module that needs no renderer. Focused target CnaMathTests; build preset unit-math. | None to read or run the tests: the oracle data is committed. Regenerating it needs the XNA 4.0 reference assemblies, Wine and mcs, per the header of the script. | Wide reach with silent failure: consumers include effects, transforms and the C ABI's value structures, and nothing throws when a convention moves. |
designDesign internals | 16 / 3 / 2; 1,191 lines; 25 test definitions | Depends on cna_math only, is outside the CNA umbrella (linked when named), has a probe and CnaDesignTests, and publishes no C routes. A minimal instance of the module template. | None. | Little runtime consequence, so it teaches structure more than behaviour. |
phonePhone internals | 9 / 3 / 2; 1,061 lines; 18 test definitions | Maps Game lifecycle events onto the Windows Phone shell and runs a loopback HTTP listener thread: a small, bounded exercise in lifecycle and thread ownership. Outside the umbrella; only its tests link it; classified out of C scope by owner decision in the coverage scope table. | Loopback sockets in tests; no display. | No link-closure probe exists for it. Two risk classes at once (lifecycle and threads). |
diagnosticsDiagnostics internals | 2 / 1 / 1; 2,438 lines; 40 test definitions | No CNA link edges, and a compile-time configuration: CNA_DIAGNOSTICS is OFF by default and the test file branches on CNA_DIAGNOSTICS_LEVEL. Good for learning that a green run depends on the configuration. | None. | Its interesting paths exist only at level 1 or 2, so a default configure exercises the smallest subset. |
platform Headlessplatform TerminalHeadless internals Terminal internals | Headless: 2 files, 392 lines. Terminal: 22 files, 4,754 lines, 117 test definitions in 8 files | Compiled into every binary (Terminal on every non-Windows target), so the conformance suite compares them with the selected backend in one process (PlatformConformanceTests.cpp). CI configures both without a display: the cells “Headless + Headless + Null audio” and “Terminal + Software + Null audio” in platform-ci.yml. The platform module can also be built and tested alone through the standalone harness, which its own header says does not need the sharp-runtime sibling. | Headless: none. Terminal: POSIX and a pseudo-terminal (the tests bring their own). | Any change to the contract they implement reaches all seven backends. Terminal owns terminal-mode restoration and signal handling. |
renderers/stubrenderers/headlessStub internals Headless renderer internals | Stub: 289 lines. Headless: 1,866 lines and 7 example programs | The two smallest implementations of IGraphicsRenderer: read StubRenderer.cpp to see how little a family must do, then HEADLESS to see validation and bookkeeping added. The unit preset selects STUB, and multi-renderer-ci.yml builds HEADLESS;SOFTWARE;STUB with CnaTests and no display or GPU. | The default SDL3 platform and its submodules unless you select another platform and CNA_ENABLE_SDL=OFF. | Their tests are example programs registered only when that renderer is selected (Stub_Smoke, Headless_*), so ctest -N in another configuration lists none. Read from source, not configured: the STUB example macro links SDL3::SDL3 unconditionally, where the HEADLESS macro guards it. |
Poor first choices, with numbers
These are not unimportant; they cannot be traced in one pass. c-api has 178,863 lines in include/ and src/, 61 headers and its own test executables, and no CI job builds it. content has 60,962 lines and 1,871 test definitions. graphics has 63,571 lines and 2,906 test definitions. platform as a whole has 63,653 lines across seven backends. audio has 17,975 lines and callback threads. A native backend such as Win32 or Wayland needs its own host to prove anything (Win32, Wayland). Start with a candidate above, and use one of these as the second subsystem once the ladder has worked.
The subsystem dossier
The dossier is one working file per subsystem, kept outside the repository and dated to a snapshot. It is the artifact every later rung is checked against. Fill it in the order below; an item that reads “none” or “unknown” is a valid entry, and an empty one is not.
SUBSYSTEM DOSSIER: <module or family> snapshot: <sha> owner: <name> 1 Contract public headers; XNA or CNAEXT; C route file (CnaCApi*.cpp) or "none" 2 Trace public call -> runtime -> backend or host, as function names 3 Selection factory, registry or option that makes this implementation live 4 CMake target, link edges (PUBLIC/PRIVATE), umbrella member?, gating option, globs 5 Lifetime owner / borrower / registry / cache / ambient, drawn as a graph 6 Generated generated inputs and outputs, or "none" 7 Tests focused target, CTest names, probes, CI cell, skips (77, GTEST_SKIP) 8 Siblings another implementation of the same contract, and what differs 9 Oracle XNA reference data or "none": what it does and does not prove 10 Reach C ABI routes, bindings, blast-radius row 11 Unknowns what could not be established, and the test that would settle it
The table shows what each item asks and fills it in for storage, the candidate with a destructive call. Storage's own page is Storage internals.
| Item | Question | Where to look | Storage, from source |
|---|---|---|---|
| 1 Contract | Which headers are the promise, and who else promises it? | Public headers under include/; the CNAEXT tag; the matching CNA/C/*.h | StorageDevice.hpp (XNA shape) with the CNAEXT SetAppNameEXT; C header storage.h |
| 2 Trace | What runs between the call and the host? | Implementation in src/; the module's internals page | Selector and open-container calls complete on the caller's thread; the root comes from EnsureStorageRoot |
| 3 Selection | What makes this implementation the live one? | source ownership: factory, registry or CMake selection | Nothing selects it: storage has no platform edge, and an empty answer is a result |
| 4 CMake | What does the module link, and who links it? | The module's CMakeLists.txt; modules/CMakeLists.txt for the umbrella list | cna_storage; PUBLIC cna_core_headers and Sharp Runtime Core.Base IO Runtime Threading; umbrella part; probe ModuleLinkClosure_probe_storage |
| 5 Lifetime | Who may still use the object when it dies? | ownership vocabulary | StorageContainer keeps a borrowed const StorageDevice* |
| 6 Generated | Does any input or output come from a generator? | generated versus handwritten | None |
| 7 Tests | What fails, in which configuration, and what skips? | test target index | One file, StorageDeviceTests.cpp; part of CnaTests; focused CnaStorageTests; no dedicated workflow |
| 8 Siblings | What implements the same contract elsewhere? | Other implementations of the interface; other callers of the shared helper | No second implementation; the comparison partners are the other callers of PathContainment (content, content-pipeline, media) |
| 9 Oracle | Is there recorded XNA behaviour? | using the XNA oracle | None found under tests/reference/xna40 (searched by name) |
| 10 Reach | What else moves when this moves? | storage boundary | CnaCApiStorage.cpp; CNA_GamerServices links storage |
The ownership ladder
Each rung has a deliverable and a proof. The proof is a check that can fail, and a failed proof sends you back one rung; time spent is not evidence. Rungs 1 to 3 match the first two stages of First 10, 50 and 100 hours; rungs 4 to 6 use the method in How to understand code you did not write and the rehearsals in Worked human changes.
| Rung | You do | You hand in | Proof of understanding, and when it fails |
|---|---|---|---|
| 1 Read | Read the module's CMakeLists.txt, public headers, implementation and tests in that order, then its internals page. | Dossier items 1, 3, 4 and 7, each a file list with a one-line role. | Closed-book listing: name every source file of the module and its role, then compare with the module index or git ls-files. Fails on any file or directory you cannot place. |
| 2 Trace | Follow one public call to the host, and one object from construction to destruction, using function names. | Dossier items 2 and 5: the call trace and the ownership graph. | Redraw the ownership graph from memory and diff it against the source declaration order and the ownership map. Fails on a missing borrower, registry or cache. |
| 3 Reproduce | Configure a build, list the subsystem's tests, run one focused case, and explain each skip. | A reproduction record: the selected axes, the exact commands, ctest -N output, skipped cases and why. | Write the expected test count and the expected skips first, then compare with ctest -N and --gtest_list_tests. Fails when a skip is unexplained or a test you named is not in this configuration. |
| 4 Test | Write one characterization test for behaviour you could only read, before any change. Then make deliberate one-line breaks on a scratch branch. | The test, plus a table of breaks: predicted failing tests, actual failing tests. | Predict before running. Fails when a break you expected to be caught fails nothing (a test gap) or fails something you did not predict (a missed edge). |
| 5 Patch | Make the smallest change, with the invariant it must preserve written before the edit. | The diff and an investigation record naming the axes, owner, order, other implementations and validation run. | Predict the outcome of a lifecycle or ordering change (which destructor runs when, which callback still fires) and confirm it with a focused test or a sanitizer run. Fails when the prediction and the result differ and you cannot say why. |
| 6 Review | Review a diff in your subsystem written by someone else, human or AI. | A review memo in the style of worked changes: owners, borrowed pointers, backend assumptions, untouched variants. | Explain the diff without a summary: only the diff and the source are open, and a second reader can tie every claim to a file and function. Fails on any claim that cannot be so tied. |
| 7 Teach | Hand the subsystem to someone else, or correct the Development page where your reading disagrees with it. | A source-pinned correction, or a walkthrough with the dossier as the script. | The learner passes rungs 1 and 2 against your dossier, and you answer their “what breaks if…” questions from source without notes. Fails when you have to look something up that the dossier should hold. |
Rungs 3 and 4 can be destructive for storage. The unit-test fixture recursively removes CnaTestsContent002StorageDevice under the per-user data root, and a deliberately weakened containment check could follow a symlink out of it. Point XDG_DATA_HOME (or LOCALAPPDATA, or HOME, whichever the host uses) at a disposable directory holding an outside sentinel file before running any storage test, and never run a weakened build against real data (storage is a destructive authority boundary). Math, design and diagnostics have no such hazard.
Rung 3 in commands
The presets are the ones on the curriculum page; the focused target names come from the group table in UnitTests.cmake. Focused executables are built on request and are not registered with CTest, so run them directly from the repository root, which is the working directory CTest gives the discovered cases.
cmake --preset unit # STUB renderer, tests on, examples off
cmake --build cmake-build-unit --target CnaStorageTests
XDG_DATA_HOME=/path/to/disposable ./cmake-build-unit/CnaStorageTests --gtest_list_tests
ctest --test-dir cmake-build-unit -N # the whole configuration's inventory
cmake --build --preset unit-math # math instead: builds CnaMathTests
Checks that replace AI dependence
An assistant is useful for finding files and for drafting; it is not a source of ownership, because ownership is a claim about consequences that only a person can be held to. The aim is not to stop using one. It is to make every use pass through a step that a person performs and can be checked on. Run these at each rung, and keep the results in the dossier.
| Check | How | Pass | A failure means |
|---|---|---|---|
| Blind lookup drill | Choose ten behaviours from the lookup table. Without the page, name owner, header, implementation and test file, then diff. | 9 of 10 for your subsystem; 6 of 10 elsewhere. | Your map of the repository is borrowed, not held. |
| Redraw and diff | Draw the ownership graph on paper, then compare it with the source and the ownership map. | No missing edge; every borrowed pointer labelled. | You are trusting names such as “manager”. |
| Predict, then run | Before a lifecycle, ordering or option change, write which tests fail and which callbacks fire. Then run the focused target. | Prediction equals result, or the difference is explained. | The mental model is wrong somewhere. |
| Mutation table | Break one line at a time on a scratch branch and record predicted and actual failures. | Every break is caught, or the gap is written down as a missing test. | The tests are trusted more than they earn. |
| Diff without a summary | Read a diff with no description. Explain owners, invalidations, capability assumptions, assertions and configuration changes. | A second reader can verify each sentence against source. | Review is delegated to whoever wrote the summary. |
| Claim ledger | For each statement from an AI answer that you rely on, write the file and function you opened yourself. Unopened claims stay marked unverified. | No unverified claim supports a decision. | The answer is being used as evidence. |
| Opposite argument | Ask for the strongest case that your fix is wrong, then find the source line that settles it. | You can point at the deciding line. | You cannot yet tell a good argument from a fluent one. |
| Tools-only day | Do a rung's work with only rg, the module index, CMake and the tests. | The rung's deliverable is unchanged. | The search step, not the reasoning, was the dependency. |
The pass thresholds are proposals to adjust for the size of the subsystem, not CNA policy. Which uses stay open changes with the rung. At rungs 1 and 2 an assistant may list candidate files, but every conclusion must come from files you opened. At rungs 3 and 4 it may draft tests, and you write the prediction before it runs. At rung 5 it may propose a patch, but you write the invariant and the predicted effects first. At rungs 6 and 7 no summary substitutes for reading: the review memo and the correction to the pages are yours.
Worked example: the first four rungs on math
Math has no host requirement and no destructive call, so the rungs can be rehearsed without care for side effects. Rung 1. Read math/CMakeLists.txt, then Matrix.hpp, Matrix.cpp and MatrixTests.cpp; the closed-book listing is the 17 source files and what each type owns. Rung 2. Trace Matrix::Multiply and one consumer outside math. The matrix row says a change here can alter rendering silently, so also record where the value structures cross into the C ABI (CnaCApiMatrix.cpp). Rung 3. Build CnaMathTests, write down the expected number of discovered tests before --gtest_list_tests, then read the header of MatrixOracleTests.cpp. It records that XNA accumulates dot products wider than float and narrows once, and that a float accumulation disagreed with XNA in entries a rotation leaves near zero; explain in your own words why the disagreement shows up only there. Rung 4. On a scratch branch negate the sine term in Matrix::CreateRotationZ, predict whether the failures stay inside CnaMathTests or also reach CnaGraphicsTests, run both, and record the difference as the first row of the mutation table. A characterization test needs no CMake edit: the test glob in UnitTests.cmake picks up a new file under modules/math/tests/ at the next build, because the glob is CONFIGURE_DEPENDS.
Scope, exit and handover
- Scope guard. Owning a subsystem includes its tests, its CMake, its C route file if it has one, and the Development page that describes it. It does not include a backend you have not read, and a patch that leaves the module changes the blast radius; return to the matrix first.
- Uncertainty is part of the deliverable. Record what the tests do not prove, using the register in Known uncertainty and history. Renderer-specific and host-specific behaviour stays unproven until run on that host.
- Review of AI-written areas. The method for large generated changes is on Review large AI-generated areas without trusting them; oracle-backed claims are covered on Using the XNA oracle as evidence.
- Handover. The dossier, the mutation table and the last review memo are what a successor reads first. If they cannot rebuild your understanding, rung 7 is not done.
- Re-pin. Before trusting a dossier for a new patch, compare your branch with the snapshot and re-audit every changed path it depends on (how these pages are pinned).
Related pages
The same subsystem is explained at four altitudes. These are the neighbouring pages at each one.
- Architecture
- Architecture overview
- Maintainer workflow
- Blast radius and readiness · First 10, 50 and 100 hours · How to understand code you did not write · Source ownership
- Tests and validation
- What to test after changing X · Test architecture and change recipes
- Reference
- Module index · Test target index