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>
The review of the commit that taught nx2navi.ini's readers off, false and no found three readers it left behind. cfg.c
lowend_of reads low_end_gpu through raw_get, which skips switch_word, and still took anything but "0" as on: with
"low_end_gpu = off" cfg_get, cfg_bool and the menu said off while cfg_low_end_active() was 1, so the mode's defaults
and its LOWEND_ONLY keys stayed in effect, and F-7's check, which now accepts the word, logged nothing. lowend_of now
takes the value through switch_of as cfg_bool does: 0 and the off words are off, anything else set is on (an
environment value "0 ; x" stays on, as cfg_bool reads it). tools/make_game.py read the file on its own too:
shader_carry took only "0" as off and kept a comment in the value, so with "shader_carry = off" or "shader_carry = 0 ;
note" an update skipped the full shader preparation and left the shaders to a game that no longer carries them over;
guest_opt_ini took low_end_gpu as on for anything but "" and "0". shader_carry now reads the switch as the game does
(the environment wins over the file and is taken as written, the file's last line with a value wins, a comment after
a blank is cut unless the value is in double quotes, make_game.py ini_choice, and 0, off, false and no in any case are
off); guest_opt_ini keeps its cut and takes the same words as off.
tests/cfg_parse_check gains a lowend case (low_end_gpu = Off, = 0 ; note and = yes from the file, FALSE and "0 ; x"
from the environment; two of them fail on the old lowend_of), and launcher_test's MakeGameTest two tests of
shader_carry's and guest_opt_ini's reading, which fail on the old make_game.py. cfg_rows_check reports 0 problems, and
the -Wall compile of cfg.c shows no warning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that nothing on the release path ran the new export check: tools/make_launcher.py build_app and
tools/make_kit.py copied whatever --dlssg DLL they were given, and the release script still named the DLL of 0.8.0,
which lacks nx2sl_reflex2. Shipped that way, Reflex would never hold the frame limit while players still got this
branch's other changes. make_launcher.py check_dlssg and make_kit.py main now run build_dlssg.check on the --dlssg DLL
beside the translator's check, before anything is resolved, downloaded or deleted; build_dlssg.check also stops on a
missing file and on a file that is no DLL, with a message instead of a traceback. tests/dlssg_exports_check.py adds a
DLL with every declared export (it passes), the two-export DLL, a missing file and two files that are no DLL (they
stop, naming why), make_launcher.check_dlssg on both DLLs, and that both scripts' main makes the check before
streamline_pin.resolve and shutil.rmtree; it fails when the call is taken out of make_launcher.py main. The DLL built
from this branch passes make_launcher.check_dlssg; the DLL of 0.8.0 is refused, naming nx2sl_reflex2.
docs/launcher.md and docs/building.md "Checks" say so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runtime takes a missing optional export (nx2sl_reflex2, nx2sl_frames_max) for an older DLL and leaves the
feature out with one log line, so a release that ships a DLL built from older sources than its runtime loses Reflex's
frame limit without an error; the release script took the DLL of 0.8.0 from a fixed path. After the build,
build_dlssg.py now reads every PFN_nx2sl_<name> in runtime/dlssg/nx2_sl.h and the DLL's export directory (a small
PE reader of its own, PE32 and PE32+) and exits naming each export that is missing; --check <dll> makes the same
check without building. tests/dlssg_exports_check.py builds a DLL with two of three declared exports with MSYS2's
clang and checks that the third is named, and that nx2_sl.h's own declarations are read in full; docs/building.md
"Checks" and docs/launcher.md name both. The DLL built from this branch with the pinned Streamline 2.14.16 SDK passes
(15 exports); the one built before nx2sl_reflex2 fails, naming it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/cheats.py decodes the pc-relative instructions of a code cheat at main's base and held that base as its own constant, 0x7000000000. It equals recomp/layout.py FIRST_BASE today, where main is the first module of ORDER, but a change of the layout would have left the cheats decoding at the old base, and AGENTS.md has recomp/layout.py alone decide module bases. BASE is now layout.FIRST_BASE. cheats.py is part of the mods' lift key (make_game.py mod_digest), so an install with code cheats lifts once more at its next build; an install without them is not affected.
Not reproduced before the change: no fault today, the two values being equal. Checks that passed: tests/launcher_test.py (218 tests, a new one in which cheats.BASE follows a changed layout.FIRST_BASE, which fails before the change) and tools/check_content.py --staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make_game.py modules_c writes gen/modules.c with the sha256 of the player's ExeFS modules and the install's absolute paths, and wrote it with no comment at all, so check_content.py, which refuses a file by its marker, its suffix, its name or a key line, would have let it into a commit. AGENTS.md wants the marker on every file the generator emits. Its first line now carries the marker and says what wrote it. It costs players one compile of modules.c and a link at their next build: make_game.py is part of the compile key, and every other object comes from the object cache.
The other half of the finding, the marker on the lift's layout.h, waits: it is a change to recomp/layout.py and recomp/gen.py, whose digest keys every player's lift (a full lift at the update) and the execution profile's fit, so it belongs to the next release that changes recomp/ anyway.
Not reproduced before the change: found by reading the code; the audit's probe showed modules_c without the marker. Checks that passed: tests/launcher_test.py (217 tests; the modules.c test now asserts the marker on its first line and that check_content.problems_for refuses the file, and fails before the change), tests/host_probes_test.py, tests/pgo_build_test.py and tools/check_content.py --staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kernel.c's preemption stub has two bodies since 00f06e66: kern_ap_stub_ymm saves YMM0-15 with vmovdqa and is chosen only when ap_init's cpu_keeps_ymm() finds AVX, OSXSAVE and XCR0 bits 1 and 2, and kern_ap_stub_xmm serves every other processor. tools/check_isa.py's DISPATCHED listed only sha256.c's blocks_ni, so it took the YMM body for an undispatched AVX function and stopped every tools/build_game.py --cpu v2 build. The entry names that check. Players' builds (make_game.py) run no such check and were not affected.
Reproduced before the change: kernel.c built with the ucrt64 clang at -march=x86-64-v2 failed tools/check_isa.py with "kern_ap_stub_ymm ...: vmovdqa x32, vzeroupper x1", and passes now. Checks that passed: the new tests/check_isa_test.py (kernel.c and sha256.c built with -march=x86-64-v2 and without -march pass; an AVX function not in DISPATCHED still fails; it fails before the change) and tools/check_content.py --staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tools/check_platform.py scanned the raw text of each shared runtime file and knew nothing of preprocessor branches, so the Win32 use that input.c, mods_probe.c, rtss_limit.c, vk_cache_file.h, vk_dlaa.c, vk_dlssg.c, vk_dump.c, vk_ffx.cpp, vk_ffx4.cpp, vk_fg.cpp, vk_fg4.cpp and vk_pipeline.c keep behind #ifdef _WIN32 made it fail on every run, at f1a2a64e already: a check that always fails guards nothing. It now leaves out the lines of a Windows-only branch (#ifdef _WIN32, #if defined(_WIN32), #if _WIN32 up to their #else, #elif or #endif, the #else of #ifndef _WIN32 or #if !defined(_WIN32), and conditionals nested in them), found on the text with its comments blanked. Every other branch is still scanned, #elif defined(_WIN32) and #if defined(_WIN32) && X among them, which can fail the check but never lets a use through. docs/building.md lists the check and its test under "Checks".
Reproduced before the change: the check exited with 1 and these 12 FAIL lines on 2f640fb8; it now exits with 0. Checks that passed: the new tests/check_platform_test.py (guarded uses pass, unguarded ones fail, the repository's runtime passes; it fails before the change) and tools/check_content.py --staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of the packaging guard (b4c21998) found that D-1's optional behaviour probe was left out without a reason:
check_built compares only nx2navi_xlate.id with the checkout's sources, so an old exe beside a current .id still
passed, and while the exe build record waits (G-12), a probe of the exe is the only landable check on the exe itself.
It also passes the release scripts' folder, as that translator declares 40960 entries and clamps to 40959.
tools/xlate_smoke.py gains pool_check(exe): it sends a hand-assembled fragment shader that reads a texture handle from
an input (IPA R4 = a[0x80]; TXQ.B R0, R4; EXIT), which the translator reads from the texture pool array, and refuses
an exe that does not run or answer, whose res.json texture pool entry (pool 1) has another array length than
tools/xlate/Program.cs TexturePoolLength, or whose SPIR-V lacks that array or a GLSL.std.450 UMin by length - 1
(spirv_clamps reads the module itself, no disassembler needed). xlate_smoke.py runs it last and takes --exe.
make_launcher.check_xlate runs pool_check on the exe after check_built on Windows, still before anything is
downloaded, built or deleted.
Not reproduced before the change beyond the audit's probe. On this PC, pool_check passes on work\xlate_6866a13e (and
make_launcher.check_xlate on that folder), refuses the 0.9.2 translator (identity 4d051299) with "declares the texture
pool with [32768] entries, where its sources say 40960", also through check_xlate beside a current .id, and
xlate_smoke.py --exe passes on work\xlate_6866a13e. tests/xlate_package_check.py now checks spirv_clamps on a module it
assembles, that pool_check and check_xlate refuse an exe that does not run beside a matching .id, and that a
translator built in build/xlate passes when there is one; it passes. docs/building.md names pool_check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review of the packaging guard (b4c21998) found that the docstring line it edited in tools/make_appimage.py ran to
193 characters, where the rest of the docstring is wrapped at 120; the paragraph also held an older 144-character line.
The paragraph is rewrapped at 120 columns with its text unchanged.
Text only; the file still parses, and tools/check_content.py passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_xlate.py --id-only wrote an nx2navi_xlate.id for whatever exe sat in the folder, from the sources as they are,
so make_launcher's guard (check_xlate, which compares only the .id) passed an old exe beside a fresh .id. Nothing
calls the option; it is gone, and only a build writes an .id. make_release.check_translator compared build/xlate's .id
with build_xlate.identity(), which hashes the tree the last build prepared there, so a patch or vendor change since
that build passed unseen; make_appimage.py and make_kit.py checked nothing. The fresh-prepare identity moves from
make_launcher into build_xlate as source_identity(), and build_xlate.check_built(folder, exe) refuses a missing exe or
.id and an .id that differs from it. make_launcher, make_release, make_appimage and make_kit stop on its reason, each
before its downloads, builds or deletions: make_launcher's check moves from build_app to the top of main, before the
--out folder is deleted and the toolchain lock is resolved. A translator folder built from the current sources, as the
release scripts pass it (work\xlate_6866a13e), passes as before.
Not reproduced before the change beyond the audit's probes. tests/xlate_package_check.py (new) checks a matching
folder, missing files, another identity and a stale build (refused although identity() over its own tree equals its
.id), that --id-only is refused, and that make_launcher, make_release and make_kit stop on a foreign translator, the
kit before any download; check_built passes on work\xlate_6866a13e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The perf lines could not see pacing. In the 0.10.0 fresh session (nx2navi.1.log, 7,974,927 bytes) all 666 windows read
"pacing 0%" and "bound: cpu" and all 70 slow-frame lines "pacing 0.0 drain 0.0", while 610 windows had a 16.x ms
interval at fps_limit 60 under the game's own limiter. The pacer's wait happened where perf does not count it:
vk_backend.c present_image's wait for the present thread to take the previous picture was untimed, perf_end drops
waits on any thread but the presenting and replay ones (a game may acquire its window image on another), and the
acquire's GPU wait was counted twice, as pacing around b->fence_wait and as a fence wait inside vk_sync_fence_wait
(F-7-04).
- host.h: PERF_HANDOFF, the presenting thread's wait to hand the present thread the next picture, timed in
present_image from under pl (where pending is read) and kept in PresentJob.handoff_ns. perf_end_any counts a wait
on any other guest thread into a sum that perf_present adds to the frame before ft_frame reads its parts.
- nvn_api_obj.c nvnWindowAcquireTexture: the pacer's sleep is timed with perf_end_any; the GPU wait counts only as
the fence wait it is; one line names the threads that acquire and present once the first present is known.
- vk_backend.c present_main: the TRACE=present line adds the present call's time (vkQueuePresentKHR, under the queue
lock, where an overlay's frame limiter sleeps) and the job's hand-off wait; with trace=perf, a line per 60 presents
gives the present thread's blit and submit, paced wait, present call (avg, max), time past target and the hand-off.
- perf.c: "handoff" in the window line's shares and the slow-frame line, "hand-off" in the worst-frame lines and as a
cause of its own for frames over 25 ms and the stutter map. The "bound:" rule stays as it is until play-test numbers
exist.
- The cause list changes together: perf.c FT_C_* and ft_cause_name, stutter_map.c SM_CAUSES and cause_short,
tests/stutter_hitch_check.c names[] (now sized from SM_CAUSES), and tools/stutter_map.py, whose cause and hitch
patterns now take a hyphen. flight.c's frames.tsv gets a handoff_ms column (blocked_names checked against PERF_N),
and tools/flight_view.py lists it.
- tools/perf_summary.py reads the hand-off share, the present-thread lines and the TRACE=present lines, and prints
percentiles of the present interval, paced wait, time past target, present call and hand-off.
New tests/perf_summary_check.py makes lines with the runtime's format strings (checked against the C sources) and
checks the percentiles, the window averages and the hand-off share, and that older lines without these fields still
parse; tests/stutter_map_check.py gets a hand-off hitch and area cause, which the old patterns dropped. Diagnostics
only: no wait, pacing or presentation change. Not reproduced before the change (it needs a game run with trace=perf).
perf_summary_check, stutter_map_check, stutter_hitch_check, flight_flash_check and frame_step_check pass; every
runtime file compiles with the dev flags after the host.h change, and the changed files add no -Wall -Wextra warnings.
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>
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 larger pool asks for 655,360 sampled images in one set where 524,288 were asked before. The start now logs the
device's limits for one set and one stage next to that count, warns when the count is over them, and a refused
layout ends the start with the limits named instead of a bare failure.
The log line for a slot registered past the pool names both slots a shader reads instead: the pool's last entry, and
the last of the old length for a translation carried over from the translator before. The comments say what they say
without an issue number.
The translator's identity changes with this pool size: after the update every install translates its shaders again
in the background, the earlier translations serve meanwhile, and a shader gets the ground fix once it is translated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The game makes a texture pool of 33068 descriptors, and the translator clamped every pool handle to 32767, so the textures registered from 32768 up were drawn with whatever slot 32767 held, often an empty image: ground went see-through after an area load until the area was loaded again. VK_POOL_TEX and the translator's TexturePoolLength go to 40960, and a slot registered past it is logged once.
The preparation and the boot build the stage libraries of single shaders. A mesh pipeline's pre-rasterization
library needs its mesh stage and its task stage together, and its fragment shader library depends on what that mesh
stage leaves unwritten, so only a pipeline record names what they are made of.
Every listed mesh pipeline the preparation builds and every recorded one the boot queues now notes its two shader
libraries, each key once. The preparation builds them on every core after the listed pipelines and before the
pipeline cache is saved, inside the marker that covers a driver dying on a listed pipeline. The boot builds them on
one thread at idle priority beside the game, behind the memory gate, with the rest of the library prebuild
(lib_prebuild = 0 leaves it out). Each is destroyed once built, which leaves it in the pipeline caches and the
driver's own, as for the single shaders' libraries.
A mesh pipeline of known stages that a draw meets with other targets, another blend state or sample count then finds
its libraries in the caches and is linked, where it was compiled whole. The recorded and listed mesh pipelines are
still built whole ahead of play, also on a machine that leaves the rest of the shared list to its first draw.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
make_launcher.py --pgo FILE puts the profile and its record into app/<version>/data, where make_game.py looks for it,
before the prebuilt runtime is built, so the runtime library is compiled with it. The profile is part of the app zip
the update feed serves, so the build after an update compiles with it. The release stops before it downloads or
builds anything when the record names another recomp/ than the checkout's, and after the runtime library when the
release's compiler, the one players build with, left the profile out.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
tools/make_game.py, the build players run through the launcher, read no profile: only tools/build_game.py did, which
does not build 1.1.1. A kit with data/pgo-v111.profdata (build/pgo/ in a checkout) now compiles the lifted code at -O2
with it and the runtime too, the prebuilt runtime library included. The profile is left out, and every command and
key is what it was, when the kit has none, when its record names another recomp/ than the kit's, when a compiler of
the build refuses the file in one trial compile before the others start, and with NX2NAVI_PGO=off. The record holds
recomp/ by content, which a kit without git can compare. The object keys hold the profile's content and not the kit's
folder, so a launcher update that moves the kit keeps the objects.
--pgo gen (NX2NAVI_PGO=gen from a launcher) builds the instrumented game into the game folder: it counts into
<out>/pgo, records what it was built from for the merge, and stops at once when the build tools lack the profile
runtime. tools/pgo.py merge takes that record, refuses a game built from another recomp/ and finds llvm-profdata in an
MSYS2 folder too. tools/check_content.py refuses profiles in a commit.
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 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>
Conflicts: the settings key lists take both sides; frame generation keeps the camera-cut handling and logs its counters every 600 frames with the cuts among them; the pass-time mark moves into draw_viewport; mo_desc keeps the pose depth and notes the reader.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
After Keep Build Cache removed the generated sources and the kit was updated, the lift ran because layout.json was
gone while its key still matched. An interruption after layout.json was back left the saved state calling the lift
complete, and the next run compiled unfinished sources. The pull request's two commits (one a merge of main) are
applied here as their net change.
Co-Authored-By: Claude Fable 5.2 <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>
With the lifted code built for the CPU's level, a profile on an RX 580 PC (Haswell-class CPU) has two game threads
running it nearly all the time and the replay thread idle a quarter of the time: the game's own code bounds the frame.
The lifted code is built at -O1 for build time; the environment variable NX2NAVI_GUEST_OPT=2 builds it at -O2 for a
player who will wait for the longer build. Part of each object's key, so changing it rebuilds the lifted code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A NX2NAVI_PROFILE profile names Rips; tools/prof_report.py needs the link map to name them, and the launcher's build
wrote none. The link now writes the map and keeps its non-lifted lines (the runtime, libraries; the f_<hex> game
functions are hundreds of MiB of it) as nx2navi.map in the game folder. The link key changes once, so the next build
links again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 0.9.1 notes keep the release branch's text and take main's sections: New, the edge line under Fixed, and Changed.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
A draw that met a new pipeline waited 3 ms for its fast link, in every frame, which is just under what the link takes with the stage libraries in the driver's cache: in a first-visit session on a new install 66 of 87 such draws gave up at the end of the wait and their frame was held back. The wait is now 8 ms for each pipeline, counted from its first miss and shared by all its draws, and 16 ms for a frame, so a pipeline the driver has to compile costs one frame the wait and the frames after it nothing.
The pipeline list tool also takes the compute variants a game folder dispatched, which no pipeline record names and which were translated in the middle of play, and reads a list made before as one of its inputs.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Mods, on the home screen, opens the game's mods folder, adds a downloaded mod from its .zip, .7z or .rar file or its
folder, switches each mod on or off, and sets the load order (mods_disabled and mods_order in nx2navi.ini, written as
the game writes them). Each mod's description says what it changes, which other mod changes the same files and which
of the two wins, and why the game leaves it off when it does.
Before the game starts, Play puts the mods folder in order (launcher/mods.py prepare): an archive in it is unpacked into
a folder of its name and kept, a mod whose files sit a folder or more down has them moved up, and a romfs, exefs or
cheats folder put straight into the mods folder becomes a mod of its own. An archive that would write outside its
folder is refused, and nothing is deleted.
The launcher's build (tools/make_game.py) left the mods' code cheats and ExeFS patch files out, so those mods stayed
off. The lift now turns them into patch sets first, as the setup does, with the files in the lift's key and
build-cache/mod-code.json recording what it took up. Play builds the game again before it starts when an enabled mod
brings code no build has seen. The shaders' key names the lift and the compile without the mods' code, so such a build
compiles only what the patches change and leaves the shaders prepared. tools/mod_files.py is the one place the tools,
the build and the launcher find a mod's parts.
Tested: a zipped mod holding its own folder, put into a mods folder, was unpacked and moved up by the launcher's code,
and the game served its file in place of the game's. The lifter took a code cheat and a pchtxt patch made for the game's
main as patch sets. The launcher's and the setup's tests pass.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the exact-material nonnegative distance guard and the recorded NVIDIA translation profiles. Keep generated shaders and the private native patcher out of the repository. Document reported cross-vendor testing and the unresolved origin of invalid absorption distance.
Carry clip coordinates through perspective interpolation for the identified water shader pair. Keep generated modules private and document the observed results and remaining portability limits.
A new install met every pipeline in play: in Hyrule Field one 600-frame stretch of a first visit had 1,769 draws wait 461 ms in all for pipelines, the longest frame 88 ms. The boot now reads seed-pipeline-keys.bin, the release's list, as recorded pipelines after the install's own (keys_open_seed, NX2NAVI_PIPE_SEED, default 1): their pairs go into the vertex output unions and the prewarm builds them as it builds the install's. The preparation builds every listed pipeline (NX2NAVI_PREPARE_PIPELINES now defaults to all), with the unions of the same list, so the first boot of a new install finds them in the caches. The list's mesh pipelines are left out at the boot on a GPU without mesh shaders, as the preparation leaves them out. Nothing of the list goes into pipeline-keys.bin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The SM86 lowering keeps the uniform registers in the general registers after the highest one the program uses
(HighestGpr), with one spare so pairs stay aligned. The scan read the register fields as single registers, so a
128-bit load or store whose upper registers the program names nowhere else (LDG, LDS, LDL and STG, STS, STL with size
6) left Rd+2 and Rd+3 to UR0 and UR1, and the load overwrote them. The scan now counts Rd..Rd+3 (Rd..Rd+1 for 64 bits)
of these forms. An overestimate only moves the uniform registers up.
Checked: no translation changes in the game's 520 task and mesh programs, its 4,238 NVN2 compute programs, or the
2,450 vertex and fragment programs whose stored input id starts with 0, all from an installed shader cache, and all
pass spirv-val (vulkan1.3): the case does not occur in what this game version has shown so far.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loops the translator writes for narrow storage buffer stores and float atomic adds end only when their
compare-and-swap succeeds. With robust buffer access on, an atomic on a word outside its buffer may return anything,
so the compare can fail for ever and the GPU hangs until the device is lost, as reported right after the first
cutscene on an Intel Arc GPU (#610). New translator patch 0041 counts the attempts in a private word (a local cannot
count round a loop in that pass, which runs on SSA form) and leaves the loop after 4,096; contention on a word needs
about as many attempts as invocations race for it. Shared memory loops stay as they are: a shared word in range is
always settled, and the mesh programs run that loop for every primitive index byte.
Checked: all 4,238 NVN2 compute programs an installed shader cache stores translate and pass spirv-val (vulkan1.3); 45
contain a compare-and-swap, 11 of them in a storage loop, which grew by 256 bytes each and now compare the count with
4096; the other 34 are unchanged. tools/xlate_smoke.py passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An NVN2 compute program's control section gives its local memory in bytes a thread at +0x68, beside the shared memory
size at +0x64 that the job already carried. Without it the translator declared its default of 0x1000 bytes: one of the
game's compute programs needs 4,752 bytes a thread, and its translation declared a 1,024-word local array that it
indexed up to word 1,187, outside the array. The job now carries "local_mem" from +0x68, and the lowering starts a
compute program's spill area after its own local memory (at least 0x1000 bytes) instead of always at 0x1000, where a
larger program's own memory would have overlapped it.
Checked: that program's translation now declares 1,188 words (indices up to 1,187) and passes spirv-val; two programs
of 3,456 bytes a thread translate as before. All 266 compute programs whose stored input id starts with 0 in an
installed shader cache translate and pass spirv-val (vulkan1.3), and tools/xlate_smoke.py passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mesh program's header lists the components of each output vector it has; the fragment stage may read the others.
The native mesh entry function never wrote them, so they read as whatever the output held, and the fallback's vertex
stage wrote 0 for all of them, w included. Maxwell gives an unwritten component (0, 0, 0, 1), as the pipeline code
already pads a missing vertex output. Both paths now write that default: MeshSpirv.cs after copying each vertex's lines,
mesh_fb.c meshfb_vs with w as the bits of 1.0f whatever the type.
Checked: all 520 task and mesh programs of an installed shader cache translate and pass spirv-val (vulkan1.3); 500
mesh modules gained about 730 bytes each on average, so many of the game's mesh programs have partly listed vectors.
--probe-meshfb passes (fallback pictures equal the mesh pipeline's), and its module part converts all 520 new
translations with 0 failures, all 1,020 modules pass spirv-val and their compute pipelines build.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vulkan leaves workgroup memory undefined, and a mesh program need not write every vertex it has room for or every
index byte. The mesh entry function copies all of them into the outputs, so what an earlier workgroup left in shared
memory became vertices and primitives: on AMD, long thin triangles across the foliage's shadow map. The entry function
now sets the vertex lines, the primitive count and the index bytes to zero in a loop over the workgroup's threads
before the payload copy and the barrier. Task programs keep clearing only their mesh count.
Checked by translating all 520 task and mesh programs an installed shader cache stores with the translator before and
after the change: every one translates and passes spirv-val (vulkan1.3) both times; each mesh module grows by about
88 bytes, task modules are unchanged. tools/xlate_smoke.py passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
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>
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>
On Linux every driver got shadow storage (copies of guest memory beside it), which exists for amdgpu, where importing guest memory costs the write protection of its pages. NVIDIA's own driver imports without that cost, so there the copies are only the slower path: in Kokiri Forest on 0.7.1 (RTX 4090) imports ran at 51.9 frames a second and the copies at 27.4. Until now only the AppImage's play.sh knew, by setting NX2NAVI_SHADOW=0 when NVIDIA's kernel driver was present at build time, so a game started any other way got the copies, and a PC with that kernel driver whose game ran on another card got imports where it should not. The game now chooses by the driver of the device it runs on, play.sh no longer sets the variable, and NX2NAVI_SHADOW=1 or 0 still decides. The driver is asked when the device is picked, before the storage is chosen.
Windows is unchanged: the device probe here reads "mem: mode mirror" with no shadow storage line, and the shadow line with NX2NAVI_SHADOW=1. The Linux path itself was not run: there is no Linux machine here.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Brought onto the line with FSR 4, its frame generation and the debug menu. Where both sides had added to the same
place, both stay: the patch table keeps the pickup count and the free camera, the settings list keeps both sides'
names, the menu includes the co-op page and the debug menu, and the launcher builder takes the pipeline list and the
FSR 4 folder. Both sides had added the menu's typed-character event; one definition stays, with the window code that
passes characters outside the basic plane.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>