Files
nxnative-easy/docs/agents/06-dev-loop.md
T
seeddsandv0lumehi 6f58a7d4f6 Add cross-game regression harness and prevent false passes (#328)
Compare revision shader builds and pipeline interfaces across local games,
and run available smoke routes with crash, hang, timing and image checks.
Add the developer loop and share the measuring lock with bench.py.

Keep all local games in static checks and honor --no-smoke. Include shader
bytes and fallback pairs in cache keys, recheck interfaces on reuse, and
reuse smoke baselines only for matching inputs and run settings. Detect
failed prebuilds, missing or intermittently bad screenshots, and Vulkan
build failures even when no MSL was emitted. Keep game-derived files and
writable profile copies inside each game's NSP work folder.

Validation: tool tests and the lint ratchet pass. Full shader rebuilds and
gameplay comparisons remain unverified; the 26 discovered Mac builds have
no smoke routes in this checkout.

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

9.2 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. 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.

6. 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, 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.