76 Commits

Author SHA1 Message Date
chrg6
1e8045b36b Bring the tone mapped HDR picture's settings back to the menu, with defaults that give the look it had while it was fixed.
The sixteen settings the fixed look took away are settings again: the tone mapper choice, gamma correction, mid grey, match tone slope, tone slope, hue clip emulation, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare, hue correction and colour grading. They have their rows in the settings table with the defaults and ranges they had, their rows in the Screen tab under HDR output's three brightness rows (none of them with the game's own HDR), their texts in the four languages, and their lines in the example ini. The tonemap library has the luminance mapper and the grading of the observer-response mapper again, and the support buffer carries five vec4 for it.

A player who changes nothing sees the picture as before. The observer-response mapper leaves each grading step out when its value is 1, and the slider values are divided, not multiplied by a rounded reciprocal, so 50 and 100 % arrive as exactly 1. Evaluated on the CPU in single precision, the shader with every default gave the same bits as the fixed one for 400,000 random scene colours, gains and whites, in HDR and for the SDR picture; the luminance mapper and the game's curve gave the same bits as before they were removed, and the observer-response mapper with its sliders moved stayed within 0.004 % of the pixel of its old result.

Gamma correction now counts with every tone mapper, 2.2 by default, which is what gave the fixed look its true blacks; before the fixed look it ran with the luminance mapper only and the other two showed the dark tones far above black. Its off value brings that lighter picture back for a player who asks for it. The game's own HDR does not take the setting and stays at 2.2.

Lines for these settings that an nx2navi.ini still holds from before the fixed look count again; the example ini and the release notes say so. Nothing of this was seen in the game.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-14 08:35:47 -05:00
chrg6
7a8bbad47e Remove the low-end GPU mode from the settings.
The user asked for it: the mode no longer gains frame rate, and the mesh shader fallback covers the cards it was written for. Its switch in the settings menu's Game tab is gone with the rows under it, and so are their 57 text ids and strings.

low_end_gpu itself does nothing any more. It is still a known key, so an older nx2navi.ini gives no unknown-key warning, and while it is on the log says once that the mode was removed in 0.10.2. The preset that gave nine keys other defaults is gone, and so is the rule that read the mode's keys only while it was on: each of them (mesh_emulation, tex_clobber, foliage, vram_heap_limit, vram_reserve_mb, upload_batch_mb, mirror_stage, mirror_hot, submit_finish_gpu and the others) is an ordinary setting with the default of its row or of its reader, so a line a player wrote by hand keeps counting and a file with only the switch runs the game as it runs for everyone else. guest_opt in nx2navi.ini counts for the next rebuild without the switch too (tools/make_game.py).

The recorder thread read the switch itself: on an AMD card without mesh shaders the mode turned it on. It is off on every AMD card now unless vk_recorder = 1 is set.

nx2navi.ini.example lists the former mode's settings as single settings, the 0.10.2 notes have a Removed entry and name the foliage fix of the commit before this one, and the comments that said what the mode sets say so in the past tense. tests/cfg_parse_check has the new reading of the switch and of the keys without it; the test matrix is at 90 ok with the same two not ok as before (installer_test, worktree_test), and a clean build compiles 169 runtime files and links. Not run in the game.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-14 06:13:18 -05:00
chrg6
b0488796a1 Tell in the log and the menu when RivaTuner Statistics Server's own limit holds the game beside another limiter.
With NX2NAVI_RTSS=0, frame generation or the driver's low latency, the game keeps RTSS out of its frame limit, but a
limit the player set in RTSS itself (its Global profile or the game's own: a frame rate limit, a frame time limit or
scanline sync) still holds the frames, on top of the game's limiter, the driver's or frame generation's. Nothing read
RTSS's settings in such a run: a first want that kept RTSS out never started its thread, and frame_step_cap_ns folded
the reasons into -1, so the log said "NX2NAVI_RTSS=0 or frame generation" for low latency too.

frame_step_cap_ns now passes the reason (rtss_decide.h RTSS_OUT_SETTING, RTSS_OUT_FRAMEGEN, RTSS_OUT_LOWLAT; the
unlimited branch too), and the kept-out line names it. A kept-out first want starts the worker as well. While RTSS is
kept out the worker never loads RTSS's hooks library and only reads: its shared memory, and once RTSS lists the game
the [Framerate] values of Profiles\<exe>.cfg, else Profiles\Global, with the system's ini reader, again only when
either file's write time changes. When one of them limits the game, the log names the file, the values and the other
limiter, once per change, and rtss_limit_state returns 4, which the frame rate row shows as a new note
(UI_MENU_FPS_RTSS_TWO, in English, Russian, Brazilian Portuguese and Simplified Chinese). A later want of a limit
(frame generation or low latency switched off) loads the hooks and hands RTSS the limit as before; NX2NAVI_RTSS=0
stays read-only for the run. Pacing does not change.

Not reproduced before the change: a stacked limit needs RTSS running in a play-test. tests/rtss_worker_check.c gains
three scenarios against the fake RTSS (a Global limit with NX2NAVI_RTSS=0, scanline sync in the game's profile under
low latency and then a limit, frame generation with no limit of RTSS's own), and tests/rtss_limit_check.c builds
rtss_decide.h alone and checks which settings hold the frames and their text. Both pass, with frame_step_check and a
compile of every runtime file; -Wall adds no warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 18:41:39 -05:00
chrg6
26d26babe5 Bring public main's bug capture on F8 and its removal of the Steam co-op into the 0.10.0 line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	docs/architecture.md
#	launcher/steamlib.py
#	launcher/ui/app.py
#	runtime/boot.c
#	runtime/cfg.c
#	runtime/flight.c
#	runtime/flight.h
#	runtime/gfx_present.c
#	tests/launcher_test.py
2026-11-13 09:52:43 -05:00
epicmeme546
f5da269add Remove the experimental co-op through Steam.
This takes back the three commits that added it (cd2f9f1, e60677e, a01910d): its runtime layer and Steam binding, the Co-op tab and what it drew over the game, the launcher's copy of Steam's library file, its settings, tests and docs. The tree is as it was before them, with the bug capture that came after kept: its note on screen no longer makes room for co-op's lines, and dbg_area, which the co-op had brought and the capture uses, stays.

The four settings of that co-op join the retired names, so a settings file that still has them logs no unknown key. A steam_api64.dll that the launcher put beside the game is left where it is: nothing loads it any more.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-13 09:08:40 -05:00
chrg6
b912d72b87 Remove the Video memory use row; its low behaviour is the only one now.
Released textures go once they have gone unused for the hold, and what a switched-off upscaler or SMAA held is freed, with no setting to keep them. A vram_use line left in a settings file is known and read by nothing. Memory use is to be decided by the card's and the system's budget instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 06:23:01 -05:00
epicmeme546
4fd70e398f Make F8 write a bug capture and add a test run that starts from one.
F8 now always writes a folder under logs/captures: the area, Link's position and rotation, the camera of the frame on screen and the game's own, the presented frame as a PNG, frame times around the press, the machine, the settings that differ from the defaults, the end of the log, the save, and the game's flags and texts. A note on screen names the capture. The flight recorder's F8 dump goes into the capture's folder.

NX2NAVI_CAPTURE_REPLAY starts a run on a capture's settings, teleports to its area, walks Link to where he stood and puts the free camera where the camera was; tools/capture_run.py prepares the profile from the capture's save, runs it and compares the two captures. docs/bug-capture.md has the files and the steps.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-13 06:22:18 -05:00
chrg6
da65aa6099 Bring public main's Steam co-op, the removal of the old co-op and the launcher's Steam library file into the 0.10.0 line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 04:37:01 -05:00
epicmeme546
cd2f9f12be Add an experimental co-op through Steam: rooms, invites, names in the world, chat and shared progress.
Up to four players, each in their own game and save, meet in a room. Rooms are Steam lobbies and the games talk through Steam's networking, so nobody forwards a port and nobody runs a server. The game signs in to Steam as its test app (480), so Steam shows the player as playing Spacewar. Off by default; the menu has a Co-op tab with Create a room, Join a room by its ID, Invite a friend, the players, chat and Leave, in English, Russian, Brazilian Portuguese and Simplified Chinese.

runtime/steam.c loads steam_api64.dll from beside the exe at run time and declares the C functions it calls itself; nothing of the Steamworks SDK is in the repository, and a player copies the file from a Steam game they have. runtime/coop.c is the room: the lobby's owner is the host, lets games of the same protocol and game build in and lists them, and every game takes positions, chat and progress only from the players on that list. runtime/coop_game.c reads Link from the Player actor and shares progress, items, songs and quest items through the game's own flag routines, each value only ever raised; small keys, rupees, ammunition and hearts stay each player's own, and the save is copied to coop-backup before anything of a room reaches it. The other players show as a name with a distance where their Link stands, projected with the upscaler's camera.

One Steam account cannot be in a lobby twice, and this PC has not the memory for two games, so the room and the game side were run with one game and tests/coop_peer.py as the second player over runtime/coop_local.c, a stand-in for Steam on 127.0.0.1 that only the environment turns on: the peer was let in, saw the game's Link in Hyrule Field, chat went both ways, the save was copied, one flag the save lacked was set and a flag the game does not share was refused. On Steam itself one game signed in as app 480, made a room, showed its ID and the player's Steam name on the Co-op tab and left. Not run: two Steam accounts in one room, an invite, a join by room ID over Steam, a host that leaves, a removal, and the menu's text probe on this build.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-13 04:16:17 -05:00
epicmeme546
c84c035939 Remove the co-op of 0.8.0: its runtime layer, relay, menu tab, settings, tests and docs.
The hidden beta shared story progress through a relay a player had to run and never showed the other players. A new co-op replaces it. Gone are runtime/coop*.c and their headers, the menu's Co-op tab and its texts in four languages, tools/coop_relay.py, the co-op tests, docs/coop.md, the [coop] section of nx2navi.ini.example, the --probe-coop mode, the coop trace item, and Monocypher, which only this layer used.

The old settings stay known as retired keys, so an nx2navi.ini that still sets them logs no unknown key, and they stay on the list of private settings, so a bug report keeps a room's secret, a relay's address and a name out. The platform layer's UDP sockets and fs_save_snapshot stay for the new co-op; cfg_ini_now and fs_profile_path, which nothing else called, are removed.

Checked: the game builds and starts, the menu shows its four tabs, --probe-cfg and --probe-fs pass, and the launcher's private-settings test passes. tests/save_share_check.py builds without its co-op stub; it cannot run from this worktree because the folder's path is longer than Windows allows, on main too.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-13 03:37:26 -05:00
chrg6
2bbdb25de7 Add a text speed setting for the game's message windows on 1.1.1.
The patch set text_speed scales the typewriter speed of a message the game lets the player skip (fast, three times as
fast, its scripted waits as long) or runs the game's own skip on the first frame of each page (instant). Messages the
game marks unskippable, such as those that turn their page on a timer, keep the game's speed. The setting is live and
has a row on the Game tab in all four menu languages; with it at normal the set is off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 00:53:22 -05:00
chrg6
e89cc4d430 Let frame generation run without a temporal upscaler, from inputs made for it alone.
With frame generation running and no DLSS or FSR upscaling, vk_dlaa.c arms and ends the frame in the same places and,
where the upscaler's evaluation would run, makes only frame generation's inputs (fg_only_frame): the scene's depth and
camera found as before, motion vectors from the same pass at the scene's size, no jitter at all, and the picture without
the HUD copied from the game's own scaled picture. DLSS Frame Generation and AMD's frame generation no longer need an
upscaler set; NVIDIA's through NGX still needs DLSS at the start and then keeps running when the upscaler is switched
off. The menu's Frame generation row stays usable with every Upscaling choice and the Upscaling row keeps frame
generation as set, with texts in all four menu languages. The log says at the start and at each change whether frame
generation takes its inputs from the upscaler or runs without one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 00:49:30 -05:00
chrg6
c6f2484ef9 Add Native (100%) to the Upscaling row: the scene drawn at the picture's own size with no upscaler.
The row writes scene_share = 1 with dlaa = 0, which the start already sized the render-target heap for; widescreen.c now
reads the share live, says when the heap needs a restart for it (the row's note and the Restart count), and logs the
scene size, the picture size and how the scene is scaled once the fixed share takes over and at each change. The
stand-in copies a scene of the picture's size texel for texel with a nearest sampler, and the performance overlay shows
Ocarina's real scene size instead of the other builds' placeholders. scene_share gets its settings row and its ini
text; the row's texts are in all four menu languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 00:43:03 -05:00
chrg6
f2849c8b26 Merge main's tail_early, replay_small and Testing rows into the 0.10.0 line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	runtime/cfg.c
#	runtime/vk_exec_xfer.c
2026-11-13 00:26:53 -05:00
chrg6
7a952c0bdf Make the Video memory use row Low or High, and describe the new Low in the menu, the ini example and the docs.
Low, the default, lets released textures go after the hold and frees what switched-off upscalers and SMAA hold; High
keeps both for the session. The row shows Low for any setting but high, the old balanced among them. The descriptions
in all four languages no longer promise simpler surfaces or missing objects, since the default no longer trades them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 00:12:31 -05:00
epicmeme546
3d61c33b8b Give the other experimental performance settings rows under the Game tab's Testing heading.
Worker groups in flight (replay_pipe), Small groups to one worker (replay_small) and Early frame end (tail_early)
sit under Replay workers. A row whose setting does nothing without another one is greyed out and says which it needs.
The example settings file and the draft release notes name the rows, and the notes gain the replay_pipe and
replay_small entry.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 23:58:52 -05:00
chrg6
ef6efba7db Add a Video memory use row: Low, Balanced or High.
Balanced, the default, is what the earlier commits made the default. High keeps what DLSS, frame generation's
HUD-less picture, FSR 1 and SMAA made after they are switched off, as before them. Low also sets, for the keys the
player leaves unset: released textures keep at most 1 GiB (tex_released_mb), textures give way from 70% of the video
memory budget (tex_vram_pressure) and the shared pipeline list is built at first draw (pipe_seed_boot = 0). Unsampled
live textures are not dropped in any of them: that eviction left the ground see-through for good. The values come
after the device profile and the low-end mode (cfg.c VRAMLOW); the row is in the Screen tab under Texture detail and
takes effect after a restart. The architecture notes describe the new memory report lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 23:21:17 -05:00
kendo
54fa3bc0c7 Offer only L3 and the game's B button as sprint buttons, and name B as the Nintendo layout's bottom button. 2026-11-12 22:55:14 -05:00
kendo
6faf4bd7f5 Add a sprint button: a setting that makes Link sprint without rolling first, as a toggle or while held. 2026-11-12 22:54:58 -05:00
chrg6
191a439fe4 Add a Low latency row to the menu's Screen tab.
Off, On, and On with boost where the mode is NVIDIA's, in English, Russian, Brazilian Portuguese and Simplified
Chinese. The row is greyed out with the reason where the mode cannot run: a driver with neither extension, a run in
which DLSS frame generation brings its own low latency, and NVIDIA's while a frame generation presents the frames. A
mode the driver refused says so under the value.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 22:29:59 -05:00
chrg6
aeb6c0d5c1 Add the HD vibration row to the Controls tab and describe HD rumble in the controls guide.
The row switches rumble_hd and is greyed out, with a note of what it needs, while the gamepad in use is not a Switch
Pro Controller or Joy-Con. Its texts are in all four menu languages.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 22:28:36 -05:00
chrg6
85089408f0 Merge main's replay workers default and menu row into the 0.10.0 line.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 22:06:04 -05:00
chrg6
264dbe42cc Merge the stability and memory pass into the 0.10.0 line.
Conflicts: the settings key lists and section maps take both sides' entries.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 21:49:17 -05:00
chrg6
cd0b1a387d Do the whole warm on the boot screen at the first start after a driver change.
A start that finds the pipeline cache written by another device or driver has nothing built, and everything was
compiled beside the game: an 8-core integrated GPU drew fewer than 600 frames in its first 11 minutes. Such a start
now waits on the "Preparing shaders" screen, with counts, for every recorded pipeline, for the compute pipelines on
every core at normal priority, and, when a sample says the driver's own cache lacks the prepared stage libraries, for
all of them on every core. The memory gate still holds for the builds, and the library builds write the preparation's
journal while the start waits, so a variant the driver's compiler dies on is skipped from the next start on.

boot-warm-owed.txt beside the caches names the GPU and driver: a start closed during the warm is followed by one that
goes on with it, three at most, and "done" keeps later starts ordinary while the cache file still carries the earlier
driver's header. When the boot stops waiting (prewarm_wait_ms, or a minute without progress) the builders step down
to the background's thread counts and idle priority. prewarm_full = 0 turns the warm off.

With nothing in the driver's cache the probe took 425 s on an RTX 5090 with 16 CPU threads: 87 s for 5,775 recorded
pipelines, 19 s for 4,075 compute pipelines and 317 s for 42,231 prepared variants.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 20:03:12 -05:00
epicmeme546
94dd340fee Turn the replay workers off by default again, and set them in the settings menu's Game tab.
Players' games crashed with three workers on by default in 0.9.1 and 0.9.2: an RX 6700 XT lost its device after
about 18,000 frames, its last mark a compute dispatch of the replay, with an occlusion query slot found unavailable
after its fence just before. The cause is not known yet, so replay_workers is 0 again unless a player sets it. The new
Replay workers row (0 to 8, experimental) writes replay_workers and waits for a restart, as the other restart-only rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 14:48:47 -05:00
chrg6
3123d40017 Offer the low-end GPU mode only on the cards it was written for.
On a card with mesh shaders and 8 GiB of video memory or more the Game tab's switch cannot turn the mode on; it stays usable while the mode is on, to turn it off again. A player with every setting at its highest had it on on a GeForce RTX 5090, where the mode moved the sun shadow map and the scene depth to memory of their own and the ground's shadow became a dark patch with straight edges that turned with the camera.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 14:28:36 -05:00
chrg6
26f3dc47ec Show the low-end GPU mode's settings under its switch in the Game tab, and read them only while the mode is on.
The settings had a tab of their own; they are rows of the Game tab now, listed under the switch once the mode is on after a restart, and the tab is gone. With the mode off every one of them reads as unset, whatever nx2navi.ini or the environment says, so nothing of the mode acts unless a player turned it on; the build reads guest_opt the same way. The notes describe that, and take the white edges in towns and the dropped textures from the known issues to the fixes.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 13:59:43 -05:00
chrg6
d2fbaba1c3 Put the low-end GPU mode in the settings menu's Game tab, marked experimental.
The row sets low_end_gpu, read at start, and says what the mode turns on and that it was measured on one card; its text is there in the menu's four languages. The key is decided again once the settings file is read.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 13:06:02 -05:00
Tobim6
39db2ae2a1 Video memory: every VMA allocation by the heaps' live budgets, with room kept for FidelityFX
The heap limits were fixed at device creation, and only texture images looked at the budget. FidelityFX allocates
outside VMA (FSR 3.1 contexts, frame generation): 882 MiB of it with 4644 MiB of VMA's filled the 5526 MiB budget of
heap 0 while heap 2 had 1757 MiB free, and an upload submit failed with VK_ERROR_OUT_OF_DEVICE_MEMORY. vmaCreateImage,
vmaCreateBuffer and vmaAllocateMemory now go through vk_vma_budget.c, which leaves out a device-local heap whose
driver-reported use is within NX2NAVI_VRAM_RESERVE_MB (default 512) of its budget; a Lama patches row sets it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:23:16 +01:00
Tobim6
306de50589 NX2NAVI_FOLIAGE (full, reduced, off): reduced skips the indirect mesh draws with a task stage (small grass and moss), off every indirect mesh draw (all foliage); a Lama patches row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:23:05 +01:00
Tobim6
4718997313 NX2NAVI_SUBMIT_FINISH_KINDS: a settings row (default 31) and a Lama patches row, all result kinds or all but events.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:23:04 +01:00
Tobim6
0b4fdac38d Defaults from the RX 580 runs: asynchronous replay, mesh emulation, mirror_hot 25, freeze_idle 1200, native zstd, game code at -O2; freeze and zstd and replay rows in the Lama patches tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:22:57 +01:00
Tobim6
55ab90c66e Mesh emulation: the stages of a pass's emulated draws run before the pass, in a command buffer of their own.
Each emulated mesh draw ended the pass, ran its stages between four barriers and began the pass again: a pass of 20
emulated draws became 20 passes, and on an RX 580 6 to 7 ms of a frame's 30 ms of GPU time went to the gaps between
passes (gpu_passes in Kokiri Forest: 8.7-10 ms per frame outside the game's passes and dispatches, 6.3-7.7 ms of it in
273-306 gaps with barriers only; 71 emulated draws a frame, 36 of them indirect). Now the first emulated draw of a pass
ends the part there (vk_exec_split_now), so the pass begins a new part, and the stages of every emulated draw of that
pass, the indirect ones' ring entries included, go into the part's pre-pass command buffer (vk_sync_pre), which the
queue runs after the mirror prologue and before the part; the pass is not split again. NX2NAVI_MESH_EMU_BATCH=0 (Lama
patches: Mesh emulation before the pass) brings the split back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:22:35 +01:00
Tobim6
9259149527 Settings menu: a Lama patches tab with the settings of the RX 580 patches
Released textures kept, top texture levels left out, hot pools in system memory, the heap limit, the upload batch
limit, the kept ASTC buffer, mesh emulation and its arena and task records (greyed where the GPU has mesh shaders),
the overwritten-texture fix, the vector index fix, -O2 game code, pass times and the frame dump key. Each key gets a
row in the settings table; make_game.py reads guest_opt from nx2navi.ini when the environment does not set it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:22:31 +01:00
epicmeme546
1c654ba0c1 Let the Anti-aliasing row's Game SMAA, Off, FXAA and SMAA run together with FSR 4, FSR 3.1 or DLSS, and show the anti-aliasing in the performance overlay.
Choosing one of them while a temporal upscaler was set turned the upscaler off, and the picture fell back to bilinear scaling. vk_aa.c no longer keeps the game's SMAA while DLSS or FSR runs. The chosen pass replaces the game's composition as without them: in the native modes after the upscaler, and in the scaling modes before it. In the menu, a choice leaves the upscaler and frame generation alone, and the row notes that the upscaler's TAA runs too. Picking DLSS or FSR under Upscaling keeps the Anti-aliasing choice. The TAA entry, listed while no temporal mode is set, now sets FSR 4 Native AA where FSR 4 runs instead of forcing FSR 3.1. The overlay gets an Anti-aliasing line under Upscaling, for example "TAA + SMAA (Ultra)".

Checked on an RX 7900 XT in Hyrule Field with FSR 4.1 Native AA and Quality, and with FSR 3.1. The log has "the game's SMAA replaced by" smaa, off and fxaa while FSR 4.1.2 upscales, and the pictures are right.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:23:25 -05:00
chrg6
bfe88e503a Add one anti-aliasing setting: the game's SMAA, off, FXAA, SMAA at its Ultra preset, or TAA.
Ocarina anti-aliases every frame with its own SMAA in three compute passes after the bloom composite. The new aa
setting (nx2navi.ini aa, the Screen tab's Anti-aliasing row) keeps that by default. off, fxaa and smaa leave out the
game's edge and weight passes and replace its composition with a pass of our own that writes the same square-rooted
RGB9E5 words into the scene target: a plain copy, an edge smoothing written here, or the SMAA reference implementation
with its area and search textures (MIT). taa is DLSS or FSR 3.1 at the scene's size, which the row sets through the
upscaler; while either runs, the game's SMAA stays whatever aa says. The game's passes are known by a hash of their
guest code. The shader build now also rebuilds a header when a file its source includes changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:09:16 -05:00
chrg6
3529eff949 Offer both kinds of HDR in the menu again: the tone mapped picture and the game's own.
The HDR row lists Off, RenoDX (the game's picture tone mapped to the display, the one fixed look) and Native (the game's own HDR, taken up at the next start), with the peak, game and HUD whites under either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:06:30 -05:00
chrg6
309e412f1f Make HDR one fixed look with true blacks, and keep only its on switch and the three whites as settings.
HDR on is the game's scene through the observer-response mapper, anchored to the game's own mid grey and tone slope, with half of the hue bend of the game's clamp: the defaults the menu had. The tone mapper choice, the mid grey, the tone slope and its match, the hue clip, exposure, highlights, shadows, contrast, saturation, highlight saturation, blowout, flare, hue, grade and gamma are gone from the menu, the settings table and the example ini; a file that still sets them works as before, the keys read by nothing and logging nothing. The menu's HDR row is Off and On, with the peak, game and HUD whites under it. The game's own HDR is still there for a file that sets hdr = native, and the row lists it only then. The luminance mapper and its grading code, which no setting reaches any more, are removed from the tonemap library, and the support buffer carries one vec4 for it.

The dark tones of a dark scene sat far above black: the present showed the game's sRGB-encoded picture as that encode decodes, linear near black, while the game is graded for a display of gamma about 2.2, and since the observer-response mapper became the default the 2.2 correction ran with the luminance mapper only. A scene value of 0.003 came out at 0.29 nits at a white of 350 nits, where a 2.2 display shows the game at 0.015. The present now always shows the picture as a 2.2 display would, and the tonemap aims its peak lower by that correction's inverse, so the brightest light still reaches the display's peak; the same value comes out at 0.017 nits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:06:30 -05:00
chrg6
7e07e13bac Add co-op, off by default: rooms through a relay, progress sync between the players' own saves, and a Co-op tab.
Each player keeps their own game and save. One player makes a room, the others join it with its secret, and story flags and, by the room's rules, items, songs, quest items, small keys (a shared pool), rupees and ammunition reach every save. The layer is a host thread with one UDP socket to a relay the players run themselves (tools/coop_relay.py, Python's standard library only); the messages between the players are sealed with a key from the room's secret (XChaCha20-Poly1305, Monocypher 4.0.2 vendored with its licence and a row in THIRD_PARTY_NOTICES.md), every change is signed by the player who made it, a player accepts a room by its members' keys and the key of its rules, the save is copied to coop-backup before anything is applied, and values are set through the game's own routines between frames. The Co-op tab makes, joins, accepts, removes and leaves, and the menu takes typed text for the name, the relay and the secret. This is the contributor's pull request #518 on this line.

Nothing reaches the network while co-op is off: the socket is opened, and the relay's name looked up, only after co-op is on, a relay is set and a room is made or joined. There is no default relay.

Left out of the pull request: the other players drawn in the world (the "puppets") with the copied engine they need. They were unfinished, added two patch sites to the game's per-frame update that could not be put through the differential tests here, and their file helpers clashed with the ones this line already has. So no position is sent: the POS message's codec stays, nothing sends it.

Changed against the pull request:
- The menu's test hook drives the Co-op tab only when it comes from the environment. From nx2navi.ini it could turn co-op on, join a room and accept its offer.
- The log's label for a room is hashed from the secret instead of being its first three characters.
- A game that has not saved yet has no save to copy, and takes nothing from the room until it has saved once; that used to count as a copy made.
- A PING's token no longer carries the PC's uptime.
- On Windows the maker's key file and its folder get an access list with the player's account alone.
- The tests that link the platform layer get the socket library, and the save test a stand-in for the title screen's hook.

Checked on Windows, where the pull request had never been compiled: the runtime builds and links; --probe-coop passes (sockets on 127.0.0.1, the codec against its byte vectors, a fuzz of every parser, two sessions through a hostile mock relay, a relay restart, the sync engine); tests/coop_relay_test.py passes its 61 tests, with the probe through the real relay on 127.0.0.1 at 0 and 30 % loss; tests/coop_page_check.py passes and its deliberate fault fails 48 checks. Not checked: two games in one room (one game runs at a time here), any real network, the tab in the game.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 00:11:41 -05:00
chrg6
097b5e75b4 Merge the pull request intake: fewer than six cores, a comma in the game folder's path, the pipeline cache's size check, video memory and commit handling, and smaller fixes.
The two sides both made the wait after a frame's first submission cover the write-backs under shadow storage; the rule is kept in one place, vk_sync_wb_split.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 23:35:05 -05:00
chrg6
f29e34e7a0 Make the observer-response scene mapper the default HDR tone mapper, anchored to the game's mid grey and tone slope.
The HDR tonemap gains a mapper built on a finite cone response in LMS: scene grey 0.18 lands where the game's curve puts it and rises through it with the slope of the game's curve, and the hue bend of the game's clamp is given back to bright colours. It takes the scene before the game's exposure gain and the gain itself, which the splice now finds in the game's shader. The luminance mapper stays selectable and the game's own curve stays the third choice. The mapper fits the Rec. 709 gamut here, as the game's scene target holds no negative component. New settings: the tone mapper's mid grey, tone slope match, tone slope and hue clip emulation. The gamma correction on the present applies with the luminance mapper only.

Not part of this: eye adaptation, the anti-aliasing and reconstruction shaders, bloom and local exposure sliders, the output colour space choice and a 16-bit float scene target. Checked by build and numerically against the source of the port; the picture on an HDR display has not been judged. THIRD_PARTY_NOTICES.md names the source.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-10 23:20:31 -05:00
chrg6
10cfbd9ea1 Stop telling an RTX 50 player that frame generation needs an RTX 40 card, and log why it is unavailable.
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>
2026-11-10 23:14:00 -05:00
chrg6
caf50ecf1c Turn the lazy write-back off by default on AMD's driver for RX 5000 and 6000 cards, and say in the log which settings a run had.
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>
2026-11-10 15:14:25 -05:00
chrg6
381264165a Turn the lazy write-back on by default on Windows and take its row out of the settings menu.
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>
2026-11-10 10:52:11 -05:00
chrg6
cdaab81727 Merge the texture detail setting into the combined test line.
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>
2026-11-10 09:51:33 -05:00
chrg6
364a40ae9b Add the Texture detail row to the settings menu's Screen tab, in the four menu languages.
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>
2026-11-10 09:38:08 -05:00
chrg6
1f2b320736 Say "after restart" in the menu only while a restart would change something, and count the waiting settings on Restart.
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>
2026-11-10 09:14:23 -05:00
chrg6
449b5f73b0 Merge the Linux pull requests into the 0.7.0 release line.
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>
2026-11-10 07:31:55 -05:00
chrg6
9dbfed207f Turn the lazy write-back off by default and offer it in the settings menu as Experimental FPS boost.
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>
2026-11-10 07:12:23 -05:00
chrg6
75b0dca907 Add Steam Deck button prompts beside the Xbox and PlayStation ones (prompts = steamdeck).
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>
2026-11-10 06:53:30 -05:00