The action prompt at the top right of the HUD draws the four face buttons as a diamond in the console's places: A right, B bottom, X top, Y left. Under pad_layout = xbox, playstation or auto the A action is the bottom button, yet the diamond kept A on the right, so the prompt pointed at the wrong button. At start the runtime now edits the prompt's layout in the shared interface archive for such a layout (the buttons, the dots and the lit crosses move, and the bubbles of X and Y flip) and serves the result from the profile folder; with nintendo, the default, nothing is changed. Every field is found and checked before anything is written, so a layout a mod changed is left as it is, and the archive is packed again with the game's own dictionary at the same unpacked size. One SARC reader and dictionary lookup now serve this, the camera pack and the menu's assets. When the layout changes, the menu's Restart counts the diamond.
tests/action_prompt_check.py passes without game files and on the game's own archive (4 copies, 208 fields, 13 ms at start).
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
When Streamline loaded but DLSS Frame Generation was not available, the menu's row read "in use: off, needs an RTX 40 GPU or newer", also on an RTX 5080: the same check fails when the plugin did not load or a function it needs is missing. The row now points to the dlssg lines of the log, where the bridge says whether the plugin loaded and which of its functions it found, and then asks Streamline for its support result on the selected Vulkan device and logs it by name. The query is an optional export, so a bridge DLL of an earlier build keeps working.
The runtime and the bridge (tools/build_dlssg.py, MSVC, Streamline's headers) both build here. The failing case itself was not reproduced: on this RTX 5090 frame generation is available.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A player on an AMD card reported crashes in fights and that a line added to nx2navi.ini cured them. That line, async_compute = 1, is no setting of the game, and neither are two more in the same file; the one line of the file that changes how memory reaches the GPU is wb_lazy = 0. A second report, an RX 6600 on 0.7.1, lost its device 3.5 minutes into play with the lazy write-back on. Neither proves it the cause: the player changed several lines at once and sent no log, the RX 6600's last pass mark is inside a draw pass and not a fetch, cards with 8 GB lost their device the same way on NVIDIA before the lazy write-back existed, and reading the fault handler, the fetch, the arming and the buffers' lifetimes found no failure path. What depends on the driver and cannot be checked without the card: pages of guest memory are inaccessible while a host-pointer import of them is bound and written by the GPU, and Vulkan calls run inside the access-fault handler.
The default is now decided once the driver is known (wb_lazy_choice.h, vk_memory.c vk_mem_lazy_decide): on under Windows as before, except on AMD's driver branch for RDNA 1 and 2, which none of us has run it on; AMD's driver for RDNA 3 and newer keeps it on, since builds with that default were measured on an RX 7900 XT. What the player sets wins everywhere, and one line at start says the choice and its reason. On those cards open areas run at the 0.7.0 frame rate again (on an RTX 5090 that was 65 fps against 90 in Hyrule Field; not measured on AMD). tests/wb_lazy_check.c checks the choice by platform, driver and setting without a GPU.
For reports of this kind the log now holds what was missing. At start: every setting nx2navi.ini sets, and in one line the keys that are no settings of this version and do nothing; the settings menu shows those in its bottom row. At a fatal error or a crash: one line with the card, its driver, the memory mode, the lazy write-back with its reason, armed ranges, fetches so far and a fetch in progress, and the texture detail (dispatch.c rt_fatal_note_add, vk_device.c fatal_note), since a report is often the end of a log alone.
--probe-cfg loads the player's file: the three lines are named, and the menu's save keeps them and every other line it does not change byte for byte. Nothing here ran on an AMD card, and no fatal error was provoked to read the new line.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A player's AppImage build of 0.7.1 stopped in its compile step after eight minutes of RomFS work: four
files did not compile, because the kit builds with the system's compiler and headers and theirs were
older than the runtime needs. vk_pipeline.c and vk_device.c name VK_KHR_pipeline_binary,
VK_KHR_dynamic_rendering_local_read and VK_KHR_shader_maximal_reconvergence, which their Vulkan headers
did not declare, and ui/menu.cpp takes its font in with #embed, which clang has from version 19.
- Vulkan headers: tools/vulkan_pin.py holds the release the other builds use (1.4.363, the archive and
hash tools/make_release_linux.py already pinned, which now reads them from there). The AppImage carries
its C headers in toolchain/vulkan-headers and builds against them whatever the system has; a kit or a
source checkout uses the system's when they are at least that release and fetches the pinned ones
otherwise (build_game.py vulkan_include). Their folder goes first on the include path of the runtime's
sources only, so the lifted code's objects keep their keys, and a runtime object compiled before
against the system's headers is taken over instead of compiled again. AppRun no longer asks for the
system's Vulkan headers.
- #embed: tools/build_shaders.py also writes the font's bytes as runtime/ui/roboto_ttf.h (ignored by git,
carried by a kit), and menu.cpp includes that where the compiler has no __has_embed. With #embed
nothing changes.
- Before its long steps a Linux build that will compile now checks every runtime source that needs no
header of the install with the compile step's own command (clang -fsyntax-only) and links an empty
program against the game's libraries (make_game.py preflight): an old compiler, an old header or a
missing development package stops it in the first seconds with the compiler's own errors. A failed
compile names every file that failed, not only the three whose errors it prints.
Checked on Windows: tests/launcher_test.py passes with four new tests (117); the header of bytes and
#embed give the same 162,588 bytes, and both branches of menu.cpp compile; the early check passes on the
Windows file set with clang 23 and stops on files made to fail; the 34 local headers of release 363 are
the upstream tag's files by git blob id. Not run: anything on Linux, the pinned archive's download, a
compiler older than 23.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The limit counted the frames on screen, so the game was paced at the limit over frame generation's multiplier: 60 at 4x drew 15 frames a second, and each step up of the multiplier took drawn frames away. Now a limit of 60 at 4x is 60 drawn and 240 shown. Only the display choice still counts the screen's frames, since a frame past the monitor's refresh is never seen, and with no limit set the default is 60 drawn rather than the monitor's rate.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The user asked for this once the flicker fix was measured. The single-frame flicker that kept it off in 0.7.0 came from frames drawn before the game's signal that their data was written, which the previous commit fixes; the lazy write-back only made the frames come faster, so the signal fell into the replay more often. wb_lazy = 0 in nx2navi.ini still turns it off, and an ini that already holds the key keeps its value. Elsewhere than Windows the default stays 0, as its fetch inside the access-fault handler has never run there.
Measured on the build of the previous commit, 1,800 presented frames a run, standing in Hyrule Field by day with DLSS Performance, frame generation 4x and HDR. With the lazy write-back on and eight busy threads beside the game: 38 and 0 single-frame spikes with the fix switched off (408 and 44 frames whose signal came during the replay), 0 and 0 with it on (667 and 724 such frames). With it off and the same load: 0 spikes in four runs, two with the fix and two without, 86 to 140 such frames each. Without load, one run each: 88.5 frames a second with the fix against 93.3 without it with the lazy write-back on (GPU 7.2 against 6.8 ms a frame, 6.53 against 6.48 submissions a frame), 71.0 against 65.6 with it off, so the fix's cost is inside the run-to-run spread; every run ended cleanly without a fatal error. This build was started once with no setting anywhere: the lazy write-back was on, and the Screen tab shows no row for it.
Not measured: play by hand with the lazy write-back on and the fix (the user's sessions so far ran with it off), other areas than Hyrule Field, and more than one run for the frame rates.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
One conflict, in the build kit's notes, which keep both the shader cache item and the Texture detail text.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The row offers Automatic, Full, Reduced and Low, names the level Automatic picks on this card, and notes that a change takes effect after a restart.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A setting read at the start showed "after restart" under its value always. The values the game started with are now
kept (cfg.c start_values), a row shows the note only while its value differs from them, and the Restart button counts
the settings that wait, the upscaler, frame generation and HDR among them. A value the environment sets never waits.
--probe-cfg checks the count.
From pull request #377 by mooki000. Three conflicts in the menu resolved against the frame generation rows that came
in since: the upscaling row keeps its description by what can load, and the frame generation restart test knows both
ways NVIDIA's runs.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Eighteen commits, one per Linux pull request that was not in yet: the build fix for a source checkout, NVIDIA's shader
disk cache only with NVIDIA's driver, tools/run_game.py for a Linux game folder, a memory limit the low-memory handling
can read, the recording thread's core on Linux, shadow storage on Linux and Proton, the Steam Deck profile and prompts,
FSR 3.1 offered only where its library loads, DLSS frame generation through NGX on Linux, the AppImage with its window
and its workflow, and the processor level from /proc/cpuinfo. Two conflicts: the settings table keeps the area-load
settings under the new comment, and the example file keeps the lazy write-back as off by default and adds that it
stays off with shadow storage.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
With it on, single frames come out with the whole scene lit differently (brighter, shadows off). The flight recorder's
thumbnails of 1,800 presents standing in Hyrule Field show none with it off in every run, and with it on 43 to 181
while four other threads keep the CPU busy (10 to 97 in earlier runs, 0 on an idle machine); the cause is not found.
Two candidate fixes (arming only against a sync whose copies were handed over, and snapshots waiting for GPU writes in
flight) did not change the count, and fetching at every release made it far worse.
So wb_lazy is 0 unless set, on every platform (mem_init loses its special case), and the Screen tab has the row
Experimental FPS boost for it on Windows, with its text in the four menu languages; like every setting read at the
start it notes "after restart". The 0.7.0 notes describe it as experimental and off, say about 1.6 GiB for the video
memory a full shader cache no longer holds (2,484 to 846 MiB at the title screen in the measured run), and list the
two fixes before this one.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The Controls tab's Button prompts row and nx2navi.ini take steamdeck: the Deck's own labels in the game's
text, action prompt and ocarina notes, which are A B X Y where an Xbox pad has them, L1 R1 L2 R2 and L3 R3,
Menu and View. The style is drawn by tools/pad_prompts.py like the others, into romfs-patches-steamdeck
and romfs-patches-steamdeck-label, and every build with button art writes them.
Pull request #107 by loteran asked for PlayStation and Steam Deck prompts; it was closed, and the tree
took the PlayStation half in another form, drawn from button art instead of redrawn font glyphs. This is
the Steam Deck half in that form, written here: the art has no Deck style, so its entry in
tools/prompt_art.py names the Xbox pictures for the face buttons, Menu, View, the d-pad and the sticks
and the PlayStation pictures for the shoulders and stick clicks. It needs four pictures of the Xbox set
that no other style used (the white letters on a light button, for the ocarina's notes); a release made
with --prompt-art copies them with the rest.
The Windows files compile and tests/launcher_test.py passes, with the prompts step's test following the
number of folders (seven now); every picture the style names is in the art used for releases. The style
was not drawn here (that needs the game's font), and --probe-cfg, which has a new check, was not run.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
DLSS Frame Generation went through Streamline, which only the Windows build has, so framegen = dlss did
nothing on Linux. There it now calls NGX's frame generation feature directly (vk_dlaa.c vk_ngxfg_*), with
the depth, motion vectors, jitter, camera and HUD-less picture the Streamline path gets. With it on, each
frame is composed into an image of its own instead of a swapchain image, the generated frames are made at
the end of the frame's command buffer, and a presenter thread shows them and then the rendered frame, each
copied into a swapchain image it acquires itself, spaced over the time between rendered frames
(gfx_present.c ng_*). fg_mult is the multiplier: 2 to 6, or dynamic for only the frames the display's
refresh rate needs. NVIDIA's libnvidia-ngx-dlssg.so.<version> goes beside the game; it is not in the
repository or any kit.
Pull request #124 by spidercraft. Its base was the present path of 0.5, so it is ported by hand:
- taken as written: the NGX calls and the presenter (the block is lifted from the pull request's diff);
- wired again into present_image, create_swapchain, the output rate and the HUD-less picture as they
are now, with every new branch behind vk_ngxfg_wanted, which is 0 in the Windows build: the code is
compiled there and never reached;
- added: frame generation's feature and parameters are released before NGX shuts down, the presenter's
images, semaphores and command pool go when the device is released and are made again for a new one,
and a failed submit on this path settles its fence without an acquire semaphore;
- the menu: the Frame generation row follows the setting at once on Linux and says "after restart" only
when the game started without DLSS upscaling; the multiplier row lists what the GPU allows and Dynamic;
- the overlay row of the pull request it builds on (#123) is not part of this.
It touches vk_dlaa.c, which another session is changing: one field in N, the FG state after it, one FN
line, the instance extension query, the capability read in vk_dlaa_init, the release, and a block appended
at the end.
Known gap, as in the pull request: on this path fps_limit counts the frames the game draws. The Windows
files compile without new warnings; nothing was run, and the Linux path is not compiled here.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
On Linux the Screen tab listed the five FSR 3.1 upscaling modes and AMD's frame generation, but only the
Windows build loads amd_fidelityfx_vk.dll: a player who picked a mode got the scene at the mode's share of
the picture with nothing to scale it back up. vk_ffx_available answers from the load's result once it ran,
and before that from the library file being there, so the menu can ask every frame. Where it says no,
the menu lists neither, a mode that is still set shows "in use: off", the scene keeps the game's own
share (vk_dlaa_fsr3), frame generation keeps the plain swapchain with one log line, and the texts name
what does run.
Pull request #371 by mooki000, taken as it is; two hunks merged by hand (vk_fg_wanted, which now also
stays off after a failure, and a README line that had gained Texture filtering). It touches vk_dlaa.c,
which another session is changing: vk_dlaa_fsr3 gains the availability test and one log text changes.
On Windows the menu changes only where the DLL is missing. The Windows files compile; nothing was run.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
NX2NAVI_ANISO was read once, when the first sampler was made (vk_texture.c sampler_features), and make_sampler raised the level of the surface textures' samplers as it created them. Samplers are looked up by the game's parameters and live until shutdown, so a new level could only reach them after a restart, and the setting had no row in the menu.
The decision which samplers get the level moves from make_sampler into aniso_raised, and sampler_for puts the raised level into the sampler key, as it does with the DLSS texture bias. vk_sampler_aniso_live reads the setting again after a settings change, at each present beside vk_dlaa_frame's bias; on a new level it clears the slots' samplers and bumps the sampler generation, as vk_sampler_dlss_bias does, so the slots and the descriptor caches take samplers of the new level from the next draw. The old level's samplers stay until shutdown like every sampler. The setting is marked live in cfg.c, and the Screen tab gets a Texture filtering row (Game, 2x, 4x, 8x, 16x) with the description the catalog already had.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The menu test hook's shot of the Screen tab shows only its first screenful of rows, so rows further down (Upscaling, Frame generation) could not be pictured without input, and a hidden test window takes none. NX2NAVI_MENU_TEST_SET now accepts FOCUS=<key>, which calls opt_focus_key for that setting's row: the cursor goes to it and the page scrolls it into view when it is next drawn. It does not switch tabs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In the settings menu a click anywhere on a row stepped its setting to the next value, and the left arrow stood left of the value's text, so it moved whenever a value of another width came up: clicking the left arrow through a list landed on the row at the second click and stepped the other way. Each stepper now has its arrows in white frames at fixed places, the left one the width of the row's widest value from the right one, and the mouse changes the value on those frames only; a click on the rest of the row selects it. On and off rows have the same arrows. An arrow at the end of its values is drawn faint and does nothing, and a button held on an arrow repeats as a held key does. A, Enter and left and right work as before.
NX2NAVI_MENU_TEST_CLICK clicks rows by their setting's name during a menu test run. On an RX 7900 XT at 3232x1352 the arrows stepped the performance overlay one value a click and stopped at both ends, and clicks on the body of four rows changed nothing.
HDR between off and the tone mapper now follows the menu. The HDR path is set up at every start (vk_hdr_armed): the swapchain colour space and metadata extensions, the tonemap splice and the settings in the support buffer, with the spliced tonemap returning the game's own SDR result while the swapchain is SDR. A change of the setting makes the swapchain again in the other format. On the same card a run that started with HDR off went to an HDR10 swapchain and back, and one that started with HDR on went to SDR and back, with the picture right in each state. The game's native HDR still waits for a restart, as the game asks about the display once, and the row says so. NX2NAVI_HDR_LIVE = 0 leaves the HDR path out of a run that starts with HDR off.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The launcher's home has a Saves screen: Export Save writes NX2navi-save-<date>_<time>.zip into Downloads (or a chosen folder) with a manifest, the save the game would load next and, for a bug report, the game's latest log and nx2navi.ini, never a key file; Import a Save checks a zip's manifest, game, paths and CRCs, backs the current save up into the saves folder and replaces it while the game is not running. The game's Game tab has the same exports and an import that waits in profile/save-import and becomes the save at the next start, since the running game would write its progress back over a save replaced under it.
The zip code (runtime/save_zip.c) and the import at start are checked without game files by tests/save_zip_check.py and tests/save_share_check.py; the launcher side by its test suite.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ImGui_ImplVulkan_CreateFontsTexture checked no result: with the video memory full, the atlas a window size change
makes again got no memory, its copy still went out, and the GPU wrote to address 0 (issue #165, device lost after
Alt+Tab). It also submitted and waited on the game's queue itself, and when ImGui_ImplVulkan_NewFrame ran it (whenever
the back end held no atlas) that took no queue lock, beside the game's own submissions; the crashes in AMD's driver
inside the menu's drawing (issue #230) fit that. ui/menu_art.cpp menu_font_upload now uploads the atlas as the menu's
art is uploaded: VMA picks the memory (device-local, else what is left), the copy goes in the runtime's upload batch
under the queue lock, every step is checked, and the previous atlas is freed only once the new one is on the GPU and
the queue is idle. The menu no longer calls ImGui_ImplVulkan_NewFrame, whose only work was that upload. The back end's
VkResults are checked and logged (menu_vk_check), a texture whose descriptor set was not allocated is not drawn with,
and the descriptor pool holds 32 sets, room for two atlases while one replaces the other.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the pacer divides the limit by frame generation's factor, the default limit of 60 held the game to 30, 20 or 15 rendered frames a second at 2x, 3x or 4x on any monitor, 15 at DLSS 4x on a 165 Hz one. While AMD's or NVIDIA's frame generation swapchain is in use and neither fps_limit nor the older fps60 = 0 is set, the limit is now the monitor's rate, as with fps_limit = display: 4x on 165 Hz draws about 41 and shows 165. A limit the player set is kept, and the menu's Frame rate limit row shows the display choice while this applies.
The mouse camera divided its input by the limit's frame ratio, which counts generated frames, so frame generation turned it faster by its factor; it now uses the rate the game draws at (frame_step_game_hz).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
framegen_multiplier (2, 3 or 4, live) sets how many frames the display gets per rendered frame while NVIDIA DLSS frame generation runs, and a DLSS multiplier row under Frame generation in the Screen tab sets it. nx2navi_dlssg.dll now passes that count to Streamline and reports the GPU's limit (numFramesToGenerateMax) through a new optional export, nx2sl_frames_max; with an older DLL the game keeps to 2x. A value above the limit is clamped to it with a log line, and the menu lists only what the GPU and driver allow, with a note saying why the rest are missing. The Frame generation row now says "in use: off" and why when DLSS frame generation was set but does not run.
The pacer divides the frame limit by the multiplier in effect, as it already halved it for FSR 3.1, so the display gets the limit and VSync at the display's rate drops no generated frames; RivaTuner Statistics Server stays out while DLSS frame generation runs, since it counts generated frames. With DLSS 2x a limit of 60 now shows 60 on screen instead of 120.
nx2navi_dlssg.dll has to be rebuilt (tools/build_dlssg.py) for 3x and 4x.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The presence is off by default and its only switch was a row of the settings menu's Menu tab, which the Ocarina menu
left out, so it could be turned on only by editing nx2navi.ini. The row is in the Game tab now, and the launcher's
Settings has a Discord Status row that writes discord_rpc to the game's nx2navi.ini (launcher/gameini.py, which
reads the file as runtime/cfg.c does and changes only the line it sets). The connection itself works: the installed
game's --probe-discord connects and sets the status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Button prompts row gains Keyboard and mouse (prompts = pc). tools/pad_prompts.py --style pc writes
romfs-patches-pc: every button glyph shows the first key its bindings give it (Ocarina's defaults, with the bind_
lines of the install's nx2navi.ini over them), the mouse buttons as a mouse, the d-pad and the left stick as their
keys laid out as on the keyboard (1 to 4, W A S D), and the camera as the mouse moving. The action prompt shows the
key's label, the ocarina's notes the light keys. A key the art has no picture of is written on a blank key. The pad
styles draw the same bytes as before.
The prompts description says so in each menu language, and the Chinese one no longer splits words across its lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game's ACES tonemap shader gets a GLSL function linked into its SPIR-V where it clamps each channel: the game's SDR result upgraded to the HDR range by luminance and mapped to the display with the RenoDRT curve, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare and hue correction. The settings reach it as four vec4 the support buffer gains at 1440. The present encode is a port of the swap chain pass: gamma correction, gamut compression, the peak clamp and PQ. Without an HDR10 swapchain the shader gives the game's own SDR picture; native HDR is unchanged.
The menu offers Off, RenoDX and Native, with gamma correction, the tone mapper and its grading rows. The shader build compiles sources marked "keep functions" without optimisation so their functions survive for the splice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Upscaling row no longer carries "DLSS on or off: after restart". It shows "after restart" only while the upscaler picked cannot start in this run. Frame generation says so only for a change to or from NVIDIA's, since AMD's applies at once, and the HDR row no longer says it twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pad_layout = playstation plays a pad by label as xbox does, named for a PlayStation pad: cross A, circle B, square X,
triangle Y. The prompts follow it like the Xbox layout, by label. Under Automatic the note says PlayStation for a pad
whose bottom button is labelled cross. The layout description in each menu language now says what every choice does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Controls tab has a Button prompts row again, with Nintendo, Xbox and PlayStation. prompts = playstation is a new
value next to xbox; nintendo, xbox and pc read as before, and pc stays out of the row as nothing makes its files for
this game. The game reads its prompts at start, so the row says when a restart would change them.
tools/pad_prompts.py writes the overlay folders romfs-patches-xbox, -xbox-label, -playstation and -playstation-label
from the player's RomFS and a folder of button art that stays outside the repository. The button font's glyphs become
outlines traced from the art, the action prompt's letters and the ocarina's note buttons are painted again in their
own BC4 and ASTC formats, and the font's entry in the resource size table grows with it. The by-position folders show
Switch A as B or circle, the by-label ones as A or cross, following the pad layout. The settings probe checks the new
value and folder names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The foliage's sun shadows took most of the GPU time: one mesh draw shaded about 1,000 million fragments in the far cascade. The far cascade now shades them at a 2x2 rate, and the two far cascades drop one side of the double-sided cards. At the forest entrance the frame rate went from 18 to about 43 fps on an RX 7900 XT.
FSR 3.1, its frame generation and the console mode now take effect without a restart. The full performance overlay lists the settings shown in the options menu.
NX2NAVI_GPU_PASSES times each pass and counts its fragments, and NX2NAVI_SET_AT changes settings at a given frame for test runs. Both are off by default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was offered only while already set, because Streamline presented no generated frames yet. With Reflex now pacing it from the frame's start it generates frames, and the user played with it: it works well.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The picture's height can now be 1080, below the game's own 1440, for slower GPUs: the scene, the DLSS and FSR 3.1
modes and frame generation follow it, and the ultrawide shapes become 2560x1080 and 3840x1080. "Native" keeps its
floor of 1440. The example settings file now documents game_aspect and game_res.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Import Saves: the saves folder picked in the dialog imports what waits in it (it had made the folder's README the
save); the save backed up is the one the game would load, in runtime/fs_host.c save_recover's order, so an interrupted
commit's only copy is kept, and the rest of the commit journal moves into the backup instead of being deleted; the
imported save goes into place before the old one is deleted, which comes back if that fails; another NX2navi install's
finished commit counts as its save. Tests cover each.
Quit waits while the game writes a save, as Restart does: the game runs on under the menu, and closing the window
finishes only a commit that has begun. The VSync row says "in use: Immediate" when VSync On falls back to tearing, and
its default follows the device profile (cfg_default). Without the game's textures the menu's divider is a plain line
and diamond of its own, not a tracing of the game's ornament.
make_game.py: the compile key holds the processor's features (cpu_tag), so a game folder moved to a PC without AVX2
compiles again instead of crashing; the main thread gets 16 MiB of stack with GNU ld too; any error waits for the
RomFS image, so its .part file is not cut short. The launcher tests use a neutral title id; tools/menu_preview.cpp
has cfg_default.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In-game menu: the settings window, its tabs, rows, buttons and cursor, the performance overlay and the Preparing
shaders box are drawn with the game's interface textures where the RomFS holds them (runtime/ui/menu_skin.cpp,
perf_panel.cpp; plain shapes until they are decoded). Restart joins Continue and Quit, each behind a confirmation;
VSync is an on/off switch; DLSS is listed only on NVIDIA GPUs; the menu sounds are quieter; Picture shape is gone and
the setting descriptions are shorter; edge cases of input, layout and fonts are fixed. tools/menu_preview.py draws the
menu without the game.
Launcher: Import Saves takes a save from the game folder's saves/ folder or one the player picks; downloads fall back
to Windows' own certificate check when Python's refuses a server; updates are faster; the title bar is drawn in the
window and Navi is every icon. tools/make_game.py builds with CLANG64's clang where the launcher installed it, packs
the RomFS beside the lift and the compile, takes a prebuilt runtime, and compiles the game's resources without cmd.exe,
so a user folder with a space in its name builds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where RTSS is installed and running, the frame limit from the menu goes into RTSS's profile for the game's executable
through RTSS's hooks library, loaded at run time, and the game's own pacer stands aside while RTSS has hooked the game.
Menu changes apply live, unlimited clears the limit, and closing the game (or any exit path) puts back what the profile
held, or removes a profile the game created. Each change is read back from the saved profile: without administrator
rights RTSS's profile folder cannot be written, and the game then keeps its own limiter. While RTSS limits, RTSS's own
frame counter for the game is checked against the limit; well past it for three seconds, the game takes the limiting
back for the run. The log says which limiter is in use and why, and a note under the frame-rate row says when RTSS
limits, when its profile is read-only, and when it does not hold the game to the limit. rtss = 0 and frame generation
keep RTSS out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pad_layout defaults to nintendo: Switch A on the right face button, B bottom, X top, Y left, on an Xbox pad too, where
auto had picked the Xbox layout. The settings probe checks every button's position.
Menu: the "default" mark follows the Steam Deck profile's values (cfg_default); Left on a value the list does not hold
goes to the last entry; the HDR row says "in use: off" when the swapchain is not HDR10; the open state is read again
after taking the reset, so a menu opened by F1 while the overlay alone was drawn starts with its focus row; the font
atlas is rebuilt only for a scale more than 12% off or one that held for half a second, so dragging the window's edge
no longer rebuilds it every frame.
A shader that fails to translate keeps its request and code in the shader cache. NX2NAVI_GPA_TWIN=0 turns the guest
copy of device address table stores off (diagnosis). The memcpy override no longer prints its first 500 calls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A GPU index or name that is not usable, or no longer there, falls back to the best usable GPU with a log line instead
of stopping the game before the menu can change it (vk_device.c pick_device).
- A held Esc no longer opens the menu and closes it again on its first key repeat (menu_win.cpp, menu_sdl.cpp).
- The menu does not open on the boot screen, where it was not drawn while its keys queued up and changed settings once
the game ran.
- The DLSS render scale follows the upscaler that runs: a DLSS or FSR 3.1 mode picked in the menu, which applies after
a restart, or DLSS where NGX is not available, no longer shrinks the scene with nothing to scale it back up.
- Upscaling without DLSS or FSR 3.1 also turns frame generation off, which needs their motion vectors.
- The HDR peak brightness slider reaches the setting's 10000 nits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Crashes: the render-target heap (HeapDef DynamicTextureAllocatorMem and its parent) grows with the widescreen
picture, so the pause menu no longer reads a null block at 5120x2160; texture uploads keep the game's memory mapped
while they copy it, so UnmapPhysicalMemory no longer pulls it away mid-copy; DLSS Frame Generation no longer crashes
in Streamline's set-up (physical devices and the window surface go through its proxies, its swapchain images are
counted first, resources are tagged per frame); a stale core-wait target is cleared before its thread is freed.
Freezes and races: a resize during a swapchain rebuild is kept, a failed frame generation swapchain is retried
instead of replaced by a plain one, three acquire timeouts in a row rebuild the swapchain, device waits take the
queue lock, a translator child that stops answering times out, an in-flight compute pipeline builds at normal
priority, a save Commit started before the window closes finishes and none starts after, a settings change just
before closing is saved, and a movie sample taken before a flush is dropped.
HDR: Enhanced HDR extends the game's own tonemap so highlights go above the picture's white (the hue slider,
which no longer applies, is gone from the menu); frame generation gets a HUD-less picture, the HDR flag and the HDR10
metadata under HDR too.
Memory: ASTC textures are encoded to BC3 unless the PC has 20 GiB of VRAM and 48 GiB of RAM, as Windows charges video
memory to the commit charge (a long session held 17 GiB of RGBA8 textures and 45.7 GiB of commit on a 32 GiB PC).
Shaders: the translator lowers spill-mode programs with more than eight uniform register groups in a wider spill
layout, and local loads and stores with a uniform-register address (36029 of the game's 36031 variants translate,
20 failed before).
The launcher release ships the shader tools and AMD's FidelityFX library (make_launcher.py --ffx); windres there gets
the toolchain on PATH.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
HDR is now a menu row: Off, Enhanced (the runtime's own HDR tonemap) or Native. Native reports an HDR10 display to
the game with the settings' peak and paper white; the game then renders its own HDR picture and presents it as PQ in
A2B10G10R10, which goes to the HDR10 swapchain as it is. The peak, game, HUD and (for Enhanced) hue rows follow it.
The build kit's link left out runtime/nx2navi.rc, so the game had no manifest and no icon: without per-monitor DPI
awareness, a 5120x2160 monitor at 150 % gave a 3413x1440 window that Windows stretched. It is compiled with windres
and linked again.
The movie decoder's frames are copied into the game's buffer by the host; the back end is now told of that write, so
the GPU's copy of the buffer does not keep older or empty bytes (green stripes in NV12).
A longjmp out of the game's JPEG decoder now logs the guest functions that called it, to trace a later crash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Frame generation now gets the picture without the HUD each frame: the upscaler's output before the HUD is drawn over
it, encoded to sRGB as the game's last pass encodes its picture and put through the same colour, scaling and borders
as the presented frame, so it tells the HUD apart and leaves it unwarped in the generated frames
(NX2NAVI_FG_HUDLESS_SHOW=1 shows that picture, a test). The performance overlay gains an FG row and the fps line the
display's rate, generated frames included. The device enables VK_KHR_get_memory_requirements2 whenever AMD's frame
generation is on, which crashed at start with DLSS upscaling.
DLSS Frame Generation through NVIDIA Streamline (vk_dlssg.c, and nx2navi_dlssg.dll from runtime/dlssg, built with
MSVC by tools/build_dlssg.py against the SDK kept outside the repository) creates the instance and device through
Streamline's proxies and calls its swapchain hooks; Streamline still fails in its plugin start-up here, so the menu
lists it only while it is set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With Frame generation set to AMD FSR 3.1 (after a restart), AMD's frame generation swapchain takes the place of the
present path's own: the device creates two more graphics queues for its presents and image acquires, the present
path acquires, gets images and presents through its replacement functions, and every frame configures generation
and records its preparation from the depth and camera motion vectors the DLSS or FSR 3.1 upscaling pass made (one of
them must be on). Generation pauses while the menu is open. On an RTX 5090 at a 60 fps game, about one frame was
generated for every frame drawn in play.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Upscaling choice gains FSR 3.1 Native AA, Quality, Balanced, Performance and Ultra Performance. FSR 3.1 runs
through AMD's signed amd_fidelityfx_vk.dll (loaded at run time; the build kit ships it) in DLSS's place, on the same
scene light, depth and camera motion vectors and at the same render scales. Its Vulkan back end needs
VK_KHR_get_memory_requirements2, which the device now enables when FSR 3.1 is chosen.
A half-size effect pass with three targets and a half-size depth was taken for a G-buffer, which switched every later
frame to the deferred frame shape: there the output pass of a smaller scene was never found, so DLSS Quality,
Balanced, Performance and Ultra Performance did nothing on a 16:9 picture. Such a pass smaller than the scene is now
left alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game sizes its scene each frame from a GPU-time controller. The new patch set res_fixed takes the controller's
fixed path and reads the scale from the patch page, so DLSS Quality, Balanced, Performance and Ultra Performance
render at 67, 58, 50 and 33 percent of the picture and DLAA at all of it. The set goes on once the controller has run
for a few frames, and the menu's Upscaling choice lists the modes and switches them live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NX2NAVI_GAME_RES (1440, 1800, 2160, 2880 or window, read at start) sets the picture's height; the widescreen patch
sets now also run at 16:9 when the height is not the game's, with the width following the shape and the scene 3/4 of
both as the game draws it. Measured at 2160p with 21:9: a 5120x2160 picture and a 3840x1620 scene at 60 fps.
The Screen tab's Scaling-to-the-window row and the DLSS rows become one Upscaling row (Bilinear, AMD FSR 1, NVIDIA
DLSS) over NX2NAVI_UPSCALER and NX2NAVI_DLAA (option_rows: opt_choice_index and opt_apply for a row over several
settings), with the FSR strength greyed out unless FSR is chosen and a Resolution row beside the widescreen one. DLSS
always uses NVIDIA's model for its scale (no model row). There are no DLSS quality modes: the game sizes its scene
from the picture by its own 3/4 share, and writing a smaller scene into wide_scene's constant left the light target
as it was, so every mode would render the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Screen tab gets the DLSS switch and its model choice after the picture rows. There is no quality-mode row: this
build cannot change the game's scene size, so every mode would render the same. The description says that DLSS works
on each frame on its own for now and takes effect after a restart; the model row is greyed out while DLSS is off.
tools/make_game.py compiles with tools/build_game.py's aliasing rule: -fno-strict-aliasing everywhere, since the lifted
code reaches guest memory through casts of one address to several types, and the runtime at -O2 (the lifted code
stays at -O1). x86-64-v3 is left out so that the kit runs on CPUs without AVX2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game used to start after at most 3 s of prewarm, so after a translator, GPU or driver change it met the rest of
its shaders in play and stalled on each one. It now waits for all of it: a warm start is ready in about half a second
and shows nothing, while a cold one shows a "Preparing shaders" screen in the menu's colours and the game's fonts, with
the shader and then the pipeline count and a bar. The shader and pipeline prewarm report their numbers every 100 ms.
The present path takes a frame without a game image for the screen. The RomFS opens on the first host-side read, so
the screen's fonts load before the guest's first file access. NX2NAVI_PREWARM_WAIT_MS now defaults to 0 (no cap). With
no progress for 60 s the game starts anyway and the prewarm finishes in the background.
Measured with an emptied cache: 2,988 shaders translated and 2,399 pipelines built in 9.4 s behind the screen, then
0 ms shader and pipeline waits in Kokiri Forest. Warm: the game starts 0.47 s after launch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
runtime/menu_assets.c reads them from the user's RomFS at runtime (nothing is shipped): .zs files with the dictionaries of Pack/ZsDic.pack.zs (streamed when a frame does not say its size), the fonts of Font2/Scalable behind their 8-byte header and per-word XOR key, and BNTX textures (Switch 2 archives say "Ounc" where Switch ones say "NX ") through the runtime's block-linear copy and BC4 and ASTC decoders. ui/menu_art.cpp decodes the shared interface archive on a worker at the first present and uploads each texture for Dear ImGui at its first use. The menu now uses ClarimoUDPE for its text (with a sixth more line height) and CetraDisplayPro for its title and tabs, Roboto merged behind both; umber, brown and gold colours; the game's ornamented frame along the window's edge (the art holds two sides and is drawn again turned for the other two); its cursor glow and line around the selected row (edges only, so the label stays readable); and its control-guide bar under Continue and Quit. Without the files it keeps Roboto and plain shapes. The window's pointer is an original gold arrowhead drawn at start for the system DPI.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opening and closing the menu, moving the cursor, changing a value, pressing an item, cancelling a dialog and changing tabs play the game's system sounds (Plus menu open and close, cursor, decide, cancel, tab move), read from the user's RomFS at the first use and decoded from BWAV (DSP-ADPCM or PCM16, 48 kHz) in runtime/menu_sound.c. They are mixed over the game's output in the audio callback with up to eight voices started without locks. The Sound tab gets a Menu sounds volume (menu_sound_volume, 0 to 100, default 80, under the output volume). A RomFS without the files stays silent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>