How to understand code you did not write

CNA snapshot c1c316b9  ·  Development › Human Takeover  ·  source links pinned to c1c316b9

✓

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. The three traces were read from the snapshot's source and the named tests exist; nothing was executed. Binding behaviour is quoted from the binding repository at the stated commit, not from TARGET.

Start from a behaviour and produce two artifacts before editing: a call trace (how execution arrives and leaves) and an ownership trace (who creates, retains and destroys each object). A definition search alone gives neither. The procedure below is deliberately mechanical so a maintainer can repeat it without asking an AI to summarize a directory; three real traces through CNA at snapshot c1c316b9 show it applied.

Twelve source checks

  1. Locate the declaration. Search the symbol, for example rg -n 'SymbolName' modules tests cmake tools. Distinguish a public header under modules/<name>/include/, a cross-module internal contract under include/CNA/Internal/, and a module-private header under src/. XNA-facing types live under include/Microsoft/Xna/Framework/; CNA-specific ones under include/CNA/.
  2. Identify the physical owner. Read the module's CMakeLists.txt. Note whether the file is always built, optional (for example net and gamer-services behind CNA_ENABLE_NET, video-ffmpeg behind FFmpeg detection, c-api behind CNA_BUILD_C_API), part of a renderer family, generated in the build tree (the renderer registry), or a test-only object group. Because modules/CMakeLists.txt rejects any translation unit outside a declared module tree, the directory is the owner.
  3. Read the implementation, not comments alone. Follow the body and its helper calls. For a virtual method, find every implementation; for a factory, find the registry and the create path (for renderers: descriptor → generated registry → GraphicsDevice::resolveRenderer).
  4. Find construction and destruction. Search constructors, factory calls, std::make_unique/std::make_shared, Dispose, destructors and release functions. Mark borrowed raw pointers separately from owners.
  5. Find callers and mutation points. Search method calls, event subscriptions (+=, .Add( on CNA event handlers), callbacks and writes to the relevant state fields. A getter can be downstream of an event pump or a cached per-frame snapshot.
  6. Trace failure. Locate guards, exceptions, result codes, rollback and half-constructed states. Ask whether a failed attempt can be retried and what state it leaves behind.
  7. Find tests. Search the symbol and the behaviour's name in module-local tests/, root tests/ and the module's examples/, then read the assertions and fixture setup. An example executable registered with cna_register_renderer_test may be the oracle rather than a GoogleTest case.
  8. Find build registration. Read the CMake selectors, target linkage, the generated registry and the CTest registration (cmake/UnitTests.cmake, cmake/TestHelpers.cmake, the module's examples/CMakeLists.txt). A file existing at the snapshot is not proof that it compiles in your configuration.
  9. Compare variants. Search every platform and renderer implementation of the same contract; separate the neutral guarantee from the chosen backend's behaviour.
  10. Check C ABI reach. If public API or lifetime changes, search modules/c-api/include/CNA/C and modules/c-api/src, then the external bindings' revision and ABI-version gates. Do not assume direct C++ parity is already exported.
  11. Write a one-page trace. Include the selected CMake axes, entry point, object owner, state transition, exit, error path, tests and other backends. Mark every uncertain edge.
  12. Only then edit. Add a failing test where possible, make the narrowest change that preserves the identified invariant, and run the change→test route.
⚠

Never silently combine revisions. The Development pages are pinned to c1c316b9. When your working checkout has moved, read the pinned state with git show c1c316b9:<path> or git grep -n '<pattern>' c1c316b9 -- <path>, and for a new patch inspect the actual branch as well (git diff c1c316b9 HEAD -- <path>). A trace that mixes a pinned explanation with a different implementation revision is not reviewable. The maintenance & pin policy explains how these pages move to a new snapshot.

Example: what does GraphicsDeviceManager::ApplyChanges actually change?

A declaration search lands in the runtime module's public header (GraphicsDeviceManager.hpp), not in graphics or a renderer. The owning implementation is modules/runtime/src/GraphicsDeviceManager.cpp. The manager is normally caller-owned (a member of the derived game), registers itself with Game's service container as both IGraphicsDeviceManager and IGraphicsDeviceService — a borrowed pointer, not an ownership transfer — and applies preferences to the GraphicsDevice that Game already constructed. Its constructor deliberately does not call ApplyChanges; Game::DoInitialize calls CreateDevice() instead, and that is where the manager subscribes to the device's Disposing, DeviceResetting and DeviceReset events.

Follow ApplyChanges: it returns immediately when no preference changed (prefsChanged_, useResizedBackBuffer_), otherwise builds a GraphicsDeviceInformation, updates the window through BeginScreenDeviceChange/EndScreenDeviceChange, and calls applyToExistingRenderer, which sets the graphics profile and presentation mode and then calls GraphicsDevice::Reset(pp, adapter) in GraphicsDevice.cpp. Reset raises DeviceResetting, unbinds any render targets, applies the window size, resizes the renderer's virtual resolution, updates presentation formats, re-applies the multisample count and swap interval, updates the viewport, and raises DeviceReset; if the renderer resize throws, the presentation bookkeeping is rolled back before the original exception is rethrown. The change is therefore not simply a window setter: it may alter back-buffer extent, presentation mode, state and renderer resources. Construction-time selection has already happened — a reset is an in-place reconfiguration, not a new call to a renderer factory.

Renderer-detected loss follows a different road: createRenderer() gives the renderer a deviceEventCallback that raises DeviceLost, DeviceResetting and DeviceReset on the device, and the manager forwards them to its own listeners. At this snapshot the families whose sources use that callback are DirectX 9, DirectX 11, Vulkan and WebGPU.

Now inspect GraphicsDeviceManagerTests.cpp: ApplyChangesRaisesResettingAndResetExactlyOnce, RendererDetectedDeviceLostIsForwardedToManagerListeners, ForwardedDeviceEventsReportTheManagerAsSender and RepeatedDisposeDoesNotReraiseDeviceDisposing establish the observable events. Search renderer implementations of reset and presentation before changing neutral logic. The resulting trace should distinguish manager ownership, device ownership, the renderer's native reset and the listeners that observe it; a green unit test under the STUB renderer does not prove a real swapchain resize on any backend.

Example: a terminal key appears but never releases

A public keyboard query is downstream of two calls that feed one shared TerminalInputDecoder: TerminalPlatform::PollEvents (which pumps the decoder and drains its events into the frame's batch) and TerminalKeyboard::Update (which Game::PollEvents calls once per frame to advance the snapshot). Read TerminalCapabilityProbe.cpp first: did the terminal advertise the Kitty keyboard protocol? TerminalPlatform.cpp creates the session controller, decoder, keyboard and mouse lazily in EnsureCapabilitiesDetected (only when the terminal can be queried) and reports exactKeyboardState as exactly the detected Kitty support.

In TerminalKeyboard.cpp, Kitty press, repeat and release events update the held state directly; legacy byte sequences create a press with a timed synthetic release (ApplySyntheticPress, ExpireSyntheticKeys) that a repeat pushes back. The inexactness is advertised through exactKeyboardState=false; "fixing" a legacy-terminal release by claiming exact state would break the platform contract. TerminalKeyboardTests.cpp covers the cases — LegacyPressGetsOneTimedSyntheticRelease, LegacyRepeatRefreshesTheReleaseDeadline, SplitSequencesWaitForTheirFinalByte, SnapshotRemainsHeldAcrossRepeatAndClearsOnlyOnRealRelease, EventPumpAndSnapshotShareOneReadWithoutLosingEvents, PlatformAdvertisesExactStateOnlyAfterKittyWasDetected — and the Terminal internals page adds session ownership. If the symptom occurs only after presenter creation, inspect the session rebuild in TerminalSessionController.cpp (reconfiguration rebuilds the session and bumps GetGeneration()) and KeyboardAndPresenterShareTheOneProcessSession, not just key parsing.

Example: a binding reports an invalid SoundEffect handle

Begin at the binding's foreign wrapper, then the cna_sound_effect_create_instance or destroy routes in CnaCApiAudio.cpp, whose caller contract is in CNA/C/audio.h. HandleRegistry (CnaCApiDetail.hpp, CnaCApiDetail.cpp) decodes a 64-bit handle into a slot index and a generation, and every typed lookup checks three things in order: the generation (a released and reused slot fails), the object kind (a handle of another kind returns CNA_RESULT_INVALID_HANDLE) and the creating thread (another thread gets CNA_RESULT_THREAD). So "invalid handle" already narrows the cause to a stale or wrong-kind handle; a threading mistake reports differently (or, when the game has enabled the opt-in foreign-thread queue, cna_game_set_foreign_thread_calls_ext, is run on the game thread instead of being refused). The registry itself is covered by HandleRegistryTest.cpp.

A child instance retains its parent: the instance resource holds a shared pointer to the SoundEffect resource, and cna_sound_effect_destroy still refuses with CNA_RESULT_INVALID_STATE while any C-created instance exists, so the public contract is child-first release even though the native parent could not dangle. Inspect the header and the pure-C test AudioSoundEffectSmoke.c (CTest CApi_AudioSoundEffectSmoke) before blaming the C++ SoundEffect mixer.

If the foreign wrapper targets a different ABI minor from the one native CNA reports (0.46.0 at this snapshot), diagnose version policy first. Nine external bindings have existed over time: one maintained (C#) and eight archived in CNA Lab. The maintained one refuses any other minor outright: the C# binding's policy file, eng/cna-native-abi-policy.json, admits only exact reviewed entries; read at 860f92c (before the repository was renamed from cna-cs to cna-dotnet) it named consumer ABI 0.44.0, and CNA records that cna-dotnet 8219a6c moved it to exactly 0.46.0. The archived bindings stopped at ABI 0.21–0.23 (Common Lisp) or 0.35.0 (the other seven), so none of them is current for this snapshot. The C# internals page and the archived Java and Python pages describe those bindings at their recorded revisions; none of them establishes compatibility with this snapshot. The bindings boundary lists each binding's gate.

A useful investigation record

Symptom and exact reproduction: __________________________________
CNA commit (vs c1c316b9) + CNA_PLATFORM / CNA_AUDIO_PLATFORM /
  CNA_GRAPHICS_RENDERER(S) / toolchain / CNA_ENABLE_SDL: ___________
Active renderer at run time (GetActive, fallback history): _______
Public entry → implementation → selected backend: _____________
Owner → borrowers → destruction sequence: ______________________
State written, read and invalidated: _____________________________
Invariant and failure / rollback path: ___________________________
Focused test and negative case (passed / failed / SKIPPED): ______
Other backends, C ABI and bindings, CI / live-host gaps: _________

This record is more useful in review than an unexplained list of modified files. If an edge cannot be established from source, label it unknown and make validation target that uncertainty. The review memo on the worked-changes page is the same record written after the patch.

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