Files
NX2navi/docs/bug-capture.md
2026-11-13 08:25:02 -05:00

9.3 KiB

Bug captures

A bug capture records where and how the game was when a player saw something wrong, so that the bug can be looked for again at that place instead of somewhere else. The player presses F8; a test run then starts from what the press wrote. Code: runtime/bugcap.c, tools/capture_run.py. Ocarina of Time 1.1.1 (the place and the camera come from the debug tools, runtime/debug_tools.c); on other game versions a capture still has the picture, the timing, the settings and the save.

For the player

When you see a bug, press F8 while the game window is in front. About a second later a note at the top of the screen says Bug capture 3 saved with the folder's name. Tell whoever looks into the bug the number ("capture 3") and what you saw; nothing else is needed. Press it as often as you like: each press is its own capture, and a press while the one before is still being written is skipped.

The captures are in the game folder under logs\captures, one folder each, named by number and time (003-20261113-141502). Menu > Open log folder shows logs. A capture is a few megabytes, most of it the picture (about 9 MB at 3440x1440 with HDR); delete old ones whenever you like. Nothing leaves your PC: a capture is files on your disk.

What a capture holds

File What
capture.txt Written last: a folder that has it is complete. key = value lines under [sections], see below
frame.png The frame on screen as the window got it, the overlay included, without the capture's own note (an HDR picture converted to sRGB)
frames.tsv The last 600 presents before the press and the 30 after: frame, interval and replay time in ms
log-tail.txt The last 256 KB of the run's log
settings.ini Every setting that had a value (from nx2navi.ini, the menu or the environment) as nx2navi.ini lines, without the private ones (runtime/cfg_private.h)
NX2navi-save-<time>.zip The save the game would load next, as Menu > Export save writes it
flags.tsv, texts.tsv The game's on/off flags and its 64-character texts, by the hash of their names
flight/ With flight = 1: the flight recorder's dump of the same moment (tools/flight_view.py reads it)

capture.txt:

  • [capture]: number, name, time, time_utc, trigger (F8, NX2NAVI_CAPTURE_AT, replay), the NX2navi and game versions, the presented frame, run_seconds, menu_open.
  • [place]: area (the game's own name, which says child or adult for the outdoor areas: HyruleField_Adult), player_pos in metres (none at the title screen and while loading), player_rot (Link's 3x3 rotation by rows) and player_yaw, and nearest_start, the area's start position nearest to him, when the list of start positions was read (the debug menu or a test reads it).
  • [camera]: camera_pos, camera_dir, camera_yaw and camera_pitch (radians; the camera looks along (cos pitch sin yaw, sin pitch, cos pitch cos yaw), the free camera's convention) with camera_source: the frame on screen when DLSS, DLAA or FSR runs (then also camera_fov_y_degrees, camera_aspect, the near and far planes), else the free camera when it is on, else the game's own camera. game_camera_pos / game_camera_target and event_camera_pos / event_camera_target are the game's two scene cameras as its camera update chose them in the frames after the press, whatever was drawn.
  • [picture]: the window's size, the game image's size, HDR, the scaler, the present mode, the display's rate.
  • [timing]: over the presents before the press: the average frame rate, the median, 95th and 99th percentile and the longest frame time, how many frames took over 33 and 50 ms, the last 30 intervals before and the first after, and the performance overlay's numbers when it was on.
  • [system]: OS, CPU, RAM, GPU, driver.
  • [settings that differ from the defaults]: the settings whose value is not the default; a private one shows only that it is set.

Position, rotation, area and the camera of the frame on screen are read on the render thread at the press, so they belong to the frame the timing ends with; the picture is the next present.

Starting a test run from a capture

python tools/capture_run.py 3
python tools/capture_run.py "C:\...\game\logs\captures\003-20261113-141502" --set FSR4=0 --hold 30

The argument is a capture's folder or its number under the installed game's logs\captures (--game names another game folder). The script

  1. refuses to start while a game is running (one game at a time);
  2. makes a throwaway profile: the game's own profile without its saves, with the capture's save as a waiting import, which the game makes the save when it starts;
  3. starts the game with NX2NAVI_CAPTURE_REPLAY=<the capture's folder>, in a window of the capture's size that takes neither the focus nor your input (--foreground and --fullscreen change that), co-op and Discord off;
  4. waits while the game presses A through the title screen and the file menu, teleports to the capture's area, walks Link to where he stood, puts the free camera where the camera was and takes a capture of its own;
  5. closes the game and prints both captures side by side: area, positions, the distance between them, cameras, and the mean difference of the two pictures.

The run's log and its capture are in <capture>/replays (--out names another folder). --set KEY=VALUE puts one setting on top of the capture's for this run, which is how a suspected setting is tried: run once as captured and once with --set. --hold <seconds> keeps the game open after its capture; --no-camera leaves the camera to the game, for a bug that is about the camera itself. Any other test setting can ride along with --set (DUMP, SHOT_DIR and SHOT_EVERY, DBG_TEST, TRACE).

What the game does in such a run (NX2NAVI_CAPTURE_REPLAY, environment only):

  • The capture's settings.ini stands in for nx2navi.ini, which is neither read nor written; the environment still wins over it. The log's first lines say so and list the settings.
  • Until Link appears the pad presses A (four reads in every hundred).
  • Three seconds later the game changes area as its own doors do (dbg_warp), to the start position of the capture's area that is nearest to the captured place; when the save already starts within 60 m of it, there is no teleport. A refused teleport (an event still running after the load) is asked again, up to six times.
  • Link runs to the captured x z (dbg_route_to), steered by the game's own camera and walking the last three metres, and counts as there within half a metre. He is walked, not put: his position cannot be written (debug_tools.c). When something is in the way he sidesteps, and after ten tries the walk is given up where he is. When he does not move at all for two seconds, the game holds him (a conversation that began where the teleport arrived, on a save that had not been there): A is pressed until he moves, and the walk is asked for again.
  • The free camera is turned on standing (the pad still plays the game) and put at camera_pos, camera_yaw, camera_pitch. NX2NAVI_CAPTURE_REPLAY_CAMERA=0 leaves the game's camera.
  • Three seconds later the log says capture: replay of <name> ready: ... with how far Link is from the captured place, and the run takes its own capture (trigger = replay), whose capture.txt adds replay_of, replay_player_off_metres, replay_walk and replay_camera.

What a replay does not bring back: the time of day, enemies and other actors, Link's pose and what he holds, an event or cutscene that was running, the camera's field of view where the game had changed it (the comparison shows both), and progress made since the last save (the save is the one on disk at the press; the flags that differed are in flags.tsv). A capture taken at the title screen or while loading has no area: its replay applies the settings and stops.

Measured on the PC the feature was written on (RX 7900 XT, HDR on): in a 3232x1352 window a capture is written in 1.2 s and holds about 9 MB, eight of them the picture; in a 1720x720 window 0.6 to 0.8 s and 3.5 MB. The frame after the press is some 10 ms longer, for the read-back of the picture. A replay of a capture taken three metres from where the save starts took 45 s from start to its own capture, with Link 0.4 to 0.5 m from the captured place and the camera's position and angles equal to the last digit; one that teleports to another area and ends a conversation on the way took about 100 s.

Without the script: set NX2NAVI_CAPTURE_REPLAY to the folder and start the game with --profile <a copy of a profile>; NX2NAVI_CAPTURE_DIR says where the run's own capture goes (default <capture>/replays).

Test settings

Environment or nx2navi.ini, for runs without a keyboard:

  • NX2NAVI_CAPTURE_AT=<frame>[,<frame>...]: a capture at each of those presented frames (up to 16).
  • NX2NAVI_CAPTURE_DIR=<folder>: the folder captures go to, in place of logs/captures.

The flight recorder

flight = 1 keeps a ring of the last frames with a small picture each (runtime/flight.c). Its dump on F8 now belongs to the capture: it goes to the capture's flight folder. Its other dumps (a frame over flight_spike_ms, NX2NAVI_FLIGHT_AT, NX2NAVI_FLIGHT_EVERY) go to the flight folder beside the game folder as before.