Skip to content

Release 0.9.0: nxnative merge up to 2716f78, upstream's cheat engine - #29

Merged
gnosticgeek merged 39 commits into
mainfrom
release-0.9.0
Oct 31, 2026
Merged

gnosticgeek merged 39 commits into
mainfrom
release-0.9.0

Conversation

@gnosticgeek

@gnosticgeek gnosticgeek commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Requested by Tom

What and why

Release 0.9.0. It merges seedds/nxnative main up to 2716f78 (34 commits since 6d880d2): Metroid Prime Remastered, Hyrule Warriors: Definitive Edition, DQ III HD-2D and Atelier Ryza, NVN depth/stencil and ring-buffer fixes, the BotW ldaxr lock fix, faster rewriter scans, build-time host shaders, and upstream's cheat engine. The CHANGELOG 0.9.0 entry has the details, and BACKLOG.md is updated.

The biggest conflict was cheats. Upstream's hle_cheats.c replaces NXForge's Markdown engine, and the Quick settings sidebar's Cheats section now reads upstream's rows, the same ones as the new Cheats menu and panel (⇧⌘C). The folder is unchanged. Old .md files convert with dm/tools/cheats_md.py txt, and Tom's Curse of the Moon file is already converted. The present filters, FXAA and Sharpen moved into upstream's dm/gfx/msl/present.metal.

The four upstream contributions are open as seedds#789, seedds#790, seedds#791 and seedds#792.

Checked

  • M4 Pro, macOS 27.0.1: the Mac host builds for Wonder, Hades and Digimon, and host_warnings.py reports no new warnings. Host tests and GPU tests (make -C dm/gfx/tests run) pass. Every gfx test and the iOS sources compile.
  • Python tool tests pass, except test_host_msl.py, which fails the same way on a clean upstream main on this Mac. lint_python.py reports 0 new findings, and release_check.py check 0.9.0 passes.
  • Not checked locally: clang-tidy (upstream's new lint-c workflow; NXForge's own C and Objective-C have not been through it before), and the converter app.
  • No game played yet. The sidebar's Cheats section, the present filters and FXAA have not been seen on screen. Every game's shaders translate again (new chain version).

Docs

  • docs/cheats.md rewritten for upstream's engine, docs/settings.md's Quick settings table updated, symptom index regenerated
  • dmsh_chain.py --header run; dm/gfx/nvn/dmsh_chain.h is part of the change
  • No game files, keys or data extracted from a game in the change

🤖 Generated with Claude Code

seedds and others added 30 commits October 30, 2026 21:26
…the 13 fixed frame-limit rows removed (issue seedds#635)

The rewriter finds each TAutoConsoleVariable the host may write from the
engine's own registration code: the variable's name (UTF-16 or UTF-32), the
adrp + add that loads it into x1, and the first call after it. A bl is UE
4.18's constructor, with the object in x0; a blr is the console manager's
register call (UE 4.2x and 5), and the object is the one the returned value
pointer is stored into. A name registered at two objects gives none, with a
[RED] reason. It writes UE_CVAR:<name> rows into main's fixups for t.MaxFPS,
rhi.SyncInterval, nvn.SyncInterval, r.Streaming.PoolSize and
Project.FrameRateLimit.

The host reads them (loader.c): hle_fpslock.c raises the frame limit and the
present interval from them for fps_target, and hle_uecvar.c the texture pool,
with its search code gone. This replaces the fixed k_locks rows of OCTOPATH
TRAVELER 0 and II, DQ7 Reimagined, FINAL FANTASY RESONANCE (global and Asia),
SMT V Vengeance and DQ XI S. The new profile key ue_fps_cvar names the
variable to write (default t.MaxFPS; SMT V's Project.FrameRateLimit).

Checked offline over 61 mains: the search finds exactly the objects of the
13 removed rows, and nothing in non-Unreal games. The rewrite cache keys (NSP
script, regress.py, build_src.sha256) now include ue_cvar.py, ue_malloc.py
and loader_census.py. Open: OCTOPATH TRAVELER's inlined registration.
…in the build folder

regress.py took any <id>_build/profile/<id>/game.tsv as a derived profile
and ran it instead of dm/games/<id>. The NSP script leaves a provisional
profile there from a game's first packing, before its profile is added, so
38 of the 57 games on the game drive were tested with their guessed
profiles. OCTOPATH TRAVELER 0's had no tex_dyn_slots (three particle shaders
refused, with red lines), no fps_target and no depth_mode.

A build's profile now runs only when it is derived (the NSP script's
<id>.derived.key beside it) or the game has no profile in dm/games. The games
line marks such a game with "the build profile". On the game drive that
leaves Mario Kart 8 Deluxe (derived) and Atelier Ryza (no profile yet).
… its toolchain (GitHub issue seedds#717)

Each Xcode downloads and uses its own Metal toolchain, whatever the macOS version,
so with Xcode 27.2 selected -downloadComponent fetched 32023.922 again. The message
now names the selected Xcode and says to install 27.1 beside it, select it, then
download its toolchain; README links Apple's download page instead of the App Store.
The lock stays at the one tested compiler.
…(PR seedds#425)

.clang-tidy runs the bugprone checks and clang's static analyzer (core, unix, osx,
nullability, two security checks) over dm/host and dm/gfx, with each excluded check's
reason on its line. dm/tools/lint_c.py builds a compile database from the Makefiles'
dry runs (196 sources) and counts findings per file and check against
dm/tools/lint_c_baseline.tsv (273 findings in 140 pairs, LLVM 23.1.4, current main).
The lint-c workflow downloads that LLVM release (sha256 pinned) on xcode-27.

The ratchet works like lint_python.py's: a new finding fails (fix it, or // NOLINT a
false positive with a reason), so does a fixed one still listed (tidy --update only
shrinks), and a pull request's baseline may not grow over its base's (--against),
unless LLVM_VERSION was raised (--rebaseline).

Left out of the PR: the clang-format report. .clang-format cannot state the house
style (one statement per line), and a root .clang-format would let editors'
format-on-save and git clang-format rewrite dense lines.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
…ge per PR (PR seedds#439)

make coverage in dm/host/tests and dm/gfx/tex/tests rebuilds the tests with clang's
source-based coverage into their own folders (dm/sanitizers/coverage.mk), runs them
with continuous-mode profiles (children that end with _exit still count) and exports
llvm-cov's lcov. coverage.py measures the tool tests over dm/tools, dm/gfx/tools,
dm/games and scripts, subprocesses included. dm/tools/coverage_report.py writes the
job summary: totals per area, the largest files and, on a pull request, the changed
lines the tests run and those they do not. The coverage job in tests.yml is report
only and uploads the raw reports.

Changed from the PR: the C line counts are llvm-cov's own (the DA lines of
-format=lcov, which llvm-cov show marks) instead of a rebuild of its line logic
from the JSON export's segments; both give the same counts on every file of the
host and texture tests (42 files, 0 lines differ).

Today: host 61.7% of 7,883 lines, CPU textures 85.4% of 240, Python tools 51.9%
of 15,023.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
…he test binds its loop variable

ue_cvar.registered_at decoded add_imm(word(j)) twice, and pyright could not tell the
second call was not None; it is decoded once now. test_ue_cvar's lambda bound
base_ctor by reference (B023); it takes it as a default now. Neither changed
behaviour: the lambda ran within its iteration.
…sue seedds#700)

A native-texture GetOutputBuffer returned its slot inside the call, the host
decoded the next packet into it at once and announced it, the player's
decoder thread acquired it again, and the player's own release of the old
picture then freed the new one: a picture one slot cycle (16 frames) ahead,
then back. A rendered slot now waits for the client's release; until the
decoder has seen its client release a rendered slot, it comes back at the
client's next GetOutputBuffer instead (players that never release).

Metroid Dread on the Mac, 75 s: 15 jumps of 533 ms before, none after.
BotW regress check PASS.

Fixes seedds#700

Co-authored-by: wriver0s <37092604+wriver0s@users.noreply.github.com>
… 1.6.0 ran a job twice (issue seedds#251, PR seedds#562)

Breath of the Wild 1.6.0 crashed on the Mac 10 to 60 s into its first scene
(10 of 10 runs, in h_nvnBufferGetAddress and elsewhere). Its job queue takes
its lock with ldxr; cbnz; stxr; cbnz and no barrier after the loop: an
exclusive load without acquire ordering. Armv8 lets the queue reads after it
be satisfied before the lock is held; Apple cores do it, so two workers
popped the same job and built their draw lists into one arena.

patch_nso.py (acquire_lock_sites) turns every such acquire into ldaxr, found
by its instruction shape in every module, with no address list and no build
gate; a loop followed by dmb ish or dmb ishld is already ordered and left
alone (Tears of the Kingdom's 32 and Super Mario Bros. Wonder's 33 loops of
this shape all are). The statistics line counts the rewritten ones as
acquire_lock. docs/reference/threads.md has the entry, the BotW note the
finding; test_acquire_lock.py checks the shape on hand-assembled words.

Reach over the local games: only Super Mario 3D All-Stars' main (7 sites,
no repository profile) rewrites differently: 14 bytes, the 7 sites and their
7 spin-wait copies. Every other local game, BotW 1.9.0 included (no loop of
this shape), rewrites byte-identically. 1.6.0 is not on this Mac; the
contributor's runs: 10 of 10 crashed before, 0 of 7 after (up to 900 s).

Regression (1 game, narrow reach): BotW 1.9.0 full check against e513bfe: PASS (19592/19592 shaders, 0 changed; 110 s route, fps 59.1 → 58.5, no new RED).

Co-authored-by: Suraj Mandal <11650177+surajmandalcell@users.noreply.github.com>
…channel has an output (GitHub issue seedds#715)

Super Mario Odyssey 1.4.1 crashed entering a Sand Kingdom pipe with rumble on: the SDK's own call
opened the hid service, which sm refuses, and read the null service object (sdk+0x24254c). The host
now answers it from vib_targets, as GetVibrationDeviceInfo reports the device.

Co-authored-by: Wjouter <34310151+Wjouter@users.noreply.github.com>
…e capped at 1080p (PR seedds#367, GitHub issue seedds#705)

A TV gets the game's view through an external display scene: 16:9 fills it, the game
switches to console mode (operation mode notification, as a Switch on its dock), and
the iPhone or iPad keeps the pad. Unplugging moves the view back and restores the
earlier mode. An app built with the iOS 27 SDK is offered the TV scene only after it
registers a scene accessory (registerSceneAccessory); the manifest entry alone is
ignored and iOS mirrors the whole screen, pad included (issue seedds#705). The Info.plist
entry stays for iOS 26.

The TV picture is at most 1920 pixels across the 16:9 picture (taken from the picture,
not the screen, so a display of another shape keeps the full 1080p): the game renders
1080p docked, and a 4K drawable only added an upscale pass and a 4K stream. An Apple TV
over AirPlay showed black with it (GPU 15 to 60 ms a frame). Only the external display
role gets the TV scene configuration; every other role keeps Default.

Tested by the author on an iPhone with a TV by cable and an Apple TV over AirPlay, and
in issue seedds#705 on an iPad Air M3 (iPadOS 27.2) with a 4K TV by USB-C: the game fills the
TV, the iPad keeps the pad, the game is docked. Open: unplugging and plugging in again
on a device, the iOS 26 manifest-only path, and LiveContainer (it connects an unclaimed
external display scene; a follow-up was offered in the PR).

Co-authored-by: Zorrobyte <7257172+zorrobyte@users.noreply.github.com>
…l profile and the NSZ conversions follow it (PR seedds#475)

--work-dir <folder> (WORK_DIR in Spyder) is the game's build folder, used as given. Without -o the IPA (or the
Mac app) defaults to the folder holding it. The provisional or derived profile goes to <build folder>/profile
unless --profile-out names another place, and the NSZ / XCZ conversions to <build folder>/nsp/. Without the
option every path is as before; the survey stays in the NSP folder.

A front end that stages the NSPs in a fresh folder for every build (LNPANDY/NX2Apple#132) lost the profile with
that folder: it was surveyed again, and its dated heap check line changed its content, which the shader split
and the iOS shaders key on, so both ran again (about 3.5 hours on a 73,000-shader game). Converted NSZ files
were lost the same way and converted again. A profile in the default <title ID>_build/profile is copied once
into a new build folder that has none; the original stays.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
…ytes on SDK 10 and older (GitHub issue seedds#708, PR seedds#709)

The host zeroed 0x40 bytes of nv::MemoryInfo on every SDK. On SDK 10 and
older the record is 0x20 bytes (four 64-bit counters), so the answer overran
the caller's record by 32 bytes: Hyrule Warriors: Definitive Edition 1.0.1
(SDK 3.5.1) passes a 32-byte record, and AddressSanitizer reported the write.

Every local sdk module from 3.4.0 to 20.5.22 fills four counters; on
3.4.0-10.8.1 the function has no 0x40 path, from 11.4.3 on it zeroes 0x40
when nv is not initialised. That is the DM_SDK_OLD boundary the NVN API
version and the shared-font layout already follow, so the size follows
dm_sdk_major(). The pool figures lie in the first four counters, so both
sizes carry them. t_nv checks both sizes between guard words and in exact
allocations under AddressSanitizer.

From PR seedds#709 by monoquark; review: the size follows the SDK version instead
of recognising the wrapper's instruction sequence (the modern SDKs fill four
counters too, so the instruction form did not mark the record's size).

Co-authored-by: monoquark <24584001+monoquark@users.noreply.github.com>
…s chunked zlib .gz files read by the shader split

Atelier Ryza: Ever Darkness & the Secret Hideout DX (0100199023B2E000), update 1.2.0 (v65536):
Gust's engine on Koei Tecmo's KTGL, SDK 18.3.2, NVN. Plays on the Mac at 30 fps, as on the
Switch: the opening movie, the title, a new game through the opening story into the field,
the menu and the map, and a load of that save. Profile (no cuts, no run-time keys), prewarm
list, note, games table row and gallery screenshot.

- Shader split (shader_split.py gust_gz): every frame was black, the title included, with
  26 shaders missing from the map. They were the post effects' GLSLC outputs in
  Data/NX/PostEffect/pe_pack.elixir.gz, one of the game's 156 .gz files in Gust's chunked
  zlib (a u32 stored length and one zlib stream per 16 KB chunk, ended by a zero length);
  the other 3,956 are CRAE archives stored as they are. The split inflates that form, by
  its structure, and scans the result as a loose file: 921 -> 1,049 shaders.
  New test: dm/tools/tests/test_split_gust_gz.py.

Regression (very certain, 1 game): the change reaches only the split. Fire Emblem: Three
Houses is the only other local game with a .gz file whose fifth byte is 0x78 (one file,
which takes kt_gz first); its tables split with and without the change are identical
(1,372 shaders). regress.py check on it (chain only, no smoke route): PASS.
Atelier Ryza DX leaves cube textures (NVN target 8) in slots its draws read as 2D: a compute
shader of CLC.g1s, the PBB materials' pixel shaders and the river's vertex shader, a
different texture each run, so stale bindings. tex_fit had no view for that case and bound
the cube as is, a type mismatch whose reads are undefined on Metal (up to five red lines over
the opening). Maxwell keeps a cube's faces as six layers and a non-array sample reads layer
0, as for the 2D array -> 2D view tex_fit already makes; the cube now gets a one-face 2D view
(valid under the Metal API validation layer). What the Switch reads there was not measured.

Ryza, same save-load route with the host before and after: the cube mismatch lines are gone
(the river slot's buffer texture and a 3D texture left in slot 20 in one run stay reported,
no view bridges them); the field at frames 3000 and 4000 looks the same, 0.3 % of pixels
differ, scattered particles and foliage. ROMANCE OF THE THREE KINGDOMS 8 REMAKE has the same
case in two vertex shaders (its note updated); not run, its build folder is gone.

Regression (narrow reach, 1 game: only Ryza and RTK8R bind a cube in a 2D slot in any run log on
disk): Ryza as above. Extra: regress.py check on Xenoblade Chronicles 2 (full route, against
origin/main fe6d3df): PASS, 15,572 shaders unchanged, 30.0 -> 30.1 fps, screenshots match.
…ore the game runs are the ones it uses (GitHub issue seedds#614)

The game calls nvnDeviceSetDepthMode with near = 0 ([nvn] depth mode near=0 in the Mac report of seedds#614 and in the iPhone logs of seedds#149 and seedds#292), and the profile had no depth_mode, so the NSP script's prebuild made the Mac app's bundled archive (1.2 GB) for near = -w. The host follows the game's mode and found none of those bases ("made for ... depth near=-w ... this run needs ... depth near=0"), so every base compiled while playing (found none 6, bases compiled 3 / 258.6 ms up to frame 1200), one cause of the hitches on new moves. Legends: Arceus, Scarlet and Violet already set it. Only the offline bases change; the shader input digest and libraries do not.

The game note records it, what stays open in seedds#614 (shader library loads; the 33/50 ms fullscreen alternation, which matches seedds#347), and the iPhone page faults of seedds#149 / seedds#292 as open: both logs come from builds before native texture atomics. Checked: game.py reads zero; docs, nso_match, patches and NSP script tests pass. Not re-measured with a repacked app.
…e rewriter (ue_region.py); the 7 game_args rows and Pikmin 4's cut removed (issue seedds#635)

The rewriter finds, in any Unreal Engine 4 / 5 main without a build ID, the store of the engine region's size right before nninitStartup reserves it with
nn::os::AllocateAddressRegion, and puts observation gate OBS_UE_REGION on it. The host (hle_svc.c) sets the size register and x1 to what the alias region has
room for, StandardAllocator's formula (now one shared function): 2.81 GB in the 3.75 GB block, 4.06 GB in Engage's 5 GB block. On the 123 mains on the game
drives it finds the reservation of the nine Unreal builds with the option and nothing else.

-SwitchVirtualAddressSpaceSize= was no general way to size it: Pikmin 4's UE 4.26 nninitStartup reserves the region only while its size variable is 0, the
game's own code. The default path takes the gate there, so its patches.py cut goes.

Observation gates no longer need x9-x17 dead at their site (scratch_dead removed): the gate frame saves and restores x0-x28 and the flags, and three of the
size stores read x10.

Not booted yet on any game.
…o two 16-byte literals found in main's code (uepak.py code_keys)
…s carried across game updates (PR seedds#406)

hle_cheats.c loads the running build's <build ID>.txt from DM_CHEATS (default
<DM_ROOT>/cheats, flat or the atmosphere/contents tree) and runs the dmnt cheat
VM, opcodes 0-C4 as Atmosphère 1.12.1 runs them (docs/reference/cheats.md says
where the host differs), once per presented frame and from a ~60 Hz thread
while the game is not presenting. On top of the plain format: labels and EdiZon
sections, on / off pairs as one switch, per-build toggles (<build ID>.enabled),
typed values, run-once cheats, a dry run, a master switch and, on the Mac, safe
mode after an unclean exit. The Mac window gets a Cheats menu and a floating
Cheats panel (⌘⇧C) that never takes the keyboard from the game.
scripts/cheat_anchor.py imports a cheat file into a version-independent source
anchored to the game's code and data and compiles it for any build.
docs/frontends.md has the files as the contract for front ends.

Changes to the pull request:
- Cheats never write over the rewriter's work. patch_nso.py writes
  <name>.changed.tsv: the text words it changed (sites, cuts, lock acquires,
  jump tables) and those whose copies run instead (windows, spin waits); Mac
  apps carry it. A code write that covers one turns the cheat off with the
  reason: the Switch's original word there (an "X - off" restore, most often)
  would run an SVC or a TLS read natively or undo an ldaxr, and a write into a
  copied instruction never runs. Without the list (an older rewriter's
  modules) no code write is allowed. Writes past the game's own image (the key
  page, the frame step page, the patch areas) are refused too.
- No thread without cheats: the ~60 Hz apply thread starts with the first file
  that has a cheat in it, not for every game.
- No code writes without the writable alias of main. On the iPhone the code
  pages are the app's signed pages, and toggling the game's own pages writable
  on the Mac races with its cores, so the mprotect fallback is gone: the
  iPhone runs data cheats only.

t_cheats covers the refusals (143 checks, also under ASan and UBSan). The
clang-tidy findings the new files brought are fixed (a realloc that lost its
buffer, a shift by 64 on an untyped value, three nils handed to AppKit, the
test's unchecked lookups) or marked: file loads under the cheats lock, which
the per-frame pass only trylocks.

Regression (normal reach, 3 games), check against fe6d3df:
- Super Mario Bros. Wonder: PASS (16268/16268 shaders, fps 59.4 → 59.6, no
  new RED).
- Tears of the Kingdom: the first run never presented. Its window resigned
  key before the first frame and the game suspended on the out-of-focus
  notification, as on a Switch. A second run booted (first frame 7.9 s, fps
  34.5 → 46.5, p99 51.6 → 17.9 ms, 28669/28669 shaders); only its 90 s
  screenshot was missing, after a late focus loss.
- Breath of the Wild: frame p99 17.5 → 33.4 ms in 3 of 3 runs (fps 60.0 →
  58.5, 19592/19592 shaders, no new RED). This baseline's 17.5 ms is the
  outlier: BotW's other checks today measured 32.8 to 33.5 ms on both sides
  (e513bfe, 195986f). Without a cheats file the host's only new per-frame
  work is a timestamp and a flag check.

Co-authored-by: Amost <6445689+Amost-@users.noreply.github.com>
…ept in the log (PR seedds#721)

Arcade Archives RAINBOW ISLANDS (01004E101D476000, 1.0.2, SDK 19.3.11) crashed
with SIGBUS on the Mac (M1, macOS 27.2) at its first nn::audio::StartAudioOut,
and the process then hung. The game calls Start on its own thread, and the
first AudioOutputUnitStart of the process sets up the Core Audio session
(HALC_ProxyIOContext::StartIOProc -> +[AVAudioSession sharedInstance], which
connects to the session service over XPC). That ran off the bottom of the
game thread's stack: the fault is the top of the page just below the stack,
sp in the guest stack block, and the faulting frame varies between runs
(CFString formatting under NSXPCInterface, the Swift type demangler under
CAReporter).

Start now runs on the thread's big host stack (HLE_FORCE_BIG), as the file
system take-overs do, and so do Stop (AudioOutputUnitStop) and Close (disposes
the AudioUnit), which talk to the same service from the game's thread. Running
Start on a helper thread, as Open does, would join that thread while holding
g_aos_lock, which can give up the guest core to a thread that then waits for
the lock.

The big-stack entry now keeps the game's return address beside the saved sp
at the top of the host stack, and dm_bigstack_caller (hle.h) gives it back, so
the Start / Stop / Close log lines still name the game's call site instead of
"host". t_bigstack runs a take-over on a 16 KB fake guest stack and checks the
switch, the arguments and the call site on both paths. The audio unit tests
link hle_bigstack.c and stub dm_guest_as_contains, as the file system tests do.

Co-authored-by: jasonkimball82 <350284318+jasonkimball82@users.noreply.github.com>
…r(type, format) taken over, short timed waits as polls

Hyrule Warriors: Definitive Edition (0100AE00096EA000, SDK 3.5.1, KTGL on NVN) plays on the Mac
from the first-run questions through the title, Legend Mode's opening movie and the first battle,
at the game's own 60 fps with no red line. Two general host changes:

- hle_movie.c: SDK 3's movie::Decoder constructor takes only a type and an output format; the
  host took over the SDK 4 and later forms only, so the SDK built its own decoder and the host's
  Configure called through a null pointer. It is now taken over as a CPU-mode decoder; SDK 3
  numbers its formats one lower (3 = NV12, 2 = ARGB) and reports an alignment of 0x20 as
  GetOutputBufferSize's second output.
- hle_thread.c: a wait with a timeout under 20 µs that times out is a step of the run of yields
  (poll_wait_begin / poll_wait_end): the game's main and update threads polled their frame events
  with 30 ns and 500 ns waits, so the back-off never started. Its first battle on the Mac:
  CPU 102% -> 27% of a core, 4.6 -> 1.5 W, same frame times. Test: t_pollwait.

Regression check (regress.py check, baseline 6b0cffb, 5 games): Xenoblade Chronicles 2 (SDK 3.5,
imports the same constructor) 29.8 -> 29.8 fps; Breath of the Wild 55.7 -> 59.2; Tears of the
Kingdom 54.6 -> 57.3 (--prebuild-limit 3000); FINAL FANTASY X/X-2 28.5 -> 29.3; Catherine: Full
Body 29.9 -> 29.9; no new red line, screenshot differences are the same scenes at other moments.
… the shader split, the pixel sample index and GLSLC's sample position table, and image stores kept under rasterizer discard

- shader_split.py: Retro's RFRM forms in either byte order (rfrm_order), TOCC resource lists with 52-byte ADIR entries, and GLSLC's extra control data past a control section's own length
- Translator: PIXLD.MYINDEX reads the sample index (IoVariable.SampleId, SampleRateShading); the host fills the pixel stage's sample position rows of c0 (0x620, 0x630) and dmsh_build allows the dynamic index into them; nvnProgramSetSampleShading answered
- dmsh_build: a vertex shader that writes images, not only storage buffers, keeps its draws under rasterizer discard (the volumetric fog's froxel volume)
- Chain d1043b780a74
…fests of the older hashing hashed again

nvn_manifest hashed a shader's variant files (<key>_bg0.json, _bgv.json and the others) as bytes, and
their reflections carry the chain stamps (chain, chain_metal, chain_compiler), so every chain change
reported every shader with variants as changed (2026-10-31: 720 on Donkey Kong Country Tropical
Freeze, 5 on Xenoblade Chronicles Definitive Edition, all only the stamps). Variant .json files now
drop the same fields refl_hash drops for <key>_b.json (refl_canon); .metal and .metallib variants
stay hashed as bytes (pull and vertex-input variants leave only their metallib in the library, and
the same MSL compiles to the same bytes).

A reused chain manifest made by the older hashing would compare against new ones the same way, so
manifests record their hashing (hashes: 2) and one of another number is hashed again from its
outputs when they are on disk. Tests: test_variant_hash_ignores_chain_stamps,
test_reused_manifest_of_older_hashes_is_hashed_again. No game regression run: the change is in the
harness's comparison only.
Bind the current Capstone instruction in the operand helper and assert
that a detected engine-region gate has a size register before encoding it.
These make the existing invariants explicit without changing valid rewrite
behavior or widening the Python lint baseline.

Reviewed PR seedds#756. Focused UE-region, lint-ratchet and docs tests pass
(28 tests); the pinned lint check has 392 baseline findings, zero new
findings and zero stale entries. Regression risk is low: instruction
matching and emitted gate encoding are unchanged, so no game regression
run was needed.

Co-authored-by: Eliran Zach <4012041+thehobbit85@users.noreply.github.com>
Declare the near-zero depth selected by Echoes of Wisdom's NVN calls so its
offline pipeline archive uses main0_z and matches the runtime stamp.

Compare observed runtime depth with each tested profile in NVN smoke runs,
including quick runs without an archive. Detect bundled archive stamp
rejections and prevent mismatched runs from recording a green validation.
Invalidate cached baselines that predate these observations.

Add regression coverage and document the mechanism and validation limits.
Both archives built all 7,495 bases with matched historical host/shader
artifacts: GL was rejected and zero passed 16/16 coverage probes on the
opening route. Castle-entry performance remains unvalidated without a save.

Validation: 77 targeted tests passed, one compiler-dependent check skipped;
Python lint and diff checks passed. Regression risk is low because only a
profile archive input and harness diagnostics change; no multi-game
regress.py check was run.

Refs seedds#758.

Co-authored-by: Marceloalexandregoncalves <62747593+Marceloalexandregoncalves@users.noreply.github.com>
Guest ring allocators update NVNbuffer offset and size directly at +0x18
and +0x20. The host previously read +0x10 and +0x18, so GetAddress kept
the initial offset and GetSize returned the allocation offset.

Reserve the intervening bytes and assert the guest field offsets. All
buffer queries now use the updated fields without a game-specific rule.
Add public-entry-point tests for raw stores, ring wraparound, GPU-only
pool mapping restrictions, builder independence and neighbouring objects.

Fixes seedds#723.
Source: seedds#727.

Co-authored-by: Eliran Zach <4012041+thehobbit85@users.noreply.github.com>
Record depth_mode zero for SteamWorld Heist II, NieR:Automata, Portal,
DRAGON QUEST III HD-2D Remake, Raging Loop, Subnautica and Below Zero.
Their registered builds select near-zero NVN depth while the omitted key
made offline pipeline builds default to the GL vertex entry.

Confirm the recorded routes against registered module build IDs and
reproduce each game's actual NVN depth call with an isolated headless boot.
Extend the archive-builder regression test to check all eight affected
profiles' stamps and vertex entries, including Echoes of Wisdom. Record
the evidence and validation limits in each game note and the shared reference.

The original 14-profile audit now has no observed depth mismatch. The
shader-library input digests are unchanged; archives must be rebuilt and
apps repacked. Rebuilt archive acceptance and performance remain unmeasured.

Validation: seven isolated depth confirmations passed; 77 targeted tests
passed, one compiler-dependent test skipped; Python lint and diff checks
passed. Regression risk is low: only profile archive metadata, tests and
docs change, so no full multi-game regression run was performed.

Refs seedds#758.
The poll-wait test reads pthread storage after registering its guest thread. Assert that setup invariant before dereferencing the nullable result so a setup failure is explicit and the clang-tidy nullability check passes.
Keep default-valued render functions unspecialized while loading and let the
existing Metal 4 descriptor carry their values into pipeline compilation.
Compute shaders and per-value render variants retain library specialization.

Use one stable fallback record per function so overlapping MTLDevice builds
share a single specialization without blocking unrelated functions. Preserve
the record when the same library entry is registered again.

A gated-library regression fails on the original PR and passes with the fix.
Metal pipeline tests: 90 passed; pending-pipeline tests: 52 passed; docs: 11
passed. The full clang-tidy ratchet passed with no new findings.
Full regressions for Breath of the Wild, Pokemon Scarlet and Tears of the
Kingdom stopped before gameplay because the offline Metal compiler is missing.

Co-authored-by: KillReal1234 <48804399+KillReal1234@users.noreply.github.com>
…eedds#738)

Eight color targets and independent depth/stencil store decisions require
ten records. Size the ledger and lookahead buffer from one constant to
prevent the tenth record from corrupting cached pipeline formats.

Retain both physical planes of combined storage, preserve logical aspect
use, normalize live/predicted/historical keys, and reject invalid formats
before either Metal pipeline compiler can assert.

Add asset-free GPU coverage and make its parsing, readback allocations and
format comparisons explicit for the clang-tidy ratchet.

Co-authored-by: Ethan Cha <43095500+ripull125@users.noreply.github.com>
Readback and PNG capture stall measured frames and can change adaptive
pacing. Request default visual proof after the timing window and parse
game speed from the host's current frame-step log format. Preserve
explicit screenshot schedules and document their timing cost.

Link the finding from the Breath of the Wild and Tears of the Kingdom
game notes. The PR's region lint corrections are already on main.

Validated with the 24 benchmark, region and documentation tests.

Co-authored-by: Leandro Piccione <15013313+ComicBit@users.noreply.github.com>
seedds and others added 5 commits October 31, 2026 15:05
Run the x18 scan and spin-wait analysis in forked pools while keeping
patch emission ordered. Share a worker budget across module rewrites,
honor explicit DM_REWRITE_J settings, and reject invalid counts before
loading modules.

Exit through pool cleanup on SIGTERM so cancelled rewrites reap their
workers. Add regression tests, a rewriter reference entry and its symptom
index entry, and document the completed game validation.

Validation: 173 focused tests, the Python lint/type ratchet and docs checks
passed. Breath of the Wild's full regression passed; all 21 binary and TSV
outputs of its three modules were byte-identical. No completed full-game
regression for other titles in this change.

Co-authored-by: Daniel Johnson <41651107+djdev0010@users.noreply.github.com>
Host-owned shader libraries compiled from source at first use, including
one clear library per attachment slot and output type. Move their MSL to
build inputs, combine the clear variants, and load cached metallibs in
the shared graphics paths.

Compile Mac libraries for MACOS_MIN and recheck the compiler, SDK,
deployment target and sources on every build. Reuse unchanged headers,
but regenerate source-only headers when the Metal compiler becomes
available. Prepare and fingerprint the generated header in the NSP
packer, regression host cache and iOS compile check.

Validation: graphics tests, tool tests, focused build/cache tests, Python
and C lint, documentation checks and iOS source compilation pass. Full
game regression was not completed; gameplay timing remains inconclusive.

Co-authored-by: KillReal1234 <48804399+KillReal1234@users.noreply.github.com>
…s#644)

The settings screen offered pipeline prebuild even when Metal 4 pipelines
were off, although both runtime prebuild paths require them. Add the
existing needs metadata to the shared settings row and document when it
appears. Preserve the platform defaults and saved choices.

Validation: all 17 focused settings and native Swift launcher tests pass.
Regression risk is low: only settings metadata and prose change, so no
game regression runs were needed.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
Emit a shaders_stale event before rebuilding libraries after a chain or
profile input change, with the same explanation in human progress modes.

Check completion stamps separately for the selected targets so partial
current-chain outputs cannot hide old libraries. Count each current shader
key once per selected target instead of summing retained historical copies.
Document the count as scheduled base libraries, including cache restores,
and cover interrupted rebuilds, mixed targets and duplicate old caches.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
34 upstream commits since 6d880d2. Conflicts resolved:

- Cheats: upstream's engine (hle_cheats.c, PR seedds#406) replaces NXForge's
  cheats.c / cheats_host.c and their loader, frame and heap-region hooks. The
  Quick settings sidebar's Cheats section reads upstream's rows (the same as the
  Cheats menu) through declarations in quick_settings_mac.h, which front_mac.m
  checks against dm.h. The switches live in <build ID>.enabled, not in the
  preferences. dm/tools/cheats_md.py now converts the old Markdown files to
  .txt.
- The present filters, FXAA and Sharpen moved from nvn_mtl.m's runtime MSL
  string into upstream's dm/gfx/msl/present.metal.
- Makefile and NSP script: the Swift sidebar object next to upstream's
  host_msl header, in the build and its fingerprint.
- t_hid keeps NXForge's IsVibrationPermitted rule and gains upstream's
  IsVibrationDeviceMounted checks.
- Docs: both sides kept (games table, settings sections, development.md's
  cleanup and org.nxforge bundle ID), upstream's cheats rows in
  env_switches.tsv. dmsh_chain.h and the symptom index regenerated.

Checked on an M4 Pro (macOS 27.0.1): the Mac host builds for Wonder, Hades and
Digimon with no new warnings, host tests and the GPU tests pass, every gfx test
and the iOS sources compile, the Python tool tests pass but test_host_msl.py
(it fails the same way on upstream's main on this Mac), and the Python lint is
clean. clang-tidy was not run locally. No game was played.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gnosticgeek gnosticgeek self-assigned this Oct 31, 2026
gnosticgeek and others added 4 commits October 31, 2026 13:36
The host's compile line passed -Xlinker -rpath -Xlinker /usr/lib/swift. The
clang-tidy compile database (dm/tools/lint_c.py compdb) keeps flags and drops
non-source arguments, so every host source's entry ended in "-Xlinker -c":
clang-tidy got a link job instead of a compile and reported nothing for the
host's files, and CI's ratchet failed on 23 findings "fixed" (crash.c's
system() call among them). -Wl,-rpath,/usr/lib/swift is the same link flag,
and lint_c.py skips it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… count lowered

With the host's sources in the compile database again, clang-tidy checked
NXForge's own Mac files for the first time. capture_mac.m read the adaptor's
nullable pixelBufferPool twice and handed a possibly nil Metal device to
CVMetalTextureCacheCreate; front_mac.m's mods_list listed a folder whose path
is nil when there is no Application Support folder. Each now checks the value
once. t_hid.c has one memcmp of dm_vibration less since the merge kept
NXForge's startup check, so its baseline count goes from 2 to 1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
clang-tidy's next pass found two more nullable values in capture_path: the
Pictures or Movies folder URL can be nil (the home folder is used then), and
stringByAppendingPathExtension: can return nil (with_ext keeps the name).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gnosticgeek
gnosticgeek merged commit a0329f8 into main Oct 31, 2026
9 checks passed
@gnosticgeek
gnosticgeek deleted the release-0.9.0 branch November 9, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants