Files
nxnative-easy/AGENTS.md
T
seedds 7d368c4249 Ghost Trick: Phantom Detective (010029B018432000, base game 1.0.0 = v0, Capcom's RE Engine on SDK 13.3.0, with the Bonus Content add-on): game profile, and finalized pool buffers no longer kept by a guest thread's autorelease pool (every game). Plays on the iPhone (IPA 3.0 GB, user)
The profile is the NSP script's provisional one with tex_flush_upload and tex_dirty_upload: RE Engine writes its UI textures (ASTC) into CPU-visible pools in place, as Monster Hunter Rise does, and without both keys the language screen's background and buttons were magenta (with tex_flush_upload alone they still were). 7,744 shaders from the PAK's SDF definitions and main; 7,739 build. The other 5 are compute shaders with atomics on a 3D image, which SPIRV-Cross indexes with an int3, as Octopath Traveler 0's 16; none was requested on the routes below. PR #138 (Nishikori-Yui) built them by flattening the coordinate in dmsh_build.py, a chain change that would rebuild every game's libraries, so it is left out until a run requests one (the game note). PR #138's profile is the same as this one (the same keys and shader tables).

Host fix: the memory line's "freed but alive" count only grew in most games (Animal Crossing: New Horizons 3,113 after 27,900 frames, Pokémon Sword 2,050, Super Smash Bros. Ultimate 1,279; Ghost Trick 125 to 146 buffers, 6.6 to 8.7 GB of zero-copy buffers, so GPU allocated read 9.9 to 11.7 GB against 2.2 GB resident). A temporary census of every host table that can hold a texture over a pool buffer found no holder: mtl_pool_del runs on the guest thread that finalizes the pool, whose implicit autorelease pool never drains, and adding the buffer to the weak table g_dead_pools left it autoreleased there. containsObject: / removeObject: on a weak NSHashTable do the same (a standalone test), and res_add / res_remove call them on guest threads too. mtl_pool_del, res_add_at and res_remove now have their own @autoreleasepool. Ghost Trick: 0 freed but alive, GPU allocated 1.7 GB instead of 8.3 GB 300 frames into the boot and 2.9 GB in Chapter 1, resident memory, frame times and pictures unchanged; Pokémon Sword: 8 to 0 at the title after 90 s, the same frame pacing.

On the Mac: the opening movies, the language screen, the first-boot notices, save creation, the Bonus Content notices and Chapter 1's opening in the junkyard (the Ghost World, the first trick), and a second boot that loads the save, at 60 fps (frame interval p99 about 17 to 19 ms), about 2.3 GB resident, no red line; the Wwise sound plays (a muted run with DM_AUDIO_DUMP). Checked: the tool tests (57, the docs check among them), t_nvn (124 checks), a boot of the rebased host. README (games gallery, a Mac picture of the junkyard), AGENTS.md's count (sixty-two: the gallery's cells), the game note, the games table, docs/reference/nvn-objects.md and the symptom index say so
2026-10-25 17:44:41 +08:00

7.9 KiB

Notes for agents

  • This repository runs Nintendo Switch games natively on Apple platforms: the game's own code runs, rewritten only where Apple's kernel requires it (dm/tools/patch_nso.py), on a host that answers Horizon's SVCs and the SDK's services (dm/host) and implements NVN, Vulkan and NVIDIA's desktop OpenGL on Metal (dm/gfx/nvn, dm/gfx/vk, dm/gfx/gl). Sixty-two games are playable on the iPhone; the games table lists each with its build, SDK, engine and graphics API, and links its note, dm/games/<id>/README.md: what its profile needs and why, what was found on it, how to drive it on the Mac, what is open. README.md is the player-facing page (games, packing, playing); docs/development.md has the internals, the script's options, building by hand, prewarm lists and power.

  • Where to start:

    Task Read first
    A crash, abort, hang, [RED] line or wrong picture docs/agents/symptoms.md: search it for the text in the log or what you see; it leads to the entry in docs/reference and the games it was found on. Recipes (crash reports, lldb, probes, dumps) are in 05-debugging.md
    A game that does not reach its frame loop 03-bringup.md (the run loop, reading the log), then the symptom index
    Rendering or shaders 04-graphics.md, then rendering, shaders and NVN objects
    Work on one game, or a change to its profile its note, dm/games/<id>/README.md, then dm/games/README.md for the keys
    Another Switch title the NSP script (below), then docs/agents
    Packing, building, prewarm lists, power docs/development.md
  • Where a new finding goes, once: a fact about one game (its passes, shader keys, measurements, why its profile needs a key or a cut, its status) in that game's note, in the section it belongs to and marked current or resolved; a general mechanism (a host or tool change that holds for other games) as an entry in its docs/reference/<subsystem>.md section (Symptom, Cause, Fix, Found on with the game's note linked), then python3 dm/tools/docs_symptoms.py to update the symptom index, and a line under the game note's "Found on this game". Link to a finding instead of repeating it. Adding a game takes its note, its row in the games table and its line in README's games list; this file changes only when a rule does (and the count above). dm/tools/tests/test_docs.py checks the links and anchors, the notes and their table rows, the symptom index, and the length of this file (1,500 words) and of each guide in docs/agents (6,000).

  • Each game's constants and shader tables form a game profile in dm/games/<id>/, where <id> is the base game's title ID: game.tsv plus the tables (and patches.py when the rewriter has patches for its builds). Mario's profile is 010015100B514000. Tools pick the profile with DM_GAME; make -C dm/host and build_ipa.sh pick it with GAME. There is no default game: set it every time (the tools stop and list the profiles otherwise). Put per-game values in the profile, not in code (dm/games/README.md).

  • Prefer general optimizations and fixes to game-specific rules. Performance work (frame pacing, power, caches, load times) counts here as much as correctness. First look for a change that holds for every game: Horizon / NVN / Vulkan semantics, or what the binary, its SDK version, its imports or its files show. Use a game-specific rule (a profile key, a rewriter cut, a build-ID gate, a per-game branch in the host) only when it is absolutely necessary, that is, when no general rule can do it.

  • Per-build knowledge is keyed by the NSO build ID: rewriter cuts, observation gates and frame-step stubs in the profile's patches.py (a game's own modules) or dm/tools/sdk_patches.py (Nintendo's sdk modules, which several games ship), which dm/tools/patch_nso.py (the general rewriter) loads, the profile's build rows (the builds the game was brought up on, and their display versions; the NSP script packs any other version from a derived profile, without those builds' fixes), and host behaviour that uses fixed module addresses checks module_t.build_id (from fixups.tsv; dm_main_build()). When a new game build appears, carry addresses over with dm/tools/nso_fnmap.py and check them by disassembly; never let fixed addresses run on a build they were not found in.

  • To analyse or port another Switch title, first run scripts/build_ipa_from_nsp.py on its NSP folder. With no matching profile, it writes a survey to <NSP folder>/<title ID>_survey/survey.md, then a provisional profile guessed from the game files (dm/games/profile_guess.py; <NSP folder>/<title ID>_build/profile/<title ID>/, found by the tools through DM_GAMES_DIR), with a short heap check on the Mac when the game has virtual address memory, and a best-effort IPA that may not boot. Then follow docs/agents (extraction, binary survey, host bring-up, graphics specifics, debugging recipes; the symptom index for each failure) and add a profile with its note.

  • Scripts the user runs live in scripts/ at the repository root. Each one runs from a terminal (non-zero exit on error). build_ipa_from_nsp.py also runs in Spyder (F5, configured by the settings block at its top; command-line options override those settings); the others need not. Tools that only agents or other scripts call stay under dm/.

  • Game files, keys and anything extracted from them live in the game's NSP folder, never in this repository and never in the home folder (it is nearly full): the work folder <NSP folder>/<id>-work for extraction, runs and analysis, beside the NSP script's <id>_build/. Tool venvs and other large scratch data go on that drive too. Do not print key values.

  • Language: everything is in English: README.md, docs, code comments, log messages, script output and other files. Red log lines (something unimplemented or wrong) start with [RED] . Text that must match a game's own Chinese data is written as escapes ("\xE8\xB5\xB7"), never as characters.

  • The shader translation chain has one version for every game, DMSH_CHAIN in dm/gfx/nvn/dmsh_chain.h, which the host is compiled with and loads only libraries of. It covers the chain's tools: dm/gfx/tools/dmsh_* (dmsh_build.py, dmsh_chain.py, dmsh_fetch.py, dmsh_vin.py, dmsh_geometry.py, dmsh_facts.py), Program.cs, ios_metal.py and dmsh_chain.lock. A profile's own inputs (shader_map.tsv, shader_facts.tsv, shader_pairs.tsv, bf_want.tsv, its shader_* / nvn_* keys) are not part of it: they are in each shader's cache key, and their digest stamps the libraries (b-<V>/inputs.tsv). The procedure:

    1. After changing a chain tool, run dmsh_chain.py --header once and commit dm/gfx/nvn/dmsh_chain.h with the change (dm/tools/tests/test_nsp_chain.py fails on a stale one).
    2. After that, or after changing a profile's inputs, rebuild that profile's shaders: a full dmsh_build.py run (Mac and, with IOSOUT, iOS) stamps its libraries and writes the profile's c1_full.tsv and cb_extent.tsv from its Mac outputs (git ignores them; the host takes them only beside the stamp they name). Give each game its own output directory.
    3. Rebuild the host.

    The translator is dm/gfx/tools/dmsh_xlate_bin, which matches dmsh_chain.lock; a fresh dotnet publish does not.

  • iOS builds are unsigned IPAs that the user sideloads and tests; diagnostics come back as the app's Documents/log.txt. Do not sign or install builds on the user's devices.

  • Commit only when asked, with git -c user.name=seedds -c user.email=seedds@gmail.com commit.