Commit Graph
50 Commits
Author SHA1 Message Date
seedds 9ca4e233ea README: Pokémon Legends: Z-A moves to the playable table (on an iPhone) 2026-10-21 07:28:35 +08:00
seedds 56b806155f One NVN driver layout for every game; shader sources read from the game files
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.
2026-10-21 07:23:10 +08:00
seedds d34e0446aa Pokémon Legends: Z-A plays on the Mac: Trinity packs through the game's Oodle, transform feedback, stencil views, the base instance in the driver segment
- Profile 0100F43008C44000 (2.0.2, SDK 19.3.11): 5 GB layout with two cuts (StandardAllocator 12 → 4 GB, node area), mem_map_limit (the pool enforced as on Horizon), shader_oodle.
- Shaders: Trinity packs (arc/data.trpfs, FlatBuffers, Oodle Mermaid) decoded with the game's own OodleLZ_Decompress in the Mac host; 28,603 shaders.
- SDK 19: GetAudioOutBufferCount, crypto random bytes, ToPosixTime with a rule, NVN state getters, CommandBufferFenceSync, the 64-bit WaitForAddress type.
- Graphics: transform feedback (vertex capture entry point, rasterizer discard), layered passes (the colour grading LUT), point size dropped beside a layer output,
  function constants specialized, a stencil-only view for STENCIL-mode texture views (striped lighting), sampler min / max reduction (Metal 26),
  the draw's base instance at c0 + 0x644 (occlusion boxes; characters' legs folded back from the hips), the zero cube for a 2D texture in a cube slot.
- Shader chain changed (dmsh_build.py): every profile's dmsh_chain.h rewritten; the other profiles' c1_full lists carried over (that change touches no c1 read).
2026-10-21 03:47:16 +08:00
seedds aa65318b33 README: Octopath Traveler 0 runs at 60 fps (tested on an iPhone) 2026-10-21 03:04:38 +08:00
seedds 5b2aa07f7b Octopath Traveler 0 runs at 60 fps: the host writes Unreal's t.MaxFPS and rhi.SyncInterval 2026-10-21 03:00:56 +08:00
seedds 79d82f7b64 Remove the D2R code and the legacy frame step
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).
2026-10-21 02:09:49 +08:00
seedds dcd6a52c00 README: Octopath Traveler 0 moves to the playable table (on an iPhone)
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.
2026-10-21 01:59:59 +08:00
seedds 8627ac02e8 System keyboard: the guest clock keeps running, as on the Switch (Octopath Traveler 0's sound stuttered on an iPhone)
While swkbd::ShowKeyboard was up the host froze the guest clock (d2r-mobile's choice, so that monsters did not attack
while the player typed). Unreal calls it off its game thread, so the game kept running, and every thread that waits
on the clock spun while its audio mixing starved: on an iPhone the sound stuttered and the input box lagged (one
4.7 s frame, 5,035 wakeups a second while the box was up instead of about 1,800, no audio underruns). On the Switch
the applet blocks only the caller while the clock runs, which the host now does too. The test switch
DM_TEST_SWKBD_HOLD_MS (with DM_KBD) holds the keyboard up on the Mac: frozen, 8.8 fps and 7,936 wakeups a second in
that window; running, 30 fps and 1,788. The inline keyboard keeps the freeze: its game (Monster Hunter Rise) does
not block on hardware, and the host's input box blocks it.
2026-10-21 01:31:52 +08:00
seedds c9f6683742 Octopath Traveler 0 plays into free play on the Mac: dynamic texture slots, image atomics, save calls
A 26-minute run from a fresh save goes through character creation and the story opening into free play with no red
line; the game saves four times and a later boot continues from the saves. Not yet run on an iPhone.
- Dynamic texture slots (profile key tex_dyn_slots, on for this game only): 61 vertex shaders (particles) pick one of
  several c2 table slots at run time, which the translator cannot trace to one slot (Ryujinx's 32-entry fallback
  arrays, <stage>_<t|tb|s>_cb0_40000000). The reflection reads them as NVN_RB_TEXD / NVN_RB_SAMPD arrays, the replay
  writes slot indices into that shader's c2 tables and binds slot k at element k. They used not to load; the castle
  scene's snow is back. Host only: no chain version changes.
- Image atomics: SPIRV-Cross's MSL 2.3 emulation binds a device atomic pointer (<image>_atomic) beside the image, which
  the build reads as a second image of the slot, so the replay put a texture resource ID where an address belongs.
  NVN_RB_IMGA binds the storage of a buffer or linear image there (a buffer texture is a view of its pool); a
  block-linear image's atomics go to the dummy buffer (logged). 2,084 shaders in this game, at least ten run.
- Buffer textures at an offset below Metal's reported 16-byte alignment are created when the offset is a multiple of
  the element size (measured on an M6: views at offsets 4, 8 and 12 read the right elements); they were dummies.
- The first occlusion-query reset inside a pass without a visibility buffer splits the pass, so it is counted.
- A buffer texture in a slot a shader reads as another type (a cube) reads 0 instead of undefined data.
- Service calls: nn::fs::GetSaveDataSizeMax (SIGSEGV at the first save, about 13 minutes in), GetCacheStorageSize,
  GetFreeSpaceSize, account::PushOpenUsers, web::ShowOfflineHtmlPage, oe::ExecuteProgram, found by listing main's
  unhooked imports.
- The engine is Unreal Engine 5 (its paged global distance field shaders and LowLevelTasks), not 4.26 / 4.27; the 16
  compute shaders with 3D image atomics are that global distance field, which the mobile renderer does not dispatch.
Tears of the Kingdom boots the same with the new host (no shader libraries there, so the new bindings are not
exercised); the dm/gfx unit tests give the same results as before the change.
2026-10-21 01:06:04 +08:00
seedds 476b1135b4 Octopath Traveler 0 boots on the Mac: Unreal pak and Oodle shader decode, the host's allocator for Unreal
Octopath Traveler 0 1.0.5 (Unreal Engine 4.26 / 4.27 mobile renderer, SDK 19.3.13) plays from boot through character
creation into the story on the Mac (15 minutes with the iPhone memory layout, no crash); not yet run on an iPhone.
- Shaders: the 10,324 are in the two shader code archives of the game's pak, each one Oodle-compressed. uepak.py reads
  the version 11 pak (AES-256 index; the key is the window of main that decrypts it, never printed or written; AES
  through CommonCrypto) and the archives; the Mac host decodes them with the game's own OodleLZ_Decompress
  (DM_OODLE_JOB, hle_oodle.c, build-gated) and shader_split.py reads the decoded LSLG records (profile key
  shader_ue_decode; the NSP script's decode_ue_shaders stage). Bindings measured: Engage's separate texture layout.
- Memory: Unreal's nninitStartup reserves a 62 GB address region and its FMallocBinned3 37 GB of it. A cut skips the
  allocator's constructor and the host puts its own FMalloc vtable on a malloc zone in the object (hle_uemalloc.c);
  the region is capped at 2 GB with the game's own -SwitchVirtualAddressSpaceSize (new profile key game_args, also
  for the iOS app) and SDK 19's node area is cut to 62 MB as Engage's, for a 3.75 GB guest block.
- Host: game arguments are set before nninitStartup, as on hardware; an undefined weak import nothing defines binds
  to 0, as rtld binds it (Tears of the Kingdom boots the same with the old and new hosts: its weak glslc* imports
  now resolve to 0); nvnCommandBufferFenceSync (OP_FENCE_SYNC), the texture / sampler pool getters,
  vi::GetLatestFrameNumber / ListFrameHistory; DM_ALIAS_WINDOWS for sizing fixed-window allocators.
- Rewriter: a byte jump table whose add sits 17 instructions before its br (jump_bytes_far, tried last).
- build_ipa.sh quotes FONT_PY (a python path with spaces failed the font step).
Open: 61 vertex shaders pick a texture slot at run time (dynamic handle), 16 compute shaders with 3D image atomics
do not build. The profile (dm/games/01005270232F2000) holds the shader tables, translation chain b-ba76ba138e78.
2026-10-20 23:01:20 +08:00
seedds 906d56fff9 Unicorn Overlord boots on the Mac: Vanillaware profile, multi-stream Opus C interface, CSRNG, alloc_big_align
Unicorn Overlord 1.05 (SDK 16.2.4, Vanillaware's engine as in 13 Sentinels) plays from boot through the title screen
into the opening story scene. Three boots, three fixes:
- the hardware multi-stream Opus decoder's C interface (CRI's Opus streams; the size query takes the SDK 12+ option
  argument) goes to the sdk's software OpusMultiStreamDecoder, as the single-stream one does (hle_codec.c);
- nn::crypto::GenerateCryptographicallyRandomBytes, which the sdk's own getentropy calls under std::random_device,
  fills from the host's entropy stream instead of opening csrng (hle_os.c);
- 13 Sentinels' 64 KB alignment of large StandardAllocator blocks becomes the profile key alloc_big_align. The engine
  cuts ring heaps out of a memory pool at 64 KB GPU alignment; with 4 KB aligned pool storage one kept base 0, and the
  game wrote to 0x268 right after the title screen. 13 Sentinels sets the key (its profile lists only the 1.10 build the
  old check named), so its behaviour is unchanged.
The profile (dm/games/010069401ADB8000) holds 1,725 shaders from 2,188 GLSLC outputs in main, Mario's binding layout
with every binding decoded, translation chain b-239c170b7616.
2026-10-20 21:35:54 +08:00
seedds 38b7f27fa9 README: Hades II moves to the playable table (on an iPhone)
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".
2026-10-20 20:57:29 +08:00
seedds e27cbcb0b4 Hades II boots on the Mac: NVN profile, texture slots up to 34, SDK 19's nn::ro::LoadModule
New profile 0100A00019DE0000 (base game v0, display version 0.0.126424, SDK 19.3.9), from the NSP script's provisional
profile. Unlike Hades it renders with NVN: 88 BNSH shaders in Shaders/NVN/bin; the ExeFS holds only main and sdk.
- Shaders bind textures at fixed, sparse slots (0, 1, 3, …, 31 and 34): SceneFade.frag reads c2 0x130 = 0x20 + 8 x 34,
  past Mario's 32 slots, so dmsh_build left that binding undecoded. nvn_tex_slots 35; the game has no image bindings.
- hle_ro.c: SDK 19's LoadModule takes one more bool (…mib), which the older hook did not catch; it and ro::Finalize
  are hooked. The Epic Online Services NRO stays offline as in Hades (RegisterModuleInfo fails).
- Memory: no system resource; the 3.10 GB heap fits the 4 GB layout; main's 82 MB bss gives mem_used 108 MB.
- nvn_mtl.m: DM_NVN_HILITE set its keys and library up lazily on whichever thread built a pipeline first; with several
  keys two threads raced and one used a library the other had replaced (objc_msgSend crash in pso_desc). Set up once.
On the Mac: title, a new save, the opening and Erebus with combat at 60 fps, resident memory flat at about 3.4 GB,
one red line (the NRR). The two long lines through Melinoë while the staff's Omega attack charges are the game's own
aim lines (WeaponStaffSwing5: AimLineCountOverride 2, AimLineAngleOffsetOverride 180), traced with vertex dumps and
probes; docs/development.md says so. IPA HadesII.ipa (4.0 GB) built; awaits an iPhone test.
2026-10-20 20:54:38 +08:00
seedds bc5815acc9 README: Ace Attorney Investigations Collection moves to the playable table (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".
2026-10-20 19:58:05 +08:00
seeddsandarmyja e437e80ab6 Ace Attorney Investigations Collection boots on the Mac: Unity 2022 shaders, the C library's file checks and clock
New profile 010005501E68C000 (update 1.0.0.2, Unity 2022.3.17f1 IL2CPP, SDK 17.5.3; the base game an XCI):
- Rewriter: two IL2CPP thunks end in an indirect tail call, ldr x18, [x0, #0x18]; ...; br x18 (main+0x1d271bc,
  +0x204ebec), which asserted as an unknown jump table. When neither table form is found, the last write of x18 is a
  plain load and the br ends the function, it becomes x16 = guest x18; br x16 (tail_call / site_br_call). Tried only
  where the rewriter used to stop, so modules it accepted before come out byte-identical.
- sdk cut: the AddressRegionManager node area 1 GB -> 62 MB on this build (sdk+0x139468 / +0x1394fc), as for Engage;
  StandardAllocator::Initialize aborted in nninitStartup without it.
- Shaders (unityfs.py, shader_split.py): Unity 2022 keeps each program's parameters as blob entries of their own
  (m_ParameterBlobIndices; the programs are m_PlayerSubPrograms[tier][].m_BlobIndex), and subprogram version
  202012090 (from 2021.2) has no local keyword list. The player's files borrow the unitybuiltinshaders bundle's Shader
  type tree. 4,951 shaders, 4,950 build (one geometry shader); Engage's layout of driver segments decodes every binding.
  UnityPy's type trees for 2020.3.18 and 2021.3 have neither field, so Engage reads every entry as before.
- hle_fs.c: nnmusl (open / stat / opendir) checks every path with nn::fs::IsPathMounted, GetPathLengthMax,
  GetUtf16PathLengthMax, GetEntryNameLengthMax and GetUtf16EntryNameLengthMax, which the SDK answered from its empty
  mount table: File.Exists failed with no nn::fs call, Addressables never found settings.json and the game crashed on
  a null asset (main+0x31100ec). Answered from the host's mount table with Horizon's 0x300-byte limits.
- hle_time.c: clock_gettime(CLOCK_REALTIME) reads the source nntimeStartup selects (sdk+0x8e414),
  nn::time::GetStandardUserSystemClockCurrentTimespec, which asked the time service; system_clock::now() then called
  std::terminate after the first dialog. It and the adjustable clock's getter are the host clock now.
- Memory: Engage's 5 GB block with a 1 GB application pool. StandardAllocator commits 0.19 GB; with a 2.5 GB pool
  its reservation and Unity's own pools reached the end of the 4.375 GB alias region after 140 s (probes on
  VammManager::AllocateAddressRegion / FreeAddressRegion). 25 minutes now peak at +2.81 GB.
On the Mac: first-boot settings, title, Investigations 1's Episode 1 into the first investigation, 60 fps, FMOD sound,
25 minutes with no red line, 1.55 GB resident and 1.25 GB of Metal allocations, flat.
IPA AceAttorneyInvestigations.ipa (7.0 GB) built from HEAD + these changes; awaits an iPhone test.

Also: UNIMPL stubs log once (the seen counter was a byte and wrapped every 256 calls); build_ipa.sh quotes FONT_PY
(a Python path with spaces broke the font step); profile_guess.py marks IL2CPP by global-metadata.dat (Unity 2022's
main has no il2cpp_ names). Not needed in the end: pull requests #15 (this main's string table is intact) and #16
(nninitStartup sizes no heap with virtual address memory).

Much of the bring-up was found first by armyja (GitHub issues #17, #18, #19, pull requests #15, #16).

Co-authored-by: armyja <546323405@qq.com>
2026-10-20 19:55:55 +08:00
seedds 2515a5b759 Hyrule Warriors: Age of Calamity runs at 60 fps; battles convert only the vertices each draw reads
60 fps (30 on the Switch), two host-only cuts on 1.3.0 main (patch_nso.py):
- nnMain sets KTGL's device up with present interval 2 (main+0x8d94b0), and the device's frame rate, 59.94 / interval,
  is what its event scripts step by (main+0x7802c4). The config interval becomes 1, the engine's own 60 fps mode.
- A KIDS task waits until 1 / fps has passed since the last frame (fps 30 from engine data) and then steps the game by
  the time that really passed. Its wait becomes 1/60 s (main+0x166db80).
On the Mac the opening scenes at frame 2N match the original's frame N (same subtitle, same poses) and hold 60.

Battles ran at 25.8 fps at a 30 or a 60 fps target: the replay converted the soldiers' Half2 / Short2 attributes (fed
to integer shader inputs) on the CPU over whole bound mesh pools, 4.4 million vertices per submission at about twelve
submissions a frame, 24 of the 32 ms the replay took. In nvn_mtl.m:
- Profile key vconv_range (Age of Calamity on): only the vertices each draw reads (first / count, or the indices'
  smallest and largest, kept while the index pages stay unwritten and dropped once a GPU write is queued on them),
  bound as many vertices before the conversion as the range starts at. 219,000 vertices per submission;
  DM_NVN_VCONV_CHECK=1 found no stale index range in 983,000 reuses. Keeping the conversions across frames did not
  pay: the soldiers' attributes change every frame.
- vconv_fill: loops of their own for the 8 / 16-bit integer and the half formats (byte-identical to vconv_elem for
  every format in an offline test; affects every game that converts).
- The visibility buffer ring is as deep as the command allocator pool (8, was 4): the submit thread waited for the
  previous frame's GPU work.
First battle on the Mac (M4 Pro): 58.6 fps, frame p50 16.9 ms, replay CPU 10 ms, GPU 11 ms. On an iPhone the GPU will
likely decide in battle; the game steps by measured time, so it keeps normal speed below 60.
IPA HyruleWarriorsAgeOfCalamity-2026-10-20-60fps.ipa built from HEAD + these changes; awaits an iPhone test.
2026-10-20 16:20:54 +08:00
seedds b1a5774a1b Breath of the Wild: a render scale (DM_RENDER_SCALE 0.5 to 1.0)
A stub sets the scale where the game's dynamic resolution applies its own (main+0x1266880), so the scene buffers, the
shader's resolution ratio and the LOD check all follow it; a second stub takes that apply block every frame when the
factor is not 1.0 (the game only runs it after recomputing its scale). 1.0, the default, keeps the game's dynamic
resolution. Mac: at 0.5 every scene pass halves while the shadow maps, the 1920x1080 output and the HUD keep their
size; handheld works too (960x540 at 0.75). The Mac's GPU time does not change (about 650 passes a frame, fixed cost
first); the iPhone test decides whether it helps there.
2026-10-20 13:04:01 +08:00
seedds 77019a9d02 Breath of the Wild and Tears of the Kingdom: one 60 fps frame step, in whole vsyncs decided by the host
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).
2026-10-20 12:15:32 +08:00
seedds d08659f3ac Monster Hunter Rise: 60 fps (the engine's frame limit raised by the host)
- hle_fpslock.c: on every present the host writes the profile's fps_target into RE Engine's frame limit at [main+0x1295b3f0]+0x434 (FPSLocker's address for build c9a3dd77; the game sets 30 after boot). Its frame loop (main+0x6b106c8) waits until [+0x430]/[+0x434] sixtieths of a second have passed and steps by the time that really passed.
- The game also asks for present interval 2: the profile's present_interval display ignores it. Both are needed; either alone stays at 30.
- Profile: fps_target 60, present_interval display; DM_FPS_TARGET=30 gives back 30. Mac: frame time p50 16.67 ms.
2026-10-20 10:44:57 +08:00
seedds 6acac1b6a6 Tears of the Kingdom: 60 fps at normal game speed; a render scale
- 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.
2026-10-20 09:57:52 +08:00
seedds c01fad8cdb IPA builds: parallel packer, reuse of unchanged stages
- dm/ios/tools/pack_ipa.py replaces the Payload/ copy and Info-ZIP: it packs the app, the shader
  libraries, the add-on content and the RomFS straight from their folders, reading and deflating on
  all cores (zip64, modes, symlinks, the profile's zip_store as before). Metroid Dread 156 s -> 12 s,
  Breath of the Wild 171 s -> 57 s (the T7 writes at about 310 MB/s).
- The shader libraries no longer go through Xcode (Gen/msl left the project): no 1.5 GB detour
  through the home disk.
- NSP script: the rewrite, the shader split and the iOS / Vulkan shaders are reused when a sha256
  of their inputs matches the one stored beside their output; the modules are rewritten in
  parallel; J = the core count; the extraction is kept by default (--no-keep-extracted).
  A Metroid Dread repeat build: about 8 min -> 19 s.
2026-10-20 09:17:17 +08:00
seedds 8d7f957fa2 Monster Hunter Rise: plays on the Mac into the opening; translator phi fix, all chains rebuilt
- 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.
2026-10-20 07:17:31 +08:00
seedds 6563bb1990 NSP script: provisional profiles read their switches off the game files; a short heap check
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.
2026-10-19 18:50:50 +08:00
seedds c1529929a2 Three Houses: Mac input route for 60 fps
The route from a new game into the prologue battle counted presented frames at 30 fps. At 60 fps its
frame numbers double, and the repeating confirm step becomes Left, A, Left, A: A, Left, A loops forever
once it starts on an open dialog (A answers No, Left changes the selection, A reopens it), which
happened at character creation because A presses during the opening movie pause it and shift the
scenes after it. The new route reaches the battle in about 7 minutes. The DM_KEYS example in
05-debugging uses the same cycle.
2026-10-19 18:49:53 +08:00
seedds 0852da4980 Three Houses: 60 fps also in the opening movie and character creation
The render thread forced present interval 2 while either of two flags was set: one for the full-screen
movie image (the opening movie), one from the scene object (the character creation screen after it, and
similar scenes). The engine's own 60 fps modes dropped to 30 there to keep 1080p; with the per-mode
tables at 1 it would also run the render thread's own steps at half speed. A cut at main+0x50fb7c
ignores both. Mac, new game: 59.9 fps through the movie and character creation; the movie lasts
210.6 s in both the original and the 60 fps build.
2026-10-19 18:11:33 +08:00
seedds cc49b46ef4 Fire Emblem: Three Houses runs at 60 fps
KTGL already updates the game 60 times a second on its main thread (a fixed 1/60 s step); the render
thread presents the latest update with the render mode's interval, 2 in the docked and handheld modes.
Per-build cuts on 1.2.0 main (patch_nso.py) set the per-mode interval tables to 1, as in the engine's
own unused 60 fps modes, so every update is shown at the same game speed. Two more cuts make the waits
cheap on the host: the frame limiter's yield-and-poll loop sleeps the time left (it spun 2 ms a frame
and ran at 59.5 updates a second), and the render thread's wait for the next update gets one frame
instead of 16 µs (a millisecond count passed to a microsecond wait; about 50,000 wakeups a second).
Mac, prologue battle: 59.9 fps, 55% of a core, 1.8 W of CPU energy (the original at 30 fps: 44%,
1.7 W). Not yet tested on an iPhone.
2026-10-19 17:37:37 +08:00
seedds c5955d42a6 English only: no Chinese left in code, scripts, logs, tables or docs
- Log messages, script output, test labels, Makefile errors and comments are English. Red log lines start with "[RED] " (was 【红】).
- Shader map stage column: vertex / pixel / compute / geometry / hull / domain (generators, dmsh_build / dmsh_facts and nvn_mtl.m stage_of follow).
- The NSP script's regexes match the new shader_split.py / dmsh_build.py summary lines; patch_nso cut scopes are all / host-only.
- Host UI text table (dm_i18n_tab.h) is English only; dm_lang_zh() removed.
- Text that matches game data (t_swkbd.c, t_textbox.c) is written as \x escapes.
- Shader chain: the chain files lose their bilingual comments, so every profile has a new chain version. dmsh_xlate.dll rebuilt from the translated Program.cs (SPIR-V byte-identical on 4,500 shaders of Wonder, TOTK and Age of Calamity), lock and dmsh_chain.h headers regenerated. c1_full.tsv restamped for Dread, 13 Sentinels and Wonder; the other profiles follow when their rebuild finishes.
- AGENTS.md and docs/agents: the language rule now covers logs; quoted log lines updated.
2026-10-19 15:34:57 +08:00
seedds c162b95a93 Super Mario Galaxy: profile 010099C022B96000 (1.3.1, SDK 18); plays on the Mac
The Wii game on NERD's Hagi layer (PowerPC code recompiled into main), rendering
with Vulkan through Hades' layer. Title screen, file creation and the storybook
prologue into the Star Festival at about 58 fps on the Mac; the IPA awaits an
iPhone test.

- Vulkan layer (hle_vk.m): push constants, descriptor arrays, dual-source
  blending variants, copies inside a render pass (cut and resume with deferred
  store actions), linear images over their memory, timestamp and occlusion
  queries, pipeline caches, vkCmdDraw, image copies and read-backs, USCALED /
  SSCALED and three-component vertex formats, viewport and scissor kept across
  passes, the INHERIT swapchain transform, the vi layer crop at present.
- vk_shaders.py: ArrayStride on struct members, push constants, array counts,
  `_d` dual-source variants, extra SPIR-V input dirs; non-vert/frag stages
  skipped with a red line.
- Run-time SPIR-V capture: DM_VK_CAPTURE in the layer, profile key
  shader_vk_capture and vk_capture.tsv (3,089 keys), and the NSP script's
  Vulkan shader capture stage.
- Handheld pointer: profile key op_mode, touch hooks fill touches only in
  handheld mode, InitializeTouchScreen(int,int), DM_KEYS taps, iOS gamepad
  forwards untaken touches to the game.
- SleepThread under 20 us takes the yield back-off (Hagi's 100 ns poll kept a
  core at 100%).
- nx_extract / NSP script: --allow-damaged for NSPs whose files fail the NCA
  hash check.
2026-10-19 14:35:31 +08:00
seedds d481cb2f25 Yield back-off starts after 2 ms of yields: Tears of the Kingdom holds 30 fps again
The back-off (each yield sleeps an eighth of the run so far, 10-250 µs) started after 200 µs of consecutive yields. That also caught TOTK's main thread waiting for its ModuleSystem workers, about 350 yields a frame in runs of a few hundred µs on the frame's critical path: a sleep that overran the workers' finish pushed frames past 33 ms, and the game then drops itself to present interval 3 (20 fps) for a while. Mac, two runs side by side: 9 drops in 300 s and frame p95 41.7 ms with the back-off, 0 drops and 34.4 ms without, at the same CPU time. On the iPhone frame p95 was 87-91 ms and the threads stayed on efficiency cores at 1.5 GHz.

Waits under 2 ms are handoffs between working threads; limiters and idle polls run far longer. With the 2 ms start TOTK's main thread and busiest worker never back off (0 drops, 30.00 fps, as with the back-off off), and Three Houses' 23 ms limiter wait still does: CPU 78% of a core without the back-off, 19% with it, 14% with the old start, same frame times. DM_YIELD_BACKOFF=<cap µs>[:<start µs>] sets both (default 250:2000).
2026-10-19 09:56:06 +08:00
seedds 9020ca4edf Power lines show render-target traffic and guest CPU by Switch priority; DM_QOS_MAP maps priority to QoS (off by default)
- hle_nvn.c power_line: the 驻留集 line ends with attachment bytes loaded and stored per frame, passes that load and store everything, and the stores DM_NVN_UNWRITTEN_DC saved (mtl_bw, so far only in the per-frame TSV, which phone runs do not send). A new 线程 line gives guest-thread CPU in ms per second by priority band (<= 44, 45-59, >= 60) and the three busiest threads (dm_thread_cpu in hle_thread.c).
- hle_thread.c DM_QOS_MAP=1 (DM_QOS_EDGES=44,59): priority <= 44 user-interactive, 45-59 user-initiated, above utility, at thread start and on SetThreadPriority (applied by the thread itself at its next SVC). A thread waiting in ArbitrateLock lends its class to the owner until the hand-off (pthread_override_qos_class_start_np). Host helper threads then set their own class instead of inheriting the creating guest thread's: AudioOut notify, silent sink and vsync user-interactive, movie decode user-initiated.

Measured on the Mac: BotW runs 527 ms/s of guest CPU at priority <= 44 and 65 at 45-59, Mario Wonder 138 and 4.5, Three Houses all at <= 44, so the map has little to move; it stays off for a phone A/B. The same runs settle the TBDR question: DM_NVN_TBDR=1 cuts BotW's stores 13% and Wonder's 42% but raises GPU p50 (8.8 → 10.4 ms, 2.8 → 3.5 ms) and CPU energy (+22%, +73%).
2026-10-19 09:52:00 +08:00
seedds df7badd23a Power: no busy-wait yields, no per-frame GPU allocations, cheaper replay, uniform updates inside passes
General host changes so the games stop doing work the Switch never did; frames and frame rate unchanged
(Mac A/B with the same routes, 11 games, no crash, red lines as before; dm/gfx/tests results as HEAD).
- Yield back-off: a run of SleepThread(0) with no other SVC sleeps an eighth of the wait after 200 us
  (10-250 us; DM_YIELD_BACKOFF). Three Houses' frame limiter yielded 228k times a frame on the iPhone;
  Mac CPU energy 3.5 W -> 0.5 W. BotW's PlacementMgr / RadarMgr spun the same way.
- SVC exit takes the global lock only while a pause is pending; a signalled event wakes only its
  waiters; the audio renderer and upload / QueueFinish waits block instead of polling.
- Upload staging: each upload slot keeps its block (BotW allocated, made resident and freed a 32 MB
  block per re-upload, ~3 GB/s); a re-uploaded texture alternates with a kept spare (movie frames,
  Link's Awakening title: submit 1.64 -> 0.37 ms); the residency set skips repeated adds / removals;
  copy parameters from the frame arena; host pre-writes unprotect page runs with one mprotect.
- Replay: per-thread memory-pool address cache, per-encoder state filter, GPU-only texture IDs stay
  cached across guest writes, kept dirty-range handle per packaged texture. Instructions per draw:
  TOTK -40%, Dread -32%, Odyssey -19%, 13 Sentinels -20%; replay kernel time BotW 1.3 -> 0.1 ms.
- Inline uniform updates inside a render pass are deferred to the pass end; later draws read a
  shadow made of the deferred updates when they cover the bound range, else the pass ends first
  (DM_NVN_UB_DEFER=0). Dread: 272 -> 26 passes a frame, GPU 8.3 -> 2.8 ms, CPU energy 1.2 -> 0.4 W.
- Whole-level format-changing copies do not load the destination.
- Measurement: [nvn] 功耗 (CPU energy W and mJ/frame, CPU, P-core share, wakeups, thermal state, top
  yielders) and [nvn] 驻留集 (residency churn by source line, pass barriers, cuts, pass splits) every
  300 frames; iOS thermal state and brightness. Opt-in switches for A/B: DM_RT=0, DM_NVN_BARRIER=game.
Docs: development.md Power section, 04 section 7 switches, 05 power lines, dm/gfx/README fixes.
2026-10-19 07:21:08 +08:00
seedds dacae6966e README: Hades, Age of Calamity and Three Houses playable on the iPhone; NSP script packs Vulkan games
Hades, Hyrule Warriors: Age of Calamity and Fire Emblem: Three Houses move into the Games table and
the screenshot gallery (Mac captures); Tears of the Kingdom is listed as being ported. The NSP script
now handles a graphics vulkan profile: vk_shaders.py translates the SPIR-V shaders for the Mac and iOS
in place of the NVN shader split and chain build (the change from an uncommitted worktree, ported to
the current script; Hades packed with it: 74 shaders, 3.6 GB IPA). AGENTS.md and the development
notes follow.
2026-10-19 01:28:33 +08:00
seedds b0a9079d24 nvnQueueFinish waits for the GPU only, not for queued presents: Three Houses' first load 78 s -> 6 s
Fire Emblem: Three Houses' KTGL loader calls nvnQueueFinish after each resource upload, and mtl_finish
flushed the present queue first: with a window that waits for the display link to take every queued
present, one or two display frames per call. QueueFinish now waits for the submitted GPU work only, as
on the Switch; AcquireTexture still waits for a texture's previous present, and mtl_drain keeps its
flush. Windowed Mac run: first load 6.1 s, frame pacing unchanged.
2026-10-19 01:16:20 +08:00
seedds d981fdc8f7 Texture compare: one channel-mapping helper; docs for the layout-capture fix
nvnTextureCompare and nvnTextureGetSwizzle share tex_swz. The BotW docs
keep the capture investigation, now told from the cause (#6). Odyssey's
layouts go through the same compare-and-replace code (main+0x982320):
with the fix its alpha_from_red list converts no texture on the Mac
smoke route to frame 20,100 (main's host did from frame 4129) and the
HUD is pixel-identical; the list stays until an iPhone run.
2026-10-19 00:06:15 +08:00
seedds b3f25a09bb Shader chain: translator fixes for 39 shaders; compiled shaders cached by their Metal source
- Ryujinx (vendored, Instructions.cs GenerateTextureSample): texel offsets read from a
  register are built with OpCompositeConstruct + Offset, and a four-offset gather becomes
  four one-offset gathers. Upstream wrote an OpConstantComposite, invalid SPIR-V that
  SPIRV-Cross refused (2 shaders each in Wonder, Link's Awakening, TOTK, Age of Calamity).
- dmsh_xlate: a shader whose SPIR-V declares a 64-bit float is translated again with
  Ryujinx's DoubleToFloat pass (30 TOTK main-material vertex shaders); TOTK's
  visibility-counter vertex shader gets MacNativeGames' subgroup lowering (MIT).
  Every other shader's SPIR-V is byte-identical.
- New dm/gfx/tools/dmsh_mtlcache.py: dmsh_build.py compiles each MSL through a cache keyed
  by the source, the compile flags and the compiler (<cache>/cas/metallib/). Before a build
  it is filled from the earlier b-*/ folders beside the output and IOSOUT, and from
  DMSH_PREFILL, once a fresh compile of a few of a folder's shaders reproduces their
  libraries byte for byte. A chain change that leaves a shader's MSL unchanged no longer
  recompiles it.
- dmsh_build.py clones files with the clonefile call instead of one cp per file: each fork
  of the build process cost about 6 ms and serialized the finishing phase.
- TOTK's rebuild after the translator fix: 49 min -> 9 min, libraries byte-identical to
  the previous build apart from the 39 fixed shaders. All 12 chain versions are new;
  Dread, 13 Sentinels, Three Houses, TOTK and Wonder are rebuilt (Mac + iOS + c1_full.tsv),
  the other six follow.
2026-10-18 23:55:19 +08:00
seedds 840171d448 TOTK's 3D scene renders; BotW issue #9 memory and 30 fps fixes; in-pass clears; GLSLC capture in the NSP script
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.
2026-10-18 22:30:11 +08:00
seedds 8b21049d2a Age of Calamity and Three Houses play on the Mac; indirect NVN draws; Dread GPU leak fixed
Hyrule Warriors: Age of Calamity (01002B00111A2000, 1.3.0) reaches its first
battle on the Mac:
- shader_split.py reads Koei Tecmo's RDB / IDRK archives (G2S `_S2G` / `_DOK`).
- Profile key mem_used (DM_GAME_MEM_USED) for svc GetInfo UsedMem; the default
  stays 0x4000000 for every other game.
- patch_nso.py: jump tables indexed by x30 / x29 (capstone names them lr / fp);
  a per-build cut for AoC's KTSL sound heaps.
- NVN: the compute program is bound apart from the graphics stages (cprog);
  nvnTexturePoolRegisterImage; DrawArraysIndirect; reflection c1 on the heap.
- oe copyright-frame calls and the hardware multi-stream Opus decoder answered.

Fire Emblem: Three Houses (010055D009F78000, 1.2.0, SDK 6.5, KTGL) plays its
prologue on the Mac:
- shader_split.py reads LINKDATA / SPKG with KT chunked zlib and keeps GLSLC
  outputs with 0x848 / 0x850 controls and headerless compute; 9 run-time GLSLC
  compiles are captured once with DM_GLSLC_CAPTURE (hle_glslc.c, profile key
  shader_glslc_capture).
- NVN: submissions serialized across the two queues; GetCommandMemoryUsed counts
  from AddCommandMemory; per-pool texture / sampler ID ranges; depth clamp
  replayed; integer vertex inputs fed half / unorm / snorm / packed formats get
  float32 bits converted on the CPU (also fixes AoC's flat soldiers).
- SDK 6: OpenPreselectedUser returns a Result; the audio renderer's DSP command
  header layout. DM_KEYS steps run in time order.

NVN indirect paths: quads and triangle fans from DrawArraysIndirect, and
DrawElementsIndirect, go through a GPU compute conversion into indexed indirect
draws; DispatchComputeIndirect is implemented (AoC's battle effects; red line
88 gone).

Metroid Dread GPU leak: defer_drain() runs in its own autorelease pool. Run on
the guest submit thread, whose implicit pool never drains, it kept every
retired object (and its upload staging block) alive; under CPU load Dread
climbed to the 24 GB watchdog. The committed tree had the same leak; it is also
behind Breath of the Wild's memory growth in issue #9.

NVN pass dump diagnostics (TOTK): TEXSNAP copies the attachments after the pass
on the Metal 4 stream; DM_NVN_DEPTHOVR=<fs key prefixes> forces depth ALWAYS;
D lines show the depth range and logic op; recorded clears are logged.

Docs: game notes, bring-up and graphics catalogue entries, the README's list of
games being ported. .gitignore: __pycache__/.

Checked on the Mac with the 9-game smoke test (Wonder, Engage, 13 Sentinels,
3D World, Breath of the Wild, Dread, Link's Awakening, Odyssey, Hades): known
red lines only, screenshots match the earlier runs, Dread flat at 1769 MB to
frame 24300. iPhone builds of the new games are untested.
2026-10-18 20:45:16 +08:00
seedds b886c3f0e5 NSP script: one progress line per stage, with a bar and ETA
scripts/build_ipa_from_nsp.py shows [k/n] stage · step. Where the work can be counted, the line has a bar, done/total, percent, elapsed time and ETA: bytes extracted, modules rewritten, shaders (cache check, translation, compile, read from the files dmsh_build.py writes), files compiled by Xcode, and bytes copied and zipped into the IPA. Elsewhere it shows a spinner. In a terminal or Spyder the line is redrawn in place; piped output gets a plain line every BEAT (30) seconds. PROGRESS / --progress auto|bar|lines|off picks the mode. Tool output now goes only to <id>_build/logs/; a failed step prints its log's last 40 lines and the log's path. No chain-hashed file and no build_ipa.sh change.
2026-10-18 16:36:03 +08:00
seedds ac7fadcef7 README: Super Mario Odyssey plays on the iPhone
- Odyssey moves from "being ported" into the Games table (base game +
  update 1.4.1, ~6 GB IPA) and the gallery (Cap Kingdom fog, captured on
  the Mac with the current build). It played at 60 fps on an iPhone; the
  fog fix (ca7216c) is in the latest IPA.
- Touch controls: Odyssey starts in 3D like 3D World and Breath of the Wild.
- Hades is the one game still being ported.
- AGENTS.md: eight games play on the iPhone; development notes drop
  Odyssey's "being ported".
2026-10-18 13:31:54 +08:00
seedds efb72ec46c Super Mario Odyssey: the Cap Kingdom fog parts around Mario again
On the Switch the ground fog opens around Mario as he walks through it. The effect is a small fluid
simulation: 256x256 and 128x128 RG16F velocity fields, a blur and a blended composite. Its only input
is a point-sprite splat per mover, drawn with agl's alPointSpriteShader. The vertex shader writes
gl_PointSize = min(size, c0[0x610]), and the host left that offset of the NVN driver segment at 0.
Every splat had size 0, so the fields stayed exactly zero and the fog never moved (reported on an
iPhone; the Mac did the same).

- nvn_mtl.m: the graphics stages' c0 driver segment gets the largest point size (2047, Maxwell's
  limit) at 0x610. It is not the SetPointSize value: the game sets that to 1, and one-pixel splats
  came out with zero disc weight. The splats now ask for 3-18 px, the fields hold velocities up to
  +/-44, and the fog clears around Mario on the Mac.
- docs: 04-graphics.md gets the offset in the driver-segment table and a write-up, with a note that
  dmsh_build.py's drv_reads scan only sees fragment-shader reads. development.md gets a Fog note under
  Odyssey.

Checked on the Mac: the smoke test of the other seven games shows only their usual red lines.
2026-10-18 13:14:31 +08:00
seedds 5ee72c154b Docs: Odyssey and Hades play on the Mac; Hades game notes
- README.md: Super Mario Odyssey and Hades both play on the Mac. Their iPhone builds are not tested
  yet, and the NSP script cannot pack Hades yet (it doesn't translate Vulkan shaders).
- AGENTS.md: Odyssey plays on the Mac (it no longer just reaches its frame loop).
- docs/development.md: Hades game notes. They cover the Vulkan layer, the SPIR-V shader table from
  vk_shaders.py, the notification-event hook, EOS staying offline, Mac input to Tartarus, and how to
  pack it by hand.
2026-10-18 10:36:32 +08:00
seedds 9b1d22d81f Super Mario Odyssey playable on the Mac: H.264 movies, sticks, HUD; all shaders compiled with preserved invariance
- Movies: H.264 decoded with VideoToolbox (dm/host/hle_movie_avc.c). This SDK's MP4 demuxer passes the parameter sets as the "avcc" record; pictures leave in presentation order through a 4-deep queue (41 of Odyssey's 44 movies have B-frames), and an end of stream drains the queue. The hint videos in Odyssey's tutorial boxes now play (they were black). VP9 unchanged (Engage: 2,816 packets in, 2,816 frames out).
- Controller input: GetNpadStates fills every requested history entry with the current state under consecutive sampling numbers. Odyssey reads all 16 entries whatever count comes back and polls every slot each frame; with only the newest written, neither stick did anything in play while buttons and menus worked (its first hint, "Look Around", waited forever). DM_KEYS takes RLeft / RUp / RRight / RDown.
- Odyssey's HUD counters were solid white cards: the profile lists its 26 layout shaders (main's BNSH at 0x132c000) under alpha_from_red, as for Breath of the Wild.
- -fpreserve-invariance on every Metal compile (dmsh_build.py MAC_METAL, ios_metal.py): Odyssey draws a depth pre-pass with alRenderDepthOnly, then the G-buffer with other vertex shaders and depth test less-or-equal; Metal gave the identical position math slightly different depths and those pixels kept stale content (Cap Kingdom's ground in 32x32 blocks on every fourth frame). Checked by recompiling the frame's 78 vertex shaders with the flag: four consecutive frames identical, no blocks. All eight chain versions change; Mac libraries and c1_full.tsv are being rebuilt and follow.
- DM_NVN_TEXSNAP works again (its entries lacked the layer field the dump loop reads) and names its files <path>_s<pass index>_<serial>_<level>.png.
- Smoke runs of the seven playable games on the Mac after these changes: only their known red lines.
2026-10-18 08:22:56 +08:00
seedds 65deb2f03d Vertex outputs start at the hardware's (0, 0, 0, 1): Link's Awakening's outdoor sunlight fixed; IPA version is the game's display version
- dmsh_build.py vs_init: every float4 vertex output is set to (0, 0, 0, 1) before the translated body, as unwritten components read on the hardware. The translator's MSL started from all zeros; Link's Awakening's surface shaders divide a world position by its w, which the vertex shader never writes, so every sun-shadow compare value was infinite and the whole outdoor scene was lit as if in shadow (dim, no shadows from fences, house or trees). All seven chain versions change.
- Mac libraries and c1_full.tsv rebuilt for Link's Awakening, Engage, 13 Sentinels and Mario Wonder (Breath of the Wild, Super Mario 3D World and Metroid Dread follow).
- DM_NVN_TEXRAW dumps the depth plane of a Depth32Float_Stencil8 texture (shadow maps).
- The IPA's version (CFBundleShortVersionString / CFBundleVersion) is the packed game's display version: the update's when an update is packed, else the base game's (nacp.py --version, DM_APP_VERSION in build_ipa.sh, project.yml; the NSP script prints it).
- README: Link's Awakening screenshot outdoors in Mabe Village.
2026-10-18 01:44:42 +08:00
seedds d035e37c37 Depth mode per game at run time: Link's Awakening's outdoor blur fixed; README screenshots of Engage, 13 Sentinels and Link's Awakening
- NVN's depth mode is runtime state: Mario, Metroid Dread, Super Mario 3D
  World and Breath of the Wild keep near = -w (GL); Link's Awakening,
  13 Sentinels and Engage set near = 0. The translator rewrote z for GL
  in every vertex shader, so near = 0 games had all depths in 0.5..1 and
  depth-to-distance effects were wrong: Link's Awakening's depth of field
  blurred the whole screen outdoors.
- The translator no longer rewrites z; dmsh_build.py (vs_depth) writes two
  entry points into every vertex library, main0 with the GL rewrite (the
  same arithmetic as before) and main0_z without; the host picks one from
  the mode the game sets (mtl_set_depth_zero, shader_load). No per-game
  setting; a red line if the mode changes after vertex shaders loaded.
  Metal function constants were measured at about 2.4 ms of
  specialization per vertex shader, so two entry points instead.
- New translator dll (Ryujinx dlls unchanged), lock and chain versions for
  every profile; Mac libraries and c1_full.tsv rebuilt for Link's
  Awakening, Engage, 13 Sentinels and Mario Wonder (Breath of the Wild,
  Super Mario 3D World and Metroid Dread follow).
- Checked on the Mac: Link's Awakening outdoors (from a save in Mabe
  Village) has a sharp middle band again; Mario Wonder's title renders
  the same as before (differences only in animation).
- README gallery: Engage, 13 Sentinels and Link's Awakening recaptured
  with this build.
2026-10-18 00:02:28 +08:00
seedds f25ca5e670 Link's Awakening: physics crash at the switch to 30 fps fixed; README screenshots recaptured on the Mac
- Link's Awakening 1.1.0 main: the per-world PhysX completion task destroys
  itself in its release (vtable back to the base, mTm = 0). At 30 fps the
  game steps physics twice per frame; a worker descheduled between the run's
  unlock and the release let the main thread rebuild the task first, and
  the late destructor wiped the new one, so the next completion crashed in
  removeReference (main+0x10ccf28). A per-build cut makes that release a
  ret. Unfixed, every Mac run with the CPU loaded crashed; fixed, runs with
  138-257 switches to 30 fps and walks into and out of the house are clean.
- Crash reports print what each register points at ([dm] *xN), so an
  object's vtable names its class.
- RED_ONCE's flag array was one slot short.
- README gallery: Super Mario Bros. Wonder, Super Mario 3D World, Breath of
  the Wild and Metroid Dread recaptured with the current build; no "(Mac)"
  captions.
- docs: the crash in the bring-up catalogue; debugging recipe for racy
  crashes (CPU load, trace gates, a ring printed at the crash).
2026-10-17 23:27:32 +08:00
seedds 7fdf6dccbe README screenshots of every game; NSP script packs unknown games with a provisional profile; Link's Awakening stencil copy and outdoor shaders
- README: a gallery with one picture per game (docs/images): iPhone shots
  with the virtual gamepad for Mario Wonder, 3D World, BotW and Link's
  Awakening; Mac shots (captioned) for Engage, Metroid Dread and 13
  Sentinels. Link's Awakening is now listed as in testing on an iPhone.
- scripts/build_ipa_from_nsp.py: a game with no profile is surveyed, then
  gets a provisional profile guessed from the survey in
  <NSP folder>/<id>_build/profile/<id>/ (outside the repo, reused on the
  next run) and a best-effort IPA; --survey-only keeps the old behaviour.
  game.py searches DM_GAMES_DIR after dm/games; the host Makefile reaches
  such a profile through a symlink; build_ipa.sh finds it through game.py.
  zip_store also stores files whose headers say they are compressed.
- copy_t2t_draw: depth-stencil copies now carry the stencil ([[stencil]]
  export from an X32_Stencil8 view). Link's Awakening's occlusion pass
  tests stencil bit 1 of its quarter-size depth copy, whose stencil was
  the new texture's undefined content.
- Link's Awakening: four outdoor shaders with the foliage shader's input
  loop failed on the iPhone (Half2 / normalized formats); listed in
  shader_facts_manual.tsv. Chain c4995bb3161c.
2026-10-17 21:04:33 +08:00
seedds ab5c8ff1f4 Link's Awakening: game sound, doorway lighting tiles and missing foliage fixed
- Sound: SDK 8 numbers the DSP commands differently and its DeviceSink is
  0x10, not 0x13, so every rendered frame played silence (only the movies
  had sound). capture() now takes the one 0x148-byte command with 1-6
  inputs as the sink when the expected ID is missing.
- Lighting: the light-culling compute shader reads its workgroup count
  from NVN's driver segment c0 (+0x200). dmsh_build's drv_reads scan only
  matches fragment-shader names, so compute and vertex shaders got an
  all-zero c0: every tile row wrote row 0, and the doorway step flickered
  with white 64-pixel tiles. A declared driver-segment binding now counts
  as read, and compute c0 carries the dispatch size (0x200) and the
  uniform buffer table (0x210 + 16 r). BotW and SM3DW render as before.
- Foliage: the shadow / depth pre-pass pipeline 147c829e79fb322f failed
  (uint4 inputs fed 16-bit normalized formats), so with depth test EQUAL
  the palm tree and flowers never drew. New profile file
  shader_facts_manual.tsv (merged by shader_split.py) declares those
  inputs float; Link's Awakening's chain is now ccb18ba743bf.
2026-10-17 15:34:02 +08:00
seedds b89c050e18 BotW: cream cloud boxes behind HUD prompts fixed (layout captures read alpha from red in all 26 layout shaders); work folders move to the NSP folder
The prompt mask is a layout capture (panes tagged CaptureOn / A8): an R8
render target mapped (R, 0, 0, 1) holding the blurred alpha in red. Besides
the black shadow (e6a88e12), each prompt bar draws a cream glow (aa209f57)
whose alpha is min(cloud alpha, capture alpha); with only the shadow shader
listed, the glow filled each key's rectangle with the Kumo64 cloud.
alpha_from_red now lists every pixel-shader variant of the layout library
(main's BNSH at 0x1d83000) and tex_alpha_red only converts render targets.

Game work folders now live in <NSP folder>/<id>-work instead of the home
folder: AGENTS.md and the docs say so, and build_ipa.sh requires W and
writes the IPA next to the work folder by default.
2026-10-17 11:34:58 +08:00
seedds e734ced7d9 Zelda Link's Awakening runs on the Mac (profile 01006BB00C6F0000, 1.1.0, SDK 8); BotW HUD prompt boxes fixed
BotW: the button-prompt shadow samples an R8 blur whose swizzle (R,0,0,1)
made alpha 1, so every prompt got a solid box. New profile key
alpha_from_red takes alpha from red for the named shader.

Link's Awakening:
- NVN lower-left window origin (negative viewport height, winding flip,
  flipped present and raw screenshots)
- texture copies with format change, scaling, mirroring and depth
  (copy_t2t_draw); SetCopyImageStride; nvnWindowGetPresentInterval
- oe::PopNotificationMessage, vi SetLayerCrop / DestroyLayer /
  CloseDisplay / Finalize, audren P_SIZE 0x40 for SDK 8, hid style-set
  update event signalled once on bind
- Vorbis movie audio through stb_vorbis (dm/third_party/stb_vorbis)
- patch_nso jump tables: other index registers, mov copies, masked copies
2026-10-17 08:28:44 +08:00
seedds 1a8a5f5e94 README becomes a player-facing page: tested games, packing in four steps, playing; developer material moves to docs/development.md 2026-10-16 22:13:20 +08:00