# Dragon Warrior IV (USA) — Recompiled (NESRecomp port)
Static recompilation of Dragon Warrior IV (NES) using [mstan/nesrecomp](https://github.com/mstan/nesrecomp), cycle backend. Also uses [RetroPortingToolKit/recomp-ui](https://github.com/RetroPortingToolKit/recomp-ui) console-agnostic launcher and in-game settings UI for static recompilation game ports. Includes a HD Graphics Pack and supports Game Genie cheat codes. HD graphics generated by AI (OpenAI GPT-Image-2.5-sunburst for character sprites, maps, titles, etc. Open AI GPT-Image-2 for monsters). The rest of the project was coded with Claude.
Not 100% play tested - gameplay seems normal. If you find a bug or a graphic that was missed in the HD pack, please report it with details. Screenshots are appreciated.
I do not have access to a Windows machine, so this was authored with what I have available with testing in mind (MacOS on Apple Silicon and Linux x86_64).
## ROM (not included)
Dragon Warrior IV (USA), NES 2.0 header, 524,304 bytes.
- CRC32 (no header): `506E259D`
- SHA-1 (no header): `1b3dc265ba0d7e4b2cef9b3f8b0be0847df67c3c`
- Board: MMC1 SUROM — 512 KiB PRG, 8 KiB CHR-RAM, 8 KiB battery PRG-RAM
User brings their own legally-obtained ROM. I will not provide a ROM or tell you where to find one - don't ask.
## Build and Play on macOS (Apple Silicon)
[Download latest release](https://github.com/arichmondphoto/DragonWarrior4NESRecomp/releases), unzip and follow directions in `READ ME FIRST.txt`.
Building requires the Xcode command line tools and [Homebrew](https://brew.sh).
```sh
git clone https://github.com/arichmondphoto/DragonWarrior4NESRecomp.git # private repo: sign in when prompted
cd DragonWarrior4NESRecomp
./build_macos.sh "/path/to/Dragon Warrior IV (USA).nes"
```
The first run installs cmake/ninja/sdl2/python, builds the game, packages it as
**Dragon Warrior IV.app** and installs it in `/Applications` (or `~/Applications`).
Open it from Launchpad, Spotlight or the Applications folder. After pulling updates,
run `./build_macos.sh` again to rebuild and reinstall.
- Settings, battery saves and save states: `~/Library/Application Support/DragonWarrior4Recomp`
(kept across rebuilds; anything in the old `build/saves` is copied over once).
- The app bundles SDL2, the launcher assets and **your ROM** — don't share the app.
- `DW4_NO_INSTALL=1 ./build_macos.sh` leaves the app in `dist/`; `./play_macos.sh` still runs the unpackaged build.
- **A download for other players:** `./build_macos.sh --release` builds the app *without* your ROM and zips it
with a `READ ME FIRST.txt` (installing, opening an app from outside the App Store, adding the ROM) as
`dist/DragonWarriorIV-macOS--.zip`. Players pick their own ROM with **Browse For ROM** on first
launch (remembered in `rom.cfg`), or drop a `.nes` file in the data folder; the HD pack starts switched on.
The app runs on Macs of the build machine's architecture, and on the oldest macOS its bundled Homebrew
libraries allow (both are written into the read-me). It is ad-hoc signed, not notarized, so players confirm it
once in System Settings > Privacy & Security.
- Cheats: the launcher's **Cheats** page takes Game Genie codes (6 or 8 letters), each with an optional name and
an on/off switch. They are saved in `cheats.txt` in the same folder and apply when the game starts.
Codes that rewrite the game's code run it on the built-in interpreter for that session (same speed in practice).
Keyboard: arrows = D-pad, **Z** = A, **X** = B, **Return** = Start, `\` = Select. Gamepads work out of the box.
Esc = menu, F7 = save states, F8 = rewind, Tab = fast-forward, F11 = fullscreen (on a Mac keyboard you may need fn+F7 etc.).
## Build and Play on Linux (AppImage)
[Download latest release](https://github.com/arichmondphoto/DragonWarrior4NESRecomp/releases), unzip and follow directions in `READ ME FIRST.txt`.
Requires Git and Podman or Docker (`sudo pacman -S podman` on Arch/Manjaro).
```sh
git clone https://github.com/arichmondphoto/DragonWarrior4NESRecomp.git
cd DragonWarrior4NESRecomp
./build_linux.sh "/path/to/Dragon Warrior IV (USA).nes"
./dist/Dragon_Warrior_IV-*.AppImage
```
- The build runs in a Debian 12 container (`linux/Dockerfile`: toolchain, Python, SDL2 built from source with
X11/Wayland/PipeWire/PulseAudio/ALSA/controller support, each loaded at run time), so it works the same on any
distro and the AppImage runs on distros with glibc 2.36 or newer (2022 onward). `--native` builds on the host
instead (it prints the packages each distro needs; the result then needs a distro as new as yours).
- The AppImage holds the game, SDL2, the launcher's assets and the HD pack (on by default), never the ROM:
pick it with **Browse For ROM** on first launch (remembered), or drop a `.nes` in the data folder.
Saves, settings, `cheats.txt` and the Mods selection live in `~/.local/share/DragonWarrior4Recomp`
(`$XDG_DATA_HOME`), set by the AppImage's `AppRun` (`NESRECOMP_DATA_DIR`, patch 0017).
- `./build_linux.sh --release` also packs it with a player read-me (`linux/READ_ME_FIRST.txt`) as
`dist/DragonWarriorIV-linux--.tar.gz`.
- The steps themselves are `linux/build_in_env.sh` (build dir `build-linux/`; the HD pack's captures are cached there).
## Build (headless baseline)
```sh
git submodule update --init
cmake -S nesrecomp/runner/cyc/project -B build-headless -G Ninja -DCMAKE_BUILD_TYPE=Release \
-DNESRECOMP_ROM="$PWD/rom/DragonWarriorIV.nes" -DNESRECOMP_GAME_CONFIG="$PWD/game.toml" -DNESRECOMP_HEADLESS=ON
cmake --build build-headless
./build-headless/nes_game rom/DragonWarriorIV.nes --frames 600 --screenshot boot.png
```
Windowed build: `cmake -S . -B build` (needs SDL2 dev package).
## HD texture packs
HD packs (Mesen format) are enabled in the build (`MODS HD_PACKS`). Packs are
presentation-only: the game runs identically with them on or off (frame hashes
match over the Chapter 1 route).
**Project standard: every HD pack is authored at 10x** (`10`, 80x80
pixels per tile, a 2560x2400 picture — the presenter's ceiling), so it stays
sharp at every Window Scale from 1x to 10x.
**HD text, windows and monsters** (one pack — the Mods system allows one HD pack at a time):
- *Text and windows* — the font, window borders, cursors and arrows, smoothly
upscaled 10x from the game's own 8x8 tiles (`tools/smooth_pixels.py`), so every
letter keeps its Dragon Warrior IV shape. Generated from **your ROM** at build time.
- *Battle monsters* — AI-generated artwork (`hd/monsters/source/DW4Sprites.jpg`)
replacing the 8-bit monsters. `hd/monsters/mapping.json` maps each sheet sprite
to a game monster ID; `boxes.json` holds each sprite's box on the sheet.
How the monsters are built (`tools/build_hd_pack.sh`):
1. `tools/capture_monsters.py` forces one battle per monster ID by writing a
fixed battle (slots `$6E45-$6E48`, `$FF` = empty; counts `$6E49-$6E4C`; flags
`$6E44`, as the game's own setup at bank 24 `$A1AD` does) into a save state
taken just after the route's first encounter, and dumps each battle (tiles,
palettes, sprites, name). `tools/force_formation.py` starts any of the game's
34 fixed formations the same way.
2. `tools/capture_necrosaro.py` starts the Necrosaro battle (formation 25) and
drives the real transformations: his HP (`$727E`) is set to 1 and Ragnar
boosted, A is tapped until the form counter `$6E7E` goes up, and each of the
8 forms is dumped.
3. `tools/make_monster_hdpack.py` cuts each sprite from the sheet (transparency
rebuilt from the black background), fits it to the original monster's pixels at
10x and slices it along the original's pieces: background cells (keyed by CHR
bytes + exact palette) and 8x8 sprites (pattern table `$0000`, stored
un-flipped). Repeated tiles inside a monster are told apart by a neighbouring
tile (`tileNearby`) or by `hmirror`/`vmirror`. Necrosaro's forms share most of
their tiles, so each form's tiles are gated on `$6E7E` (`when` in mapping.json).
The presenter draws a sprite's HD slice only where the 8-bit sprite pixel is
opaque, so for these gated sprites the art is also keyed onto the blank
background cells beneath them (gated on the cell's position), which keeps
it from being clipped to the 8-bit silhouette.
4. `tools/render_monsters_hd.py` + `tools/score_hd_sweep.py` render every monster
with `NESRECOMP_HDPACK_DEBUG=1` and report any original pixels still showing.
To change a pairing, edit `hd/monsters/mapping.json` (sheet index → monster ID;
`captures` for sprites keyed on a special capture) and rebuild;
`tools/review_sheet.py` draws side-by-side review pages. `no_art` lists the game
monsters with no sprite on the sheet (sea monsters and two recolours); they keep
their 8-bit art.
- *Map tiles* — AI-generated artwork for every 16x16 map tile of the overworld,
towns, castles and dungeons (`hd/field/source/*.png`, 6x6-cell atlases with a
`.json` cell list each; `hd/field/ids.json` ties each tile ID to its tile).
How the map tiles are built (`tools/build_hd_pack.sh`):
1. `tools/capture_field.py` loads every map (279 areas + the overworld) by
rewriting the exit table at `$05A0` one step from Burland Castle's exit, and
dumps each map's metatiles (`$7600`), sub-palettes (`$76C0`), CHR and palette.
2. `tools/field_sheet.py` lists every unique map tile and each palette it is
drawn with (`field_tiles.json`; without `--json-only` it also draws the
numbered artwork sheets).
3. `tools/slice_field_atlases.py` cuts the atlases into 160x160 tiles (finding
each atlas's gutters, which image generators shift slightly).
4. `tools/make_field_hdpack.py` keys each tile's four 8x8 quarters by CHR bytes +
palette for every palette version. A quarter shared by several map tiles is
told apart by its neighbouring quarter (`tileNearby`); repeating ground tiles
get their edges evened out so they tile without seams.
- *Palettes and night:* the art is made for one palette per tile (stored in
`hd/field/ids.json`); every other palette the game draws it with (other
towns and dungeons, and night, captured with `capture_field.py --night`,
which sets the clock at `$62ED` past `$78`) gets a recoloured copy fitted
from the palette colours.
- *Fades:* screen transitions step every colour down by `$10` three times;
each version also gets entries for those three palettes, with the same art
at the matching brightness, so the HD art fades instead of flashing 8-bit.
Map tiles generated with OpenAI's image API (like the characters):
`tools/export_chatgpt_tiles.py` packs every tile (pixel-exact, 15x, in the
palette its art is made for) onto 1536x1024 sheets of 24, by kind of area,
with prompts and `openai_sprites.py`; the results go in `hd/field/sheets/`
(with the export's `sheets.json`) and `tools/slice_field_sheets.py` lines each
sheet up with its input and cuts it into tiles, replacing the atlas art for
those tiles.
To redo art for some tiles: `tools/export_ai_tiles.py` exports every tile at
160x160 (pixel and smoothed versions, atlases, README), and
`tools/scenario_batch.py` runs them through Scenario in batch. (unused - used GPT-Image-2.5-sunburst instead)
- *Walking characters* — AI-generated artwork for every character that walks or
stands on the maps (party, townspeople, soldiers, animals, ships, map
monsters): `hd/sprites/source/.png`, one 4x2 sheet per character in the
`tools/export_chatgpt_sprites.py` layout, generated with OpenAI's image API
(`tools/openai_sprites.py`); `hd/sprites/ids.json` ties each ID to its frames.
How the characters are built (`tools/build_hd_pack.sh`):
1. `tools/capture_types.py` captures all 108 character types from the game's
character table (bank 8 `$979B`, loaded by bank 28 `$9A37` → bank 8 `$85D3`):
it points a town's people at other table entries (patched ROM, via patch
0008) so every type is loaded, including the other chapters' heroes.
2. `tools/sprite_sheet.py --types` lists every character with its 8 frames
(4 directions x 2 steps, WRAM `$7200+slot*64`), merged with what the map
captures show (palettes, the party leader's own tiles).
3. `tools/check_ai_sprites.py` fits each sheet to the 8-bit outlines (whole
sheet, then per frame) and reports frames that don't match.
4. `tools/make_sprite_hdpack.py` cuts each frame into its four 8x8 sprites,
keyed by CHR bytes + colours, for every sprite palette set (day, special,
night and fade steps). Shared tiles are told apart by `hmirror`/`vmirror` and
neighbouring sprites (`spriteNearby`); people lying in bed (tiles the game
turns 90/180/270 degrees) get the art turned the same way.
The pack sets `spriteCells` (patch 0009) so the art is drawn over
the whole 8x8 sprite, not clipped to its 8-bit pixels.
- *Title screen* — AI-generated painting in layers, `hd/title/layers/`
(`layout.json` places them; painted on the guides from
`tools/export_title_kit.py`). `tools/make_title_hdpack.py` draws the sky with
the castle and the blank plate as still ``s, the two cloud strips
as backgrounds that scroll with the game's two cloud bands (`line:Y` + `wrap`,
patch 0013), and reveals each painted letter as the game types it (8x8 cells,
every other title cell almost transparent). The phase (blue sky / sunset and
every fade step) is read from one castle cell and one plate cell
(`tileAtPosition`). Without `*_day` layers the blue-sky look is recoloured from
the sunset art along a light-to-colour ramp. `tools/capture_extras.py` records
the intro every 4th frame (`title_seq.json`, with when each letter is finished).
- *Castle doors* — the three in-between frames of the big doors opening
(drawn straight into the nametable, so not map tiles): `hd/field/doors/DOOR1.png`,
cut by `tools/door_frames.py` into map tiles DR01-DR12 in every palette the
closed door uses.
- *Endor casino* — captured without a playthrough by warping from the route
(the exit table at `$05A0` pointed at map 4, submap 1; coins in WRAM `$62AD`).
Poker: `tools/capture_casino.py` writes chosen cards into the deck (WRAM
`$7600`, rank*4+suit, `$34` = joker) and deals them, so all 53 cards and the
back are recorded; `tools/compose_deck.py` builds one consistent deck from parts
of the owner's card sheets `hd/casino/cards/C01-C09.png` (chosen in `deck.json`:
one blank face, one pip per suit, the four ace pictures, the joker and back used
whole), laid out like the game's own cards, with ranks and J/Q/K letters from
`glyphs.png` (rendered once by `tools/make_card_glyphs.py`, Liberation Serif
Bold); `tools/make_casino_hdpack.py` keys it as 5x7-tile cards (shared tiles
told apart by `tileNearby`). Slot machine: `tools/capture_slots.py` plays three rounds (stand
below a machine, step up to face it); `tools/make_slots_hdpack.py` draws the
owner's painting `hd/casino/slots/SLOT.png` as a still `` (read from
the JACKPOT sign, with the fade steps; resampled onto the game's layout by the
straight-line x/y maps in `hd/casino/slots/layout.json`; its text window is
always blanked, since the text tiles are partly transparent) and the seven reel
symbols (8x8 sprites in 16x16 groups) cut out of the painting's own pay table
(boxes in `layout.json`; `SYMBOLS.png` is the fallback); lamps and coin digits are
smoothed. `tools/export_casino.py` makes the art package for OpenAI's image
API. Without the art only the smoothed tiles are added. The arena needs nothing
extra (HD monsters and text).
- *Night town crowd* — `hd/field/sheets/N01.png` (tiles T118-T127), cut by
`tools/slice_field_sheets.py`.
- *More monsters* — `hd/monsters/extra/M###.png` (monsters the atlases didn't cover).
- macOS: `./build_macos.sh` (or `./package_macos_app.sh`) generates the pack and
bundles it inside the app (`Contents/Resources/mods`; nothing is copied to
your Library folder). In the launcher open **Mods**, turn on
**HD text, windows, monsters, maps, characters, title and casino** (once — the setting
is kept in your Mods catalog), then start. `DW4_NO_HD=1` skips it.
- Tiles are matched by their 16 CHR-RAM bytes and exact palettes.
Bump `HD_VERSION` in `package_macos_app.sh` when the pack's content changes.
## Framework patches
`patches/nesrecomp/*.patch` are fixes to the pinned nesrecomp checkout, applied
by `tools/apply_patches.sh` (run automatically by `build_macos.sh`). It restores the files the
patches touch to the pinned commit, then applies every patch in order, so any earlier state is fine.
They are framework bugs, kept here until they can go upstream:
- `0001-cyc_sdl-hd-window-native-size.patch` — with an HD pack the window was
sized from the 4x HD picture (3072x2880 at Window Scale 3, clamped by macOS) and
integer scaling stepped in HD-picture units, so the game drew small inside an
oddly sized window. Window, logical size and integer steps now use native
256x240 pixels; HD textures are sampled linearly.
- `0002-cyc-window-scale-max-10.patch` — Window Scale was clamped to 8 in
config.ini, the launcher bridge and the in-game menu; one `CYC_WINDOW_SCALE_MAX`
(10) now applies everywhere.
- `0003-cyc_sdl-fit-window-to-display.patch` — Window Scale is in points on
macOS, so large scales (e.g. 10x = 2560x2400 points) ran off the screen. The
window now uses the largest whole scale that fits the display (the setting is
kept for bigger screens). On a Retina display Window Scale 5 already shows a
10x pack at full detail (2 pixels per point).
- `0004-cyc-trim-side-borders.patch` — Dragon Warrior IV (like many NES games)
masks the leftmost 8 pixels, leaving a black bar. New setting **Trim side
borders** (in-game menu > Display; `CropSides` in config.ini; on by default)
trims 8 pixels from both sides so the picture stays centred (240x240).
- `0005-cyc-launcher-trim-side-borders.patch` — also offers that setting in the
launcher's Display settings (needs recomp-ui patch 0002).
- `0006-cyc_ui_imgui-hidpi-menu-scale.patch` — on a Retina display the in-game
menu drew at half size in the top-left with its text clipped: Dear ImGui lays
out in points but nesrecomp never set the renderer's HiDPI scale (as ImGui's
SDL example does). Ratio 1 (Windows/Linux) is unchanged.
- `0007-hdpack-memory-conditions-read-wram.patch` — `memoryCheck` /
`memoryCheckConstant` conditions only read the 2KB internal RAM, so a condition
on `$6000-$7FFF` was always false. They now read the cartridge work RAM, where
Dragon Warrior IV keeps its battle state (the pack gates Necrosaro's forms on
`$6E7E`).
- `0008-allow-prg-mismatch-env-for-tools.patch` — with the environment variable
`NESRECOMP_ALLOW_PRG_MISMATCH` set, the host accepts a modified ROM (and save
states made with the original, and a mod catalog made for the original). Only `tools/capture_types.py` uses it: it
patches Dragon Warrior IV's character table (bank 8 `$979B`) so a town's
people load every character type in turn. Without the variable nothing
changes.
- `0009-hdpack-sprite-cells.patch` — a new pack option, `spriteCells`:
sprite HD art is drawn over the sprite's whole 8x8 cell with the art's own
alpha (the PPU side channel now also records the sprite on its transparent
pixels; empty OAM slots are excluded), instead of only on the 8-bit sprite's
opaque pixels, which clipped smooth art to a stair-stepped silhouette. A
transparent pixel of a sprite with no HD art draws nothing. Packs without the
option behave as before.
- `0010-hdpack-nametable-column-condition.patch` — new pack conditions
`nametableColumn` (0-31) and `nametableX` (0-63, with the nametable select):
the column of the nametable the background pixel was fetched from. Mesen's
`positionCheckX` is screen space, so art keyed to a cell slides off when a
row scrolls (the title's parallax clouds). The column is kept in padding
bytes of the HD record, so save states are unchanged.
- `0011-hdpack-tile-recolour-turn-shared-blocks.patch` — `` lines may end
with `rgb:GR/OR/GG/OG/GB/OB` (per-channel colour map, out = in*G + O) and
`rot:N` (the block turned N*90 degrees counter-clockwise). The pack stores each
picture once and the player derives palette versions (other towns, night) and
turned forms (people in bed) at load time; identical blocks are decoded once
and shared. The pack went from 241 to 165 MiB and peak memory from ~2 GB to ~1.1 GB.
- `0012-mods-bundled-read-only-packages.patch` — with `NESRECOMP_BUNDLED_MODS`
set to a folder laid out like the Mods catalog (`packages///`),
its packages are listed as well (read-only, not removable; a copy installed in
the catalog wins). The macOS app's launch script points it at the pack inside
the app. Selections stay in the catalog's `state.toml`.
- `0013-hdpack-background-scanline-scroll-wrap.patch` — `` lines may
end with `line:Y` (scroll horizontally with the background drawn on scanline
Y — its nametable position, so a split-screen band such as the title's
parallax clouds is followed exactly) and `wrap` (repeat the image sideways).
- `0014-game-genie-cheats.patch` — Game Genie codes (`runner/cyc/cyc_cheats.c`):
the enabled codes in `cheats.txt` beside `config.ini` (or `--cheats FILE`) are
applied when the game starts, through the framework's ROM read substitution
(`cyc_mod.h`; 8-letter codes compare by address with the new
`cyc_mod_set_rom_read_override_cmp`, as the real device did on any bank).
A code whose address can hold a byte the recompiler turned into C (an opcode
or operand) makes the session run on the interpreter, which reads every byte
at run time (no visible cost: the PPU dominates). Never applied online. The
launcher's Cheats page (recomp-ui patch 0003) edits the file.
- `0015-packaged-app-save-paths.patch` — `NESRECOMP_SAVE_NAME` names the
battery save and save states (instead of the ROM file's name) and
`NESRECOMP_STATE_DIR` moves the save-state slots out of the app bundle
(a downloaded app is read-only). The macOS launch script sets both
(`saves/DragonWarriorIV.*` in the data folder).
- `0017-packaged-app-data-dir.patch` — `NESRECOMP_DATA_DIR` moves config.ini,
mods/, saves/, rom.cfg and cheats.txt from beside the program (read-only
inside an AppImage) to a data folder; the Linux AppImage's `AppRun` sets it.
- `0016-hdpack-faster-compositing.patch` — the HD frame (2560x2400 at 10x) is
composed on the CPU every frame; the title, with its stacked full-screen
layers, took 70-100 ms per frame (well under 60 fps on an M1). Now: blending
without divides, no per-pixel modulo for wrapping layers, opaque tiles and
background blocks copied row by row, drawing starts at the topmost
background that is opaque over a block (a per-image grid built at load),
tiles that are invisible everywhere (alpha <= 1/255) are skipped, and the
frame is composed in horizontal bands on all cores (up to 16;
`NESRECOMP_HDPACK_THREADS=N` overrides). Same picture (within 1/255 where
an invisible tile was blended before); about 2.5x faster per core, more with
the cores.
`patches/recomp-ui/*.patch` likewise apply to the launcher checkout:
- `0001-launcher-configurable-window-scale-max.patch` — the launcher offered
1x-6x only; `LNG_WINDOW_SCALE_MAX` is now a compile-time override (default 6),
which this project's `CMakeLists.txt` sets to 10.
- `0002-launcher-trim-side-borders-setting.patch` — an optional "Trim side
borders" checkbox in Display settings (`Settings.crop_sides`,
`GameInfo.has_crop_sides`, appended ABI-additively).
- `0003-launcher-cheats-page.patch` — a "Cheats" page (`GameInfo.cheats_path`,
appended ABI-additively): add a Game Genie code with an optional name, turn
codes on and off, delete them; each change is written straight to the text
file (`on`/`off`, code, name per line), and the decoded patch is shown.
`tools/apply_patches.sh` now also handles patches that add files.
## Test routes
| Route | Frames | Covers |
|---|---|---|
| `routes/boot_to_name_entry.txt` | 7,600 | intro, title, Begin a New Quest, name entry |
| `routes/ch1_to_first_battles.txt` | 18,000 | name/gender, Chapter 1 intro, King's briefing, Burland castle and town, world map, several battles (Slime, Giant Worm) |
Run: `./build-headless/nes_game rom/DragonWarriorIV.nes --frames 18000 --input routes/ch1_to_first_battles.txt`
Useful RAM (verified by stepping): `$40` map id (36 castle, 41 Burland town), `$44`/`$45` player X/Y tile in town/castle maps.
`tools/drive.py` steers tile-by-tile from a save state and checks each step against RAM; `tools/route.py` turns a simple tap/hold spec into an input file.
## Native coverage
`seeds.txt` is gathered with `--miss-log` along routes in `routes/`, then used as `cycle_seed_file`.
Re-run new routes with `--miss-log seeds.txt` to extend coverage.
`captures.txt` (RAM-resident code, e.g. the $0502 NMI trampoline) is gathered with `--capture-log` and used as `cycle_capture_file`.