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>
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>
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>
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>
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>
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>
On an RX 580 the second device-local heap (2 GiB) takes what the first has no room for. On a GPU with one device-local
heap (an RTX 3050 Laptop, 4 GiB, 16 GiB of RAM) the budget wrapper and the texture placement sent everything past the
heap's reserve to system memory instead: 4096 and more texture images and every other allocation, held in the
process's commit (21 GiB). The system's commit ran out (31730 of 32023 MB), host memory imports failed, a pool was
left without a GPU buffer and the GPU wrote through its address: device lost. Now an allocation leaves out a full heap
only when another device-local heap has room, and a texture's second step allocates as before, past the budget, where
the driver keeps it in video memory or moves it out itself, and NX2navi's own VRAM pressure drops textures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-op loads steam_api64.dll from beside ocarina.exe, and the repository may not hold that file. Before Play and before a start from the Steam shortcut, when the file is not there, launcher/steamlib.py now copies one: one a release ships in its bin folder if it has one, else the newest one in the games of the player's own Steam libraries that Valve signed (WinVerifyTrust and the signer's name) and that has every call runtime/steam.c uses. Nothing is downloaded, the search takes a second or two once, and with no Steam or no suitable file the game starts as before and the Co-op tab says what is missing. The tab's two texts about the file say so in the four languages.
Checked: four new tests on made-up library folders (too old, unsigned, too deep, a second library, a shipped file first, none said once) and the launcher's 190 tests pass; on this PC the search found 40 files in 1.2 s, 25 suitable, and copied a Valve-signed one; that file's library version, an older one than the game was run with, signs in as app 480 and makes a lobby in a program without the game. Not run: Play in the launcher itself with this change.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
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>
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>
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>
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>
Auto gives half the physical cores less one, at most 6: 2 on 6 cores, 3 on 8 (the count measured best on an 8-core CPU), 5 on 12. Four cores or fewer get none, and so does AMD's Windows driver for RX 6000 and older, where a player's device was lost with workers on and the cause is not known. The menu's row lists Automatic first with the count it gives on this PC, and the log says how many were started and why.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rows keep their plain names. Their descriptions now say what the number counts and that 0 turns the setting
off: 0 to 4 for replay_pipe, 0 to 64 in steps of 4 for replay_small.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The rows are now CPU helper threads, Helpers work ahead, Helpers take small jobs and Start next frame early, each
with a short description of what it does and what it gained. Helpers work ahead and Helpers take small jobs are
switches: on writes replay_pipe = 4 and replay_small = 16, the values measured best, and another value set in
nx2navi.ini shows as on and stays.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
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>
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>
The stance site called page +0xf50, which the 0.10.0 line gives the water ripples above 60 fps: the second
registration ends every 1.1.1 start with "registered twice", the sprint button off included. It calls +0xf30 now, and
the comments say which slots the other sets hold. The set's switch is NX2NAVI_SPRINT_BUTTON_PATCH, not a name another
open request uses for a set of its own.
The stand-still timings count 60 Hz frames (frame_step_frames), so a toggled sprint no longer ends on a short stop at
high frame rates. A press is decided at its start for its whole length: kept from the game and the sprint's when it
begins in gameplay (B: while Link moves), else the game's alone, so a held B is not handed to the game half way and a
B that closes the pause menu starts no sprint. A sprint switched off with the button setting does not come back by
itself, Link's state is read only where the memory is readable, and the row, nx2navi.ini.example and the controls
page say that B is the jump under the game's Swap Jump Controls and name the button without assuming the layout.
tests\sprint_check.py covers the press latch, the menu press, 240 fps and the switch-off.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It can show a dark or flickering frame now and then, so the mode no longer turns it on with its other defaults. Its row under the mode's switch still turns it on for a player who wants it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
The default strength is 130 to make up for what two motors lose. A linear actuator that takes the game's value needs none of that, and 1.3 times the amplitude clipped the strongest pulses at the safe maximum. The HD path now divides by the default, so the setting scales from the game's own amplitude.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
A staging buffer that does not fit the staging pool's blocks waits for the oldest upload batch with the upload lock
held, and a replay submission that sends its uploads waits for that lock. The wait cannot leave the lock: the caller
holds the open batch's command buffer and the buffer it asks for belongs to that batch. What the wait costs on an area
load was not measured anywhere, so it is timed and logged now: the first four waits, then at every doubling of their
count, with the total and the longest.
Since staging stays inside the pool's blocks every batch is submitted at 64 MiB of staging, so upload_batch_mb = 0 is
that limit and not "no limit", and the values above it (the low-end mode's 256, the menu's 128 to 1024) change
nothing; a discrete card keeps 256 MiB of staging for the run, not 128. The settings table, the menu's text,
nx2navi.ini.example, the audit's table and the comments say so.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
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>
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>
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>
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>
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>
The three change what every card does: the asynchronous replay can show a dark or flickering frame now and then, the hot-pool rule moves pools into system memory on every discrete card, and -O2 makes each rebuild of the game several times longer. Their rows stay in the settings tab. The USB joystick mapping is described by the device's own name, and the tab's introduction says what its settings are for.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A pool read in place gives the GPU the game's bytes as they are when it reads them. The demoted pools were ones the
CPU refills every frame, which the game rewrote for the next frame while the replay and the GPU still drew the last:
small grass and moss flickered and slid over the ground, and with 0.8.0's mesh fallback every mesh flashed. Pools with
more than 64 KiB of CPU writes a frame (written pages and flushes) now stay mirrored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
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>