The NVN driver's constant segments have the same layout in every game, so the
nvn_* profile keys are gone. Combined textures are at c2 0x20 + 8 r (a slot past
31 shares the image table; Hades II binds 34), images at 0x120, separate textures
at 0x168 (now always the host's slots 128 + n, as Monster Hunter Rise had them),
samplers at 0x568, and the storage buffer tables at c0 0x110 / 0x310 / 0x510.
dmsh_build.py decodes with that layout, the replay fills both texture tables,
and the translator numbers slots the same way (rebuilt; its SPIR-V and
reflection are byte-identical on 1,491 sample shaders, lock updated).
The shader_* switches and graphics are gone too; the build reads them from the
files (profile_guess.py shader_sources):
- Vulkan when main imports Vulkan and not NVN; its capture when the RomFS has
no SPIR-V.
- A GLSLC capture when main holds GLSL sources and imports glslcCompile.
- An Oodle decode for Unreal paks, and the tool-mode decoder for Trinity packs.
- GLSLC outputs from every main.
- A cover pairing for each single-stage vertex shader (GLSLC, Unreal).
build_ipa.sh takes GFXAPI from the NSP script.
Also removed: the last D2R pieces (the embedded-shader path in dmsh_build.py,
dmsh_facts.py --freeze / --check, maxwell_refs.py, ref/hle_hid_d2r.c).
Chain: every profile's dmsh_chain.h is regenerated. The c1_full lists are
carried over: the change touches no c1 read, and the rebuilt Octopath
Traveler 0, Hades II, Unicorn Overlord and Rhythm Heaven Groove gave the same
lists, identical tables and the same reflection apart from separate texture
slots + 128.
Still to do:
- Run the chain procedure for the other NVN profiles.
- Engage's and Three Houses' shader tables need a new split: Engage gains
Unity's 11 GLSLC shaders in main, and Three Houses' 17 BNSH vertex shaders
lose their cover pairs.
- Ace Attorney's new tables are in, but its libraries are not rebuilt.
D2R (Diablo II Resurrected) has no profile and its code could not run here: the
host-call svcs its cuts emitted only died, the touch-run-v4 rule set needed a
missing dm/PRODUCT, and the host files its hooks called were never in this
repository. Removed with it:
- rewriter: D2R cuts, observation gates and parameter slots, the touch-run-v4
rule set and private gates, the boot list, the cut scope column and the
KRES / KASPECT key slots (fixups.tsv keeps the four thread slots; no
params.tsv any more); dm/tools/product.sh
- host: D2R save edits, add-on content, render-scale controller, time()
takeover and Terror Zone clock, crash decoding at D2R offsets, host-call
registry and stubs, i18n, UI-screen, shell-app loader, zero-stripped
archives, boot profiling hooks with no implementation; D2R headers, tests
and scripts; dm/ios/Sources/dm_touch_controls; dm/third_party/libdeflate
- graphics: runtime extrapolation, world freeze, iPad UI shift, shop drag
image, brightness LUT, visual ruler, bloom / FXAA shader hashes, DM_GFXUSE,
the eco-fps setting, the loading-screen gate; shader_map.py,
nometal_run.py, nvn_slots.py, nvn_slots.tsv and the PC-texture samples
The legacy frame step (frame_step legacy, DM_FRAME_STEP) is retired: the
Breath of the Wild and Tears of the Kingdom stubs lose their mode test.
Breath of the Wild's interval-controller cuts stay: the framework keeps the
controller's interval while the game's VFR override holds.
patch_nso.py output is byte-identical for all 15 games on this machine except
the two frame-step stubs; all 18 profiles build; the host tests pass (their
Makefile now builds the general tests only).
The host decides each frame's step (1 to 3 vsyncs) from its own measurements (nvn_fstep.h, hle_nvn.c fstep_tick),
keeps the frame on screen that long and pays back real time lost to late frames one vsync per frame: even motion,
game time = real time, and 30 fps (not 40) when 60 does not hold. Tears of the Kingdom's frame-step stub writes
60 / step; Breath of the Wild gets a stub at the framework's setWaitVsyncInterval, so its VFR and every reader of the
framework's interval take the host's step, and its VFR override is published so the host holds it.
DM_FRAME_STEP=legacy (profile key frame_step) keeps each game's earlier method for the iPhone A/B test.
Mac: both games at 1.000 of real time at 60, and at 30 with 12 ms added to every frame. The docs record a
pre-existing intermittent Breath of the Wild crash at the world load (legacy too).
- patch_nso.py FRAME_STEP_BUILD (main 277178b7, 1.4.3): stubs step the game by the measured frame time instead of its own interval (2 = 30 fps), capped at the target (30 while a prerendered movie plays), with game time that ran ahead paid back by the next frames. Also the two readers of the fps, the D-pad menu fade and the swapchain argument. Sites from masagrator's FPSLocker config for this build; the pay-back from OJZen/totk-mobile; stubs written and assembled here.
- State page after the key page (fixups key FPS_STATE_OFF): the loader writes the target (profile key fps_target, DM_FPS_TARGET; 0 = every stub runs the original instructions) and the render scale (DM_RENDER_SCALE 0.5-1.0; any factor but 1 fixes the size and drops the game's dynamic resolution).
- hle_nvn.c paces presents by the cap instead of the game's interval and logs game time against real time every 1,800 presents.
- TOTK profile: fps_target 60. Mac (M4 Pro): 60 presents a second in the opening cave, game time 1.000 of real time; at 30 and 0 as before.
Measured on the Mac (title, opening cave): the alias region is used up to +1.75 GB (the SDK's ~1 GB node
area, then 0.5 GB mapped), bounded by the pool less the heap (1.1 GB). The reservation shrinks from 8 GB
(the Switch's full pool layout, too large for one iPhone reservation without the extended virtual
addressing entitlement) to 5 GB: 256 MB in front + 2.375 GB alias region + 256 MB stack + 2.125 GB heap.
Mac runs of the title and opening cave with it: no crash.
Tears of the Kingdom (two profile keys, off for every other game):
- render_cond: NVN conditional rendering is evaluated at replay from the
timestamp reports' counter 0; before, every conditional clear ran and the
lighting passes drew nothing.
- tex_dirty_upload: textures the game fills from the CPU without an NVN flush
(its 121-layer ASTC terrain arrays) are found through write-protected pages
and uploaded; before, they stayed empty (magenta in ASTC).
Breath of the Wild, issue #9 (iPhone 16 Pro Max killed for memory in the first
shrine; an 8 GB iPhone gives the app 3,375 MB):
- Finalized textures' serials no longer stay in the serial table for good:
live serials are marked at nvnTextureInitialize, dead ones resolve through a
512-entry table (about 10 a frame in BotW before, 276k after 25k frames).
- The memory-pool tracking table grows past 4,096 entries (BotW reached 4,006).
- Most of the growth was the defer_drain leak fixed in 0fc0ce4: the shrine now
stays at 2.8-2.9 GB resident on the Mac, about 500 MB under the limit.
- Falls back to 30 fps at the right speed when the GPU can't hold 60: two cuts
make the frame-interval controller always use its GPU-time path, and the
profile key gpu_timestamps segment gives each timestamp report its
segment's GPU start / end (the default, submission, is unchanged). Under
load, interval 2 held at 0.93-0.98 of real time (0.78-0.95 before). The
check that it still holds 60 on a quiet machine is pending.
NVN: clears inside a pass, with a partial channel mask or a partial scissor
are drawn as a triangle in the open pass instead of dropped, and 8-bit indices
written by the GPU earlier in the submission are widened on the GPU (TOTK red
lines 6 / 7 / 48). Adapted from MacNativeGames (MIT, dm/third_party/macnativegames).
NVN vertex fetch: 8 / 16-bit integer attributes fed to an input of the other
sign are widened by their own sign (CPU conversion, red 96 once); before, the
signedness swap widened them the other way and Three Houses' terrain heights
below 0 left the screen (no ground under close-up cameras). The pass dump's d
line shows polygon offset and depth clamp.
NSP script: a GLSLC capture stage for profiles with shader_glslc_capture
(Three Houses' 9 run-time shaders), so only the NSPs and prod.keys are needed.
AGENTS.md: only the coordinating session runs the multi-game smoke test.
Each part was checked on its own game on the Mac. The multi-game smoke test
runs after this push (Wonder and Engage already passed on the same changes
minus the vertex-fetch fix). The translator fix and its chain rebuild are not
in this commit.
New profile dm/games/0100F2C0115B6000 (update 1.4.3 = v720896; main,
subsdk0 and sdk, the last two the same builds as Breath of the Wild 1.9.0).
It plays the opening and reaches the first gameplay at 30 fps with the HUD,
stable over a 5-minute run. The 3D scene is still black: the half-resolution
deferred-lighting pass leaves its outputs at zero (being investigated).
- Memory: the NPDM declares a system resource (alias memory) and the game
also calls SetHeapSize(0x86000000). Mac partition 8 GB = 256 MB in front +
5 GB alias + 256 MB stack + 2.5 GB heap; the alias high-water mark is
1.75 GB. Too large for an iPhone; to be measured and shrunk.
- SDK 20 calls that reached a service, one abort per boot: oe
GetPreviousProgramIndex (-1); the hardware Opus decoder's C interface,
forwarded to the sdk's software OpusDecoder; cache storage (list reports
none, create / mount use a host folder beside the temporary storage);
fs::IsSaveDataExisting for another title (it asks for Breath of the Wild's
save: none) and MountSaveDataReadOnly; time::ClockSnapshot::
CreateWithStandardSystemClock (0xD0 bytes filled on the host); web::
ShowWebPage (unavailable, as the lobby page).
- shader_split.py reads zstd frames that name a dictionary, from
Pack/ZsDic.pack.zs (Python 3.14+ compression.zstd): 28,669 shaders;
28,636 compile (chain 29bb51ae3ce7).
- New profile key tex_flush_upload, off by default and on only here: TOTK
writes its streamed textures from the CPU straight into pool memory and
publishes them with nvnMemoryPoolFlushMappedRange (9,436 calls at boot);
hle_nvn.c registers such textures by range and marks the ones a flush
covers CPU-written, so they upload from guest memory. Its visual effect is
not confirmed yet (the scene stays black for the lighting reason above).
- docs/agents/03-bringup.md: the new rewriter cases and the SDK 20 calls.
The Hades session's smoke runs of the seven playable games used a host built
with these hooks: only their known red lines.