Files
nxnative-easy/docs/agents/06-dev-loop.md
T
seeddsandv0lumehi b3f97efe22 regress.py usage / impact: which games a change can reach (#329)
From PR #329 (stacked on #328): a game's usage manifest, dm/games/<id>/usage.tsv,
written by its owner with regress.py usage (shader features of its chain build,
and the NVN / HLE functions, SVCs and present path of one smoke-route run;
names and counts only), and regress.py impact <base>..<head>, which maps the
functions around the changed lines through the host's callers and the rules of
dm/tools/impact_map.tsv to manifest rows. Manifests for Breath of the Wild,
Tears of the Kingdom and Hades.

Fixed on top of the PR:
- impact checked the head out as a git worktree under the harness root, so it
  stopped (PermissionError on /Volumes/NX) for exactly the contributors it is
  for, who have no games and no harness root. The call graph is now read from
  the git object store at the head revision, and the report is printed and
  written only to --out or an existing harness root.
- A hunk mapped only to the function at its first line: a hunk spanning two
  functions named one. Every added line is mapped now, and a pure deletion maps
  to the function around it (it was moved one line up).
- Functions defined in headers were not in the call graph: the frame step
  (nvn_fstep.h, static inline, called from nvnQueuePresentTexture) reached no
  entry point, so 4acde72 named no game. Headers are scanned, and their
  functions are visible to every file. 4acde72 now names both Zeldas through
  nvnQueuePresentTexture.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
2026-10-27 08:50:52 +08:00

11 KiB

06 — The developer loop: which tool when, and before a pull request

One page for people and agents: the tools this repository has for building, running, debugging, measuring and checking a change, when to use which, and what a pull request needs. It links to where each tool is described instead of repeating it. Game files, keys, builds and runs stay outside the repository (AGENTS.md).

1. Build and run on the Mac

Step Tool Details
Pack a game for the Mac scripts/build_ipa_from_nsp.py <NSP folder> -k <prod.keys> --platform mac Extraction, rewriter, shader split and chain, the Mac host; prints the command that runs the game. A Mac-only build
A double-clickable app the same with --platform mac-app (adds the offline pipeline archive) A Mac app
An iPhone build the same without --platform (an unsigned IPA the user sideloads) The packing script in detail
Rebuild only the host make -C dm/host GAME=<id> Build by hand (Mac)
Rebuild only the shaders DM_GAME=<id> dm/gfx/tools/dmsh_build.py (after a chain change: dmsh_chain.py --header first) the chain procedure in AGENTS.md; 04
Run with scripted input DM_KEYS (button presses at fixed seconds or frames), DM_CTL (presses, screenshots and dumps while it runs) 05, Scripted input

Start Mac runs with the Metal performance HUD (DM_METAL_HUD=1). A new title starts from the checklist and 03.

2. When something is wrong

  1. Search the symptom index for the [RED] line, abort text or what you see; it leads to the reference entry and the games it was found on.
  2. Crashes: the host's CRASH sig=… report, the macOS crash report, lldb scripts, Ghidra / objdump on the rewritten modules: 05.
  3. Hangs and threads: DM_WATCH=<s> / DM_WATCH_STALL=<s> dump every thread (03, Reading the log); DM_THR=1, DM_SVC=1, DM_FS=1 log thread SVCs, memory SVCs and file opens; sample / xctrace name the guest threads during a hitch (05); DM_HLECOUNT=<file> counts every HLE function and SVC the game called.
  4. Guest state without a debugger: DM_PROBE and its watch and heap variants (05).
  5. Wrong picture: DM_NVN_SHOT screenshots, DM_NVN_PASSDUMP (every pass, its attachments, bindings and draw state), DM_NVN_TEXDUMP / DM_NVN_TEXSNAP (render targets as PNG), DM_NVN_CBDUMP (uniform buffers, vertex streams), DM_NVN_HILITE / DM_NVN_DEPTHOVR / DM_NVN_SKIP_FS (isolate a draw), DM_NVN_KEYDUMP and DM_NVN_MAGSCAN on the phone: 04, Diagnostic switches and 05, Intermittent rendering glitches.
  6. Slow frames: the [longframe] lines and the hitch dump (DM_HITCH_DUMP), pipeline compile waits (05), GPU time per pass (DM_NVN_GPUPROF, DM_AB A/B phases, DM_GPU_CAPTURE for Xcode; read with dm/tools/gpuprof.py): 05, GPU time on the device. Attachment traffic: dm/gfx/tools/tbdr_*.py over a pass dump.
  7. Memory: the host's memory line, footprint, vmmap, DM_NVN_ALLOCPROF=1: 05, Memory growth.
  8. Shaders: dm/gfx/tools/shader_regressions.py (shaders that built in the previous chain and fail now), DM_NVN_SHDUMP (programs missing from the map), the offline link check (05).
  9. On the iPhone: switches in Documents/env.txt, results in Documents/log.txt (05, On-device diagnostics).
  10. Game updates and fixed addresses: dm/tools/nso_fnmap.py, nso_syms.py, insn_census.py, sdis.py, nso2elf.py (Tools).

3. Measuring

scripts/bench.py <id>_build --route <name> runs a route of the profile's bench.tsv from a fixed save and gives frame-time percentiles, GPU time and screenshots; variants (--variant) compare two hosts, shader libraries or sets of switches in one invocation, which is the only comparison it supports (A benchmark on the Mac). Power and energy per frame: Power. Pass --lock <file> when other agents or people measure on the same Mac: the lock is held only while a game runs, and heavy builds should wait for it.

4. Checking a change on every game: scripts/regress.py

One chain and one host serve every profile, and each contributor owns only some games (GitHub issue #272). scripts/regress.py compares a baseline revision (by default the merge base with main) with a candidate (by default this checkout, uncommitted files included) on every game the Mac has a --platform mac build of. Its shader count uses the rule of shader_regressions.py, which the NSP script applies to a game you rebuild; the harness adds the other games, the MSL, the pipeline interface and a run. Full description: The regression harness.

Command What runs Cost
regress.py chain Static: each revision's shader build, compared shader by shader (built or failed, MSL / reflection / variant hashes), and the vertex / pixel interface of the pipeline archive's bases (issue #283) nothing when the chain did not change; a changed chain translates every shader again (14 min for Breath of the Wild's 19,592 on an M2 Max)
regress.py check --quick The above plus a short boot per game: the route's quick seconds, no pipeline prebuild about a minute per game and run
regress.py check The whole route with the pipeline prebuild first (DM_PSO_PREBUILD=tool) under a watchdog Breath of the Wild about 5 min, Tears of the Kingdom about 7
regress.py baseline --rev main --runs 2 Records a baseline (a missing one is recorded by check itself); two runs give the noise as check, per run

The routes are dm/games/<id>/smoke.tsv; a game without one takes part in the chain checks only. Add one when you own a game: a fixed save, the presses that load it, screenshots only while the player stands (where a walk ends moves from run to run), and the frame it must reach.

Reading the report (report.md beside report.json in <root>/reports/): one row per game, then the details. PASS and CHANGED (MSL or numbers moved, nothing worse) exit 0; NEW-FAIL (a shader, the interface check, a crash, a hang or the frame that worked in the baseline fails) and REGRESSION (new [RED] lines, a screenshot, frame time, memory or prepare time beyond the tolerances) exit 1; ERROR (the harness could not check a game) exits 2. Failures the baseline has too are listed as known and do not fail the check. Changed MSL is expected after a chain change: say in the pull request why it changed. A screenshot difference may be a fix: look at the run's screenshots in <NSP folder>/<id>-work/regress/runs/ before calling it either. The report holds only numbers, hashes, shader keys and normalized [RED] texts, so it can be pasted as it is.

5. Which games a change can reach: usage manifests and regress.py impact

regress.py checks only the games on your Mac. For the others, each profile can carry a usage manifest, dm/games/<id>/usage.tsv, written by someone who owns the game with python3 scripts/regress.py usage --games <id> --write: the features of its translated shaders (stages, binding kinds, image and buffer atomics, transform feedback, layer and integer outputs, translator notes, texture types), and from one run of its smoke route the NVN functions, HLE functions and SVCs it called, its present path and its profile keys. It holds names and counts only.

python3 scripts/regress.py impact <base>..<head> (or one commit) maps a change to those names: the functions around its changed lines, followed through the host's callers to the NVN entry points and HLE functions they serve, and the rules of dm/tools/impact_map.tsv (a file, or functions in it, to manifest rows: dmsh_atomic.py to the image atomic features, the prepare-step functions of nvn_mtl.m to the games that run it). It lists the games that use what the change touches, those reached only through a broad rule (every game of the API), and the profiles without a manifest as unknown. It reads only git and the committed manifests, so it needs neither game files nor a harness root: the report is printed, and written as impact.md and impact.json to --out or, when there is one, <root>/reports/. Run it before a change to the chain or host, and run regress.py check on the games it names that you have; ask their owners for the others. A changed file no rule maps is listed: add a rule for it.

6. Continuous integration

.github/workflows/tests.yml runs on every pull request: the host's unit tests and ThreadSanitizer targets, the CPU-only graphics tests, the Python tool tests (dm/tools/tests, dm/ios/tests, dm/gfx/tests) including the translator's synthetic-shader tests, Mac host builds for an NVN, a Vulkan and an OpenGL profile, and the iOS source compile and smoke project (Continuous integration). GitHub's Macs have no Metal 4 and no game files, so these stay local: make -C dm/gfx/tests GAME=<id> run (the Metal graphics tests), the translator tests with DMSH_TEST_METAL=1 / DMSH_TEST_GPU=1, profile_guess.py --check, and everything regress.py does.

7. Before you open a pull request

By what the change touches (the largest that applies):

  • Docs only: python3 -m unittest dm/tools/tests/test_docs.py; after a change to docs/reference, python3 dm/tools/docs_symptoms.py.
  • One game's profile or note (dm/games/<id>/): the Python tool tests; a full shader build of that game after a change to its shader inputs; the game on the Mac (or the iPhone) along its route, with scripts/bench.py numbers when the change is about speed; the finding in the game's note (AGENTS.md).
  • The host, the chain or the tools (dm/host, dm/gfx, dm/tools, dm/gfx/tools, dm/ios, scripts): everything above, make -C dm/gfx/tests GAME=<id> run, dmsh_chain.py --header after a chain change, python3 scripts/regress.py impact main..HEAD to see which games it reaches, then python3 scripts/regress.py check over every local game (--quick while iterating, the full run before the pull request), and its report.md pasted into the pull request with a word on every CHANGED, NEW-FAIL or REGRESSION. Name the games the report covers: the others are unchecked.