- The user confirmed the build on the iPhone
- README: games table row and picture (the hotel lobby); AGENTS.md: the list (forty-three games) and a note on the port; development.md: status
- The user confirmed the build on the iPhone
- README: games table row and picture (the first battle: the Archwitch's attack on the soldiers); AGENTS.md: the list (forty-two games) and a note on the port; development.md: status
The host writes Unreal's t.MaxFPS 60 and rhi.SyncInterval 1 into the game's console variables (hle_fpslock.c rows for main f7ec03e4); these are the two limits StevensND's 60 fps mod for the base main lifts with code patches (UEngine::SetMaxFPS returning at once, the name r.VSync broken). Unreal steps the game by real time: on the Mac 59.8 fps, the same route at 30 and 60 matches at time scale 1.00, same resolution, no new red line.
- The user gave the go-ahead after the iPhone build
- README: games table row and picture (Maribel and the hero by the boat at night, the scene the image-handle size query fix brought back); AGENTS.md: the list (forty-one games) and a note on the port; development.md: status
- The user confirmed the build on the iPhone
- README: games table row and picture (the arrival on the island's dock); AGENTS.md: the list (forty games) and a note on the port; development.md: status
- The user confirmed the build on the iPhone
- README: games table row and picture (Vincent climbing the first Nightmare stage); AGENTS.md: the list (thirty-eight games) and a note on the port; development.md: status
- The newest version in the folder is packed (-p / PACKAGE picks another). A registered version with every shader_versions version present builds from the profile as before
- Any other version (the base game alone, another update, other build IDs) builds from a profile derived from it in <id>_build/profile: the profile's keys, that version's build row, and shader tables made from its own files; a red line names the modules whose per-build fixes do not apply
- Host prewarm skips shader keys the shader map does not have (a list of another game version), counted in the "prewarm done" line
- Tested with Hollow Knight: Silksong: the base game alone (1.0.27776) packs with its own 2,244 shaders and plays the opening on the Mac; base + 1.0.30000 builds as before
- README: the update is optional; Silksong's row no longer names it
- The user confirmed the build on the iPhone
- README: games table row and picture (Hornet's needle slash in Moss Grotto); AGENTS.md: the list (thirty-seven games) and a note on the port; development.md: per-game notes
- The user confirmed the spin wait build on the iPhone (the first build ran Rogueport at 24 fps: its three job workers polled the job list with no SVC, 99c3f91)
- README: games table row and picture (Rogueport's docks); AGENTS.md: the list (thirty-six games) and a note on the port; development.md: status
- The game does not step by the time that passed: its frame update (main+0x6a337c) takes a fixed 1/30 s, loaded at main+0x667904, so with only the frame limiter at 60 it ran twice as fast on the iPhone. A second cut loads the 1/60 that rodata already holds (main+0x167a834), the same two words as the community's 60 fps cheat for this build (patch_nso.py). The user confirmed normal speed on the iPhone
- The earlier note that the game steps by elapsed time came from a 0.8 s walk compared by eye on the Mac; the docs now say so, keep the frame-count conversions of that 1/30 as they are, and note that ladders misplay at 60 with the cheat too
- README: games table row and picture; AGENTS.md: the list (thirty-five games) and a note on the port; SDK 4.5.0 is now the oldest so far, not Cuphead's 5.5.0
The game asks for present interval 2 (Unity's 30 fps setting) and nothing else holds it at 30;
display mode presents a frame every tick and the iPhone's display link runs at 60 Hz. Its
animations follow the time that passed: from one save, screenshots at the same moments of the move
to the merchant match at 30 and 60 fps (mean difference 3.9, against 11.0 for frame counting).
DM_PRESENT_INTERVAL=game keeps 30.
At 60 fps a ReadFile into texture pool memory failed with EFAULT in two Mac runs of three: the
dirty-page tracker can write-protect the pages again between dm_guest_write_prepare and pread.
h_fs_ReadFile now prepares the rest of the buffer again and reads on (up to eight times); four runs
at 60 fps: one retry, no failed read.
Unity 2022.3.54f1 IL2CPP on Super Mario Galaxy's sdk build, with Ace Attorney Investigations
Collection's memory layout (1 GB pool, 5 GB block). Three general changes made it play:
- svcGetInfo type 24 (FreeThreadCount): the host answered 0, and Unity creates a thread only when
nn::os::GetFreeThreadCount is at least 1, so its main thread waited at boot for a thread it never
asked for. The host answers 96 (pm's application limit) less the game's live threads, as the
kernel does.
- Movie audio decoders keep 64 outputs (32 since DRAGON QUEST XI S): Unity's movie player starts
only once it holds about half a second of audio, measured at 27 outputs with a peak of 30. Trace
lines (DM_MOVIE_TRACE) name the decoder.
- shader_split.py reads Unity ComputeShader objects (unity_compute), with Unity 2022.3.54f1's layout
in unityfs.py: the post-processing stack's 85 kernels, the colour grading LUT baker among them, so
the battle scene is no longer black. Files whose ComputeShader has no known tree are reported and
skipped: Cuphead's and Engage's tables split byte-identical.
Also nvnSyncSetDebugLabel (a no-op). The game reads no touch, so it stays docked with the 3D pad
layout. README games table, AGENTS.md game list, development notes and the bring-up / graphics
catalogues.
The host writes 60 into Unreal's t.MaxFPS and 1 into nvn.SyncInterval (hle_fpslock.c, this build's rows); DM_FPS_TARGET=30 in Documents/env.txt keeps the game's 30. Mac (M4 Pro): the field holds 60 fps (frame time p95 17 ms, GPU 6.6 ms p50) at normal speed with sound. README, AGENTS.md and the development note say so
AGENTS.md lists them among the playable games (thirty), with what each needed; the README shows
screenshots from Mac runs (Whack Match; Patrick's Parabox's Intro). Both start in the 2D touch layout;
in Big Brain Academy the touches no control takes go to the game's touch screen (handheld). The
profiles and the per-game notes say playable on an iPhone.
AGENTS.md lists it among the playable games (twenty-eight), with what it needed; the README
shows a screenshot from a Mac run. It starts in the 2D touch layout; its quick save (R3) is in the 3D one.
AGENTS.md lists it among the playable games (twenty-seven), with what it needed; the game notes describe the port. It starts in the 2D layout; the screenshot (the first episode's trial) is from a Mac run.
AGENTS.md lists it among the playable games (twenty-six), with what it needed; the game notes describe the port. It starts in the 2D layout; the screenshot is from a Mac run.
AGENTS.md lists both among the playable games (twenty-five), with what each needed;
the game notes and both profile headers say they play on an iPhone. Absolum starts in
the 2D touch layout; in Captain Toad, touches outside the controls touch the game's screen.
AGENTS.md lists both among the playable games (twenty-three), with what each needed;
the game notes and FFT's profile header say they play on an iPhone. Galaxy 2 shares
Galaxy's touch pointer and its one-pixel points and lines.
The host reported a ZCull storage size of 0. Unity 2017.4 allocates each depth
target's ZCull save area from its NVN pool allocator, which gives a zero-size
request the offset of a live block; its map of allocations then lost that block,
and freeing it later released a garbage-sized range. Buffers allocated after that
overlapped live ones, copies read staging bytes other copies had overwritten, and
in about half the runs sprites broke into slivers or went missing, flickering
every fourth frame on the iPhone. The size now grows with the texture
(hle_nvn.c zcull_size). Mac: 20 of 20 scripted Forest Follies runs clean (about
half broken before); confirmed on the iPhone.
Also:
- copy encoder: a copy writing bytes an earlier copy of the encoder reads gets a
barrier (NVN runs copies in order); a copy with overlapping source and
destination is reported red
- paused-thread context for the garbage collector: the guest's x18 from its
thread slot, and sp 32 bytes lower to cover the rewriter's scratch below sp
- docs: the Cuphead notes, the bring-up catalogue and the glitch recipe
Outdoors in Breath of the Wild (the user's Great Plateau save, Mac M4 Pro) a frame had about 386
render passes and loaded and stored about 900 MB of attachments, which an iPhone at 60 fps can
hardly move (about 55 GB/s). 220 of those passes ended only because the game updates the first
64 bytes of a 64 KB vertex uniform block before each draw group (nvnCommandBufferUpdateUniformBuffer
inside the pass) while its shaders read up to 544 bytes of it. A deferred update that covers only
part of what a draw reads used to end the pass.
- Partly covered blocks (nvn_mtl.m ubp_get): the shadow starts from memory and takes every inline
update queued in the submission, newest last, incrementally per range. It is refused, and the
pass ends as before, when something else may have written the range by the time the pass runs:
a copy, fill or feedback write queued in the submission, a storage buffer a shader may write or
a texture->buffer copy over it (sbw, by 4 KB page), or a GPU write or counter report of an
earlier submission still running (gw). A counter report of the same submission does not count:
the host writes reports on the CPU after the whole submission completes (Fire Emblem Engage
writes its reports into a uniform block's page). DM_NVN_UB_PARTIAL=0 turns it off;
DM_NVN_UBP_CHECK=1 lands the updates, waits for the GPU and compares every shadow with memory.
- cb_extent.tsv (new tool dm/gfx/tools/cb_extent.py, c1_guard.py's interval arithmetic over the
translated MSL): per shader, how far into each uniform slot it can read, and the storage slots
it never writes. Like c1_full.tsv, the host loads it only for its chain version; build_ipa.sh
copies it, the NSP script writes it for provisional profiles, and the chain procedure
regenerates it. Without it bindings are copied whole (up to 64 KB). Ten profiles have one.
- Route B's reflection never says whether a storage buffer is written, so every one read as
read-only and its writes went unnoticed: a storage buffer now counts as written unless
SPIRV-Cross typed it const (nvn_refl.m sbuf_rw) or cb_extent.tsv proves the slot unwritten.
- Lazy clears (lc_*): a set of targets that gets only clears before the game sets other targets
no longer opens a pass just to store them; the next pass that attaches the same texture level
and layer through the same view clears it as its load action, and the rest land as before
before anything else could see them. Breath of the Wild's four clear-only passes before the
G-buffer fold into its first pass. Off in TBDR mode; DM_NVN_LAZY_CLEAR=0 turns it off.
- The residency set line counts shadows from memory, refusals by reason, and lazy clears.
On the Mac, old (DM_NVN_UB_PARTIAL=0 DM_NVN_LAZY_CLEAR=0) and new side by side on each game's
route; attachment loads + stores per frame:
- Fire Emblem Engage battles: about 1,850 -> 360 MB, 88 -> 38 passes.
- Tears of the Kingdom: about 1,100 -> 700 MB, 124 -> 116 passes.
- Breath of the Wild outdoors: 386 -> about 158 passes, GPU p50 about 9.3 -> 6.5 ms, submit
5.7 -> 4.2-4.9 ms; where the old host fell to 30 or 20 fps under load, the new held 60.
- Link's Awakening, 13 Sentinels: about 15% less. Super Mario 3D World: 5% less (lazy clears).
- Odyssey, Three Houses: 2-8% less. Mario Wonder, Metroid Dread: unchanged.
No new red lines in any of them. The shadow check found every shadow equal to the GPU's memory
(BotW 346,000, Engage 284,000, TOTK 86,000), and t_nvn passes 68/68. Screenshots differ only by
timing. Tried and dropped: DM_NVN_TBDR=1 (it turns the deferred updates off), DM_NVN_BARRIER=game
(no gain), and same-target continuation outside TBDR mode (BotW's decals end the pass anyway).
What still splits BotW's big passes: the GPU particles' compute dispatches (about 190 MB a frame)
and decals that sample the G-buffer. Mac shader libraries were built for the current chain of
TOTK, Engage, Wonder, Dread, Odyssey, Link's Awakening, Three Houses and 13 Sentinels (c1_full.tsv
headers already matched). The IPA ZeldaBreathOfTheWild-2026-10-21-passes.ipa (an earlier state of
these changes plus the uncommitted frame-step probes) awaits an iPhone test.
No host change was needed. The profile reports 84 MB of used memory (mem_used): the rewritten
modules take 80 MB, and with the default 64 MB SetHeapSize fails and the game aborts at its
first allocation. 3,118 shaders, every binding decoded; prewarm list recorded on the Mac
(boot, title, storybook, Elder Kettle's house, Inkwell Isle One, Forest Follies).
README, AGENTS.md and the game notes list it as playable on an iPhone.
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).
On an iPhone it plays at 30 fps through character creation, name entry and the story opening, with no audio underruns and three saves (a footprint of about 2.35 GB after 3.4 minutes). It joins the table with a screenshot of the castle in the snow; AGENTS.md lists it with the nineteen games and a sentence on its Unreal Engine 5 port; the game notes give the iPhone status.
The IPA built from the Rhythm Heaven Groove profile (base game 1.0.0, handheld with Boost) works on an iPhone.
README lists it (~3 GB) with a screenshot from the Mac (the title screen) and takes it off the Mac-only line; AGENTS.md
counts eighteen games and notes its engine and SDK 21, and the handheld / Boost profile keys that skip the TV latency
calibration.
The IPA built from the Unicorn Overlord profile plays on an iPhone (A20 Pro: 60 fps, sound, a 2.88 GB footprint).
README lists it (~4 GB) with a screenshot from the Mac (the opening scene in the castle garden); AGENTS.md counts
seventeen games and notes the engine it shares with 13 Sentinels and their alloc_big_align key.
The IPA built from the Hades II profile plays on an iPhone. README lists it (~4 GB) with a screenshot from the Mac
(Erebus, the first room of a run); AGENTS.md counts sixteen games and notes its texture slots up to 34 and the staff's
Omega aim lines that look like a rendering bug; the game notes drop "not tested on an iPhone".
The IPA built from b1cd440 plays on an iPhone. README lists it (~7 GB) with a screenshot from the Mac
(Edgeworth's office, the first investigation); AGENTS.md counts fifteen games and says where its Unity 2022 shader and
file-API handling live; the game notes and the profile header drop "not tested on an iPhone".
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).
- TOTK moves from "being ported" into the games table (60 fps on the iPhone, 30 on the Switch); DM_FPS_TARGET=30 in env.txt keeps 30 for battery.
- Monster Hunter Rise: runs on an iPhone at 30 fps from character creation into the opening.
- AGENTS.md: thirteen playable games.
- Profile 0100B04011742000 (update 16.0.2, RE Engine): KPKA PAK shaders (shader_split.py kpka_jobs), XCI base game in nx_extract and the NSP script, BIG_TEXT patch area in patch_nso.py.
- Sparse virtual pool: a 4 KB page table (vpt_*) that the replay reads textures through, dirty tracking per mapped page, texture views from the first mapped level.
- MultiDraw*IndirectCount with a CPU wait for the count; pending GPU writes indexed by page; bindless handles read from queued inline updates.
- sbuf_ubo binding kind, integer vertex formats into float inputs, clears on textures that are not render targets.
- Inline keyboard text, state and finished callback; ngc::ProfanityFilter host vtable.
- Ryujinx BindlessElimination follows phi webs (218 MHR pixel shaders); dmsh_xlate_bin and dmsh_chain.lock updated; dmsh_chain.h and c1_full.tsv rebuilt for every Maxwell profile.
- README: Monster Hunter Rise under being ported, gallery image, XCI base games, Ryujinx local fixes.
dm/games/profile_guess.py decides the profile keys a game's files show, each written with its reason:
graphics vulkan and the modules to load (a subsdk only when main takes something from it the host does
not supply), shader_vk_capture / shader_main_glslc / shader_separable / shader_glslc_capture, the memory
partition (giant bss, no heap, virtual address memory), the SDK 8 / 10 font and NVN API, Hagi's handheld
pointer. --check compares the guess with every profile in dm/games: all 13 agree on these keys.
After the rewrite the script sets mem_used from the rewritten modules (Age of Calamity's 72 MB), writes
the empty shader tables and chain header a provisional Vulkan game lacked, and boots a game with virtual
address memory that sets a heap until its heap request to size the heap region (about 7 s; Tears of the
Kingdom gets its 0x98000000 alias region). A minute's boot could not tell which games need render_cond,
tex_flush_upload or core_preempt, so they stay with the developer.
--provisional builds a game that has a profile from a guessed one (DM_GAMES_FIRST in game.py and the host
Makefile, whose profile header is now rewritten only when it changes); --no-heap-check skips the check.
A capture run whose host stops early now says so. hle_svc.c logs a heap request the pool check refuses.