How to understand code you did not write

CNA snapshot 009d40f5  ·  Development › Human Takeover  ·  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. 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 009d40f5 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 009d40f5. When your working checkout has moved, read the pinned state with git show 009d40f5:<path> or git grep -n '<pattern>' 009d40f5 -- <path>, and for a new patch inspect the actual branch as well (git diff 009d40f5 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 Direct2D, DirectX 9/11/12, 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. 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 ABI 0.21.x while native CNA reports 0.29.0, diagnose version policy first. All eight public bindings target 0.21.x, and several refuse any other minor outright — for example the C# binding's policy at cna-cs e2239099 eng/cna-native-abi-policy.json admits only exact reviewed entries (consumer ABI 0.21.0). The C#, Java and Python internals 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 009d40f5) + 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.