41 Commits
Author SHA1 Message Date
seeddsandv0lumehi 583ae42c59 Separate scripted input from controller state (#326)
Give DM_KEYS and DM_CTL their own buttons, press latch and stick axes,
merged with player 1's keyboard/touch and physical controller state.
Controller updates no longer replace scripted holds, and script release
preserves physical input. Include the scripted path in shake detection.

Exercise the real scripted publisher with controller updates during its
hold, including repeated reads after the press latch is consumed. Cover
release, short presses, handheld axes and player isolation, and update the
input documentation.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
2026-10-27 08:36:37 +08:00
seedds 9bdef355ab Port Xenoblade Chronicles 3 2.2.1
Add Monolith archive and shader extraction, a build-gated compact memory layout, vertex storage writes under rasterizer discard, primitive ordering and interleaved geometry feedback. Correct native decoder/audio wait accounting and the oe calls used after movies.

Validated opening combat and movement on Mac with real audio, the full Mac/iOS shader build, GPU regressions, and the unsigned IPA archive. iOS runtime, later areas and DLC gameplay remain untested.
2026-10-27 07:58:05 +08:00
f7345d9a00 Same-core handoffs wait only for the threads their wake made runnable (#247, #259 section 6)
A wake's handoff waited until every runnable higher-priority thread pinned to the caller's core was blocked, not only the ones the wake made runnable. The host runs a lower-priority thread beside such threads at all other times, and a core's busy loaders, workers and pollers are seldom all blocked at once, so the wait had no bound. Tears of the Kingdom 1.4.3 (issue #247) stayed on an empty scene after the boot logos: on 82ab25b, 84 functions / 771 NVN calls a frame, no packaged texture uploaded, 250,008 ms of handoff waits in a 180 s run; a temporary log showed BootupBackgroundWorkThread (priority 58) waiting 200-290 ms at a time for ARes Loading 1, Presentation Thread and BaseProcExecutor. Donkey Kong Country: Tropical Freeze's priority-58 job thread waited the same way for pollers (issue #259, section 6).

A wake now records the woken thread in the waker's current handoff and in those the waker belongs to (note_runnable, at most four), so Metroid Dread's main thread still waits for its movie dispatcher and for the worker the dispatcher wakes. Blocking, a sleep, YieldToAnyThread (SleepThread -2, which on Horizon takes the thread off its core so that lower-priority threads run there), exit, suspension and host GPU work end a thread's hold; a handoff ends when its caller's thread SVC returns (hand_epoch). There is still no timeout, and core_preempt keeps checking every runnable peer.

Checked on the Mac, without shader libraries for chain 8393d85a4eae (draws skipped): TotK on the issue #70 route for 180 s, 1,221 ms of handoff waits and the same per-frame NVN call counts and texture first uploads as 9feac2f (before the unbounded handoff). Dread, fresh saves, DM_KEYS=4:A*20/0.7, 60 s: a control host with the handoff off crashed in 1 of 3 runs at sdk+0x347eb4 from main+0x225338; the limited handoff passed 12 of 12 runs, each through three decoders and two teardowns with about 12,400 handoffs. t_preempt adds three handoff-scope cases: a busy peer nobody woke (main's code never returns), a worker woken by the woken dispatcher (fails without the recorded chain), a woken thread yielding to any thread (never returns without that rule). Host tests, docs tests and an iOS syntax check of hle_thread.c pass. Gameplay with shaders, iOS and DKC itself are not checked here.

Co-authored-by: HougeLangley <1161594+HougeLangley@users.noreply.github.com>
Co-authored-by: aurlight <120410928+aurlight@users.noreply.github.com>
2026-10-26 15:00:32 +08:00
seeddsandv0lumehi fb20bce70e Add GPU diagnostics and close periodic profiles per frame (#225)
Add periodic encoder profiles with attachment traffic and formats, alternating A/B measurement phases, bounded Metal trace capture, and gpuprof.py log summaries. Preserve main's offline pipeline-archive integration while adding the capture build setting.

Close periodic samples at frame boundaries through the last committed shared-event fence, so every:1 reports without mixing frames or reusing an unresolved heap. Add 28 CPU checks of production profiling code and include them in CI; document the mechanism and update the symptom index.

Validated with all 31 CI-style Python test files, the profiling regression, a Mac host build, and all 133 iOS source compiles. Profiling completion and heap ownership were checked with synthetic counter/event objects; no new real-GPU or gameplay validation.

Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
2026-10-26 12:09:07 +08:00
seedds 02e40ba753 Record threading audit and retain verified game workarounds 2026-10-26 08:22:50 +08:00
seedds 9a56ecf0c1 Order same-core wake handoffs and remove Dread movie gates
Use guest runnable state after a higher-priority same-core wake instead of Mach dispatch state or a 5 ms cutoff. Honour wake handoffs for every game and remove Metroid Dread request-ring gates and its core_preempt setting.

Validate event, mutex and condition-variable handoffs with delayed host dispatch; 13 valid Dread movie-transition runs passed. Host tests, docs and shader-chain checks passed, along with an iOS syntax check. Record Wonder boot comparison and pending iOS runtime validation.
2026-10-26 02:27:03 +08:00
seedds 3a993fa3fe Subsystem references and a symptom index: the failure catalogue of docs/agents/03-bringup.md and the per-title material of docs/agents/04-graphics.md (section 1's container list, section 6 and its "Seen in <game>" subsections) move into docs/reference/, one heading per mechanism: rewriter.md, memory.md, threads.md, services.md, movies.md, audio.md, frame-rate.md, nvn-objects.md, rendering.md (with the Vulkan and OpenGL layers), shaders.md and game-state.md, indexed by docs/reference/README.md. Each entry gives the symptom, the cause, the fix (file and function), the games it was found on (linked to their notes in dm/games/<id>/README.md) and any check, caveat or later change; one mechanism met on several games is one entry. docs/agents/symptoms.md lists every entry's symptom (an assertion, a [RED] line, an abort or crash site, a visible result) with a link to it, 350 lines, generated from the Symptom bullets. 03 keeps the loop and reading the log, and its Failure catalogue section now points to the references, the index and where a new finding goes; 04 keeps sections 1-5 and 7 (the container list is shaders.md's Containers by engine), and section 6 says where each kind of finding is. Facts in the old sections about one game only were checked against that game's note, where they already were. Statements the code no longer matches are marked as history beside the current value (movie output slots: MD_NOUT 64, MD_NOUT_AAC 32, MD_NOUT_VIDEO 16; APP_POOL is the profile key app_pool; Engage's layout is 5 / 4.375 GB; inline uniform updates are deferred by default; scaled copies are drawn), and Tears of the Kingdom joins the node-area entry's build list (issue #70). The game notes, docs/agents/README.md, 02 and 05 link to the exact entries, and so do the comments in nvn_mtl.m (nvnTextureCompare) and hle_audio.c (the RemoteIO buffer) and the provisional-profile note in profile_guess.py. 03 goes from 29,600 to 470 words, 04 from 34,400 to 3,500. Checked: every backticked span, module address, issue / PR number and 16-digit hash of AGENTS.md, development.md, dm/games/README.md and docs/agents/01-05 at 6a06877 (4,600 items) still appears in the repository's Markdown (the same five file patterns as before appear as the full names they stood for), every relative link and heading anchor in the repository's Markdown resolves, and the tool tests pass (the four that need capstone or lz4 in the T7 venv) 2026-10-25 12:31:58 +08:00
seedds 3f4d973ac5 Mario vs. Donkey Kong (0100B99019412000, update 1.0.1 over the cartridge base game, Nintendo's sead / agl / gsys engine on SDK 15.3.0): game profile plus three general host changes. Plays on the iPhone (IPA 1.6 GB, user). On the Mac: the opening movie with sound, the title (Press L+R), file select, the play style choice, the first cutscene, level 1-1 (both colour switches, the spring, a time-out and its restart), the pause menu, leaving the level, level select and the save it shows: 60 fps (frame interval p99 about 17 ms), sound, about 1 GB resident in the level and flat while it runs, CPU about 30% of a core, no red line on that route.
Movie decoder (hle_movie.c, every game), both found on the opening movie (Movie/Cutscene01.mp4, H.264 and AAC through movie::Extractor):
- AAC decoders offer at most 32 outputs (MD_NOUT_AAC; Vorbis keeps 64, video 16). The game's movie player keeps the outputs it holds in a ring of 32 entries per track (main+0xe93d4c: 0x200 bytes) and pushes with no bounds check (main+0xe94140), so with 64 the 33rd AAC output wrote its index (0x3f) and time over the next heap block, and the game crashed in sead::ExpHeap's free a second into the movie, at the same address every boot. The console's AAC decoder cannot offer more than 32, or the game would overflow there too; Monster Train 2 peaked at 30 held.
- An AAC decoder's GetOutputBufferSize is one frame (1024 samples per channel, 2048 with SBR), what each output holds. The player sizes its own AudioOut buffers by it, decodes into them and appends each whole, advancing its audio clock by size / 4 samples (main+0xe92a74); with the 128 KB Vorbis bound every 4 KB AAC frame played as 0.68 s, the clock crawled and the movie froze on its first second. Vorbis keeps the bound (its packets vary in length).

Advanced blend modes (hle_nvn.c, nvn_obj.h, nvn_mtl.m, every game): the blend state keeps the mode, the premultiplied-source flag and the overlap (with their getters), and the pipeline blends the Porter-Duff modes (ZERO, SRC, DST, SRC/DST_OVER, _IN, _OUT, _ATOP, XOR, the PLUS modes) with the fixed-function factors they amount to under uncorrelated overlap (adv_blend); the others keep the state's functions with a red line naming the mode. The game's levels set SRC_OVER on one blend state, which the host used to ignore.

DM_PROBE_POLL takes a value filter (=<value>): every change is logged, the threads are snapshotted and dumped only at changes to that value. That is how the ring overflow was caught: DM_PROBE_WATCH on the heap link made the movie's file reads into the same page fail.

Profile: the provisional profile's guesses held (no system resource, a 3.14 GB heap in the 4 GB layout, modules main subsdk0 sdk), plus render_cond on (the game skips clears under render conditions; without it every level was black behind its HUD). 12,691 shaders (BNSH / BAHS in the zstd RomFS and main), all translated. Pad only (main imports no touch screen), the default 2D layout. A prewarm list recorded on the Mac from a fresh save over the route above (550 lines; with it nothing was built on demand). Checked: the NSP script packs it from the NSPs with the profile in dm/games; the graphics run list and the 49 tool tests pass; after rebasing on 6d34984 (spin waits) the game boots into level 1-1 as before. README (gallery, a Mac picture of level 1-1; 2D layout), AGENTS.md (57 -> 58) and docs/development.md, docs/agents 03 / 04 / 05 say so
2026-10-25 01:19:47 +08:00
seedds 6d34984488 Spin waits that take a spin lock (general, from PR #134, found on Breath of the Wild; fixes #129). In steady play on the Mac, Breath of the Wild had frames with 30-210 ms of CPU work every 1-10 s: the main thread and the two workers spinning in one job queue poll (main+0xef62dc). The first item of some of its jobs is for the thread of core 0; the others wait until it is taken, taking and releasing the queue's lock (an exclusive test-and-set, then a compare-and-clear) and writing a status word on every pass, so the spin wait search (a store or an exclusive store ends a path) did not take it. Polled at full speed by the two workers, the lock went back and forth between their cores, and the main thread, whose item it was, lost nearly every exclusive store: in a 40 s Time Profiler trace it never held the lock while each worker held it in about 750 samples. patch_nso.py "Spin waits": a lock taken and released on the same address on every path back to the head (spin_lock 'acq' / 'rel', counted like the exclusive adds), a test loop on a word inside the region ('tst', whose branch back goes to the original loop and its own gate), and up to four stores whose value and address registers the region never writes ('stc', only in a region that loads something). Such stores write fixed values to fixed addresses, so a loop can change its own state for a few hundred passes at most, well inside the gate's 2 ms of polling, before its passes repeat. A store of a value a pass changes ends its path and the search runs again without it (the path that takes an item stores the new cursor); when the region with stores does not cover the old one's back-edges, the old one is used. Through the gate the workers leave the lock a moment between passes and the main thread takes its item at once. Zora's Domain on an M2 Max at 120 Hz (one host, 42 s of steady play per run, three runs each, before -> after): standing still, frames with more than 30 ms of work 2.8 / 6.3 / 3.5 -> 0 per 30 s, the frame's work p99 20-21 -> 8.3-8.4 ms; turning the camera, CPU hitches 11.9 / 13.3 / 6.3 -> 0, p99 21-25 -> 9-15 ms; process CPU per frame unchanged. Every module on the game drives rewritten with the old and the new search (206: 108 NSOs and Super Smash Bros. Ultimate's 98 NROs): 175 byte for byte as before (Hades, Super Smash Bros. Ultimate, Super Mario Odyssey among them), 7 fail alike under both (every rtld, New Super Mario Bros. U Deluxe), and 24 take new or wider loops, all but Breath of the Wild's queue through the store rule alone: Monster Hunter Rise's main 90 -> 166 (72 of them one inlined countdown whose reset makes the next pass leave), BALL x PIT 87 -> 94, Animal Crossing: New Horizons 162 -> 166, Balatro 9 -> 12, Octopath Traveler 0 75 -> 78, Pikmin 4 80 -> 83, Bayonetta 3 35 -> 37, Breath of the Wild 308 -> 310, one more each in Big Brain Academy, Mario Kart 8 Deluxe, Metroid Dread, Pokemon Legends: Z-A, Stardew Valley, Super Mario Bros. Wonder (both builds) and Tears of the Kingdom; the movie library's thread loop is wider in the subsdk0 of Breath of the Wild, Mario Kart 8 Deluxe, Animal Crossing, BALL x PIT, Monster Train 2 and Octopath Traveler 0, and one loop is wider in the main of AI: THE SOMNIUM FILES and Advance Wars 1+2. No loop of the old search is lost or loses a back-edge. Each distinct shape was read in its disassembly: one-pass resets and state setters (the gate is reached only when another thread changes the word), parser states that leave on the next pass, and real flag waits. Rewriting all 206 takes about 4,100 s of CPU instead of 3,250. Diagnostics: DM_HITCH_DUMP=<ms>[:<s>] logs every guest thread (its wait, last SVC, where it is, x0-x2 and x19-x25 in guest code) when no NVN frame was presented for that long, and each guest thread names its pthread '<handle> <name>' (0x1008 Worker) before it enters guest code, the main thread '0x1001 main thread', so sample and Instruments' Time Profiler show which guest thread a sample belongs to. The graphics tests that link hle_nvn.c without the host define dm_present_ns (t_host_stubs.c weak, t_nvn.c). New test dm/tools/tests/test_spin_waits.py. Checked: the host builds, the host unit tests, the 46 tool tests and the gfx run list pass; Breath of the Wild into Zora's Domain and Mario Kart 8 Deluxe for 85 s from boot with the new rewrite (the PR's author), and Monster Hunter Rise from a fresh save for 180 s headless with each rewrite on one host (e72a065's, the chain of its Mac libraries): 60 fps, frame p95 about 17.5 ms, the same red lines, no hang. Not checked: Paper Mario: The Thousand-Year Door, DARK SOULS: REMASTERED, FINAL FANTASY TACTICS and FINAL FANTASY RESONANCE (no files on the drives), the iPhone. docs/development.md (Spin loops, Breath of the Wild) and docs/agents/05-debugging.md say so 2026-10-25 00:48:24 +08:00
d979c346b9 Integrate demand-first PSOs, far-base tracing and NRO loading
Integrate PRs #75, #90 and #91 with focused correctness fixes:
- Prioritize demanded render pipelines and bound speculative compilation.
- Follow copied jump-table bases and reject W/X register clobbers.
- Collect and rewrite registered NROs, load them on the Mac, and preserve
  runtime activation order through unload/reload and symbol rebinding.
- Preserve refused-only manifests and invalidate collection caches when
  archive readers or symbol parsing change.

Add pipeline scheduling, register tracing, NRO packaging and loader
lifecycle regression coverage. Document that executable NRO embedding
on iOS remains outside this integration.

Validation: 41 Python tests; 48 pipeline scheduling, 102 NVN and 95
geometry checks; three pipeline-key serialization cases; full host test
suite; Mac host build; iOS loader syntax checks; git diff --check.
Gameplay and controlled performance A/B measurements were not run.

Co-authored-by: blindlight86 <25404088+blindlight86@users.noreply.github.com>
Co-authored-by: v0lumehi <aljic.zijad@gmail.com>
2026-10-24 11:38:12 +08:00
seeddsandv0lumehi 76545a58a7 Mac: each controller is a player of its own, for local co-op (GitHub issue #74), from PR #84
Every GameController fed the same input path (front_mac.m), so the last one moved won, and hle_hid.c reported
exactly one npad whatever the game supports. Now the first controller is player 1, with the keyboard, the next
player 2 and so on up to 8 (pad_slots.h; a removed controller frees its player and gets it back when a
controller of the same name reconnects). Player 1 stays on No1 docked and Handheld in handheld mode, player n is
on No n (No1 stays empty in handheld mode: games take Handheld and No1 both for player 1). The game's supported
npad ids decide how many play: a one-player game ({No1, Handheld}) gets every controller as player 1, a
controller past a game's last player is not used, as on the Switch. A connect or disconnect signals that npad's
style-set event through hle_signal_bound_system_event (hle_thread.c), which signals only an event still bound,
from the macOS main thread where the notifications arrive; player LEDs follow the Switch's patterns; the
controller-support applet reports the connected players, capped at the game's maximum; only player 1 shakes.
DM_PAD_MERGE=1 merges every controller into player 1 as before; DM_KBD_PLAYER=own makes the keyboard player 1 on
its own. One controller, or the keyboard alone, behaves as before. iOS is unchanged: DmGamepad.m still feeds
player 1 and never reports further players.

Rebased onto a678703 (front_mac.m's includes, the Input row of docs/development.md). Checked: host unit tests
pass with the new t_hid (44 checks: slots and reconnects, npads docked and handheld, one- and four-player games,
events, LEDs, the applet); the Mac host builds for Breath of the Wild and Mario Kart 8 Deluxe with no new warning;
hle_hid.c and hle_thread.c pass an iOS SDK syntax check; Hades in a window with the keyboard and DM_KEYS reads
the supported ids (0x10f), and its scripted presses take it past the title. Two controllers were not run here;
the PR's author played Mario Kart 8 Deluxe in 2-player split screen with an Xbox Wireless Controller and a
DUALSHOCK 4, each its own kart, and a controller disconnected and reconnected during play got its slot back.

Co-authored-by: v0lumehi <34629096+v0lumehi@users.noreply.github.com>
2026-10-24 01:40:58 +08:00
seedds f4f0a85d6e patch_nso.py split into the general rewriter and per-build patch files (GitHub issue #63): every game's bring-up edited the one file, so PRs for unrelated games conflicted. dm/tools/patch_nso.py (1,711 → about 1,100 lines) keeps the general passes and the driver; the instruction encoders move unchanged to dm/tools/nso_asm.py; a game's own patches (CUTS, OBS and the frame-step stubs, now FRAME_STEP = {build ID: stub(ctx)}) move verbatim with their comments to dm/games/<id>/patches.py (16 profiles: Tears of the Kingdom, Breath of the Wild, Age of Calamity and Paper Mario: The Thousand-Year Door with their stubs), and the cuts of Nintendo's sdk module builds, 6 of the 15 shipped by 2 to 4 games, to dm/tools/sdk_patches.py. The rewriter loads every patch file, whatever the game, and applies those of a module's build ID at the points it did before (cuts before the scan, gates after the rewrite, stubs after the gates); a build ID claimed twice stops it, and its command line needs no DM_GAME. build_src.sha256 lists the rewriter's files and every patch file (the same for every module). NSP script: the rewrite step's reuse key counts nso_asm.py and only the patch files that name one of the game's builds (patch_files), profile_files leaves patches.py out (only the rewrite step reads it), and a derived profile gets none. dm/tools/tests/test_patches.py: no build claimed twice, the entries' forms, a game's file names only its own profile's non-sdk builds, sdk_patches.py only sdk builds some profile ships, and the reuse key's patch files. Checked byte-identical: every extracted module on the game drive (131 builds) rewritten with the previous and the new rewriter gives the same outputs but build_src.sha256, 28 of the 30 patched builds among them (all four frame-step mains), and 7 fail alike (rtld, and New Super Mario Bros. U Deluxe's, which no profile rewrites); the other two (Super Mario Bros. Wonder's mains and Absolum's, no game files here) by their CUTS / OBS data, equal to the previous tables. AGENTS.md, docs, dm/games/README.md, the profiles' comments and the host comments that named a game's cut in patch_nso.py now name its patches.py or sdk_patches.py 2026-10-23 23:36:21 +08:00
seedds 7deea8eedc Tears of the Kingdom: the SDK's address-region node area cut from ~1 GB to 62 MB on the sdk build it shares with Breath of the Wild 1.9.0 (patch_nso.py). The game's resource heaps are address regions in the 2.375 GB alias region; at the first descent from the Great Sky Island it was used up to +2.25 GB with 1.00 GB mapped, and the game took a memory shortage (MSF / R / Rf) and played the memory-shortage Blood Moon, which put Link back at the start (GitHub issue #70; 7 of 8 Mac runs from the issue's save). With the cut: +1.75 GB, no shortage in 3.7 minutes on the island. Probe tools: the write watch reports exclusive and atomic stores, and DM_PROBE_POLL snapshots every thread when a word changes 2026-10-23 14:13:52 +08:00
seedds e72a06510e DRAGON QUEST VII Reimagined plays on the Mac: profile 0100A9D01C446000 (update 1.2.0, SDK 19.3.1)
- Files and shaders: Unreal Engine 4.27 with IoStore containers beside one pak (version 11, its footer magic 0xcc bytes from the end, uepak.py magic_back); the 62,901 shaders of the pak's two shader code archives are LZ4 blocks, which uepak.py decodes itself (lz4_shaders), so the NSP script runs no Oodle host when no record is Oodle. One global shader writes integers into an R8Uint target (shader_facts_manual.tsv)
- Shader chain, for every game: a texture query on an image handle (c2 0x120 + 8 r: Unreal's GetDimensions on the RWTexture3D a compute shader writes) had no type in a facts table built without PC shaders, and the 2D default took a 3D image's depth from its width; the translator now translates such a shader again with the image types of its first translation (dmsh_xlate ImageTypesOf; dmsh_xlate.dll rebuilt, dmsh_chain.lock updated). Host, for every game: a texture binding of slot 32-39 reads the image bound in the image slot that shares its c2 word (nvn_mtl.m tex_slot_h; Hades II's texture slot 34 without an image still reads the texture). Unreal's volumetric lightmap indirection patch (7b958a7b21e72801) read the empty texture slot 32's 1x1 size and wrote only cell (0, 0, 0), so in the opening cutscene every character and prop sampled the atlas origin, a daytime level's bricks baked 2^15 brighter (white figures about 10^4 too bright). Monster Hunter Rise has four such queries. Every other profile's dmsh_chain.h rewritten with its c1_full / cb_extent lists carried over (the change touches no c1 or uniform read); DRAGON QUEST VII Reimagined rebuilt for Mac and iOS
- Host, for every game: stencil-mode texture builders (nvnTextureBuilderSetDepthStencilMode STENCIL) read the stencil of their depth-stencil storage (the hero was drawn as a dithered silhouette); the audio renderer at 32 kHz on the CPU path (the SDK makes its upsampler only for DSP rendering: UpsamplerManager::Allocate returns null and the host resamples 3:2); GetAudioDeviceOutputVolume; the shop and Nintendo Account calls of the title (ec Finalize / Cancel / GetErrorCode / ShowShopSubscriptionDetails, GetAuthorizationCode / GetState); kpr::GetKeyCodeMap; the movie output format's nv12-width / nv12-height beside nv12-uv-stride (Sofdec's VP9 movies were grey with bands)
- Memory: MallocBinned3 reserves 37 GB of pools in the engine region; its creation is cut and an observation gate hands the engine the host's malloc zone (patch_nso.py cut + gate OBS_UE_MALLOC, hle_uemalloc.c layout UE4_27; patch_nso.py places a gate where the scratch registers it uses are dead, scratch_dead); the region is 2 GB (game_args) with the SDK's node-area cut on this sdk build, in a 3.75 GB guest block; about 2.6 GB resident on the Mac
- Frame rate: this build's t.MaxFPS and rhi.SyncInterval in hle_fpslock.c; the profile keeps the game's 30 fps
- Diagnostics: DM_CTL (Mac) drives a running game line by line: presses, shots, pass and texture dumps, texlist (textures of a target with storage and block-linear layout), memdump, peek, Unreal console variables (cvarf / cvari), env; the pass dump prints buffer-to-texture copies and buffer textures' format, size and storage, and DM_NVN_CBDUMP saves the source bytes of 3D uploads (docs/agents 04, 05)
- Mac: the logos and opening movie, the title, a new adventure log, the opening cutscene (the well, the night scene with Maribel by the boat) into play at Pilchard Bay, and a saved log continued, at 30 fps with saves; no red line at run time apart from 62 of the 63 geometry shaders (one-pass cube shadow maps), which do not build. Unit tests: the graphics tests that build pass (t_refl's two sample libraries are not on this machine); 13 that compile nvn_mtl.m with hle_nvn.c already failed to build on main (g_ft defined in both, hle_nvn.c symbols missing from their stubs)
2026-10-22 23:13:12 +08:00
seedds 99c3f91481 Spin waits: the rewriter finds guest poll loops in every module and the host lets them sleep (Paper Mario: The Thousand-Year Door's job workers at 24 fps on the iPhone)
A loop that polls memory with no SVC costs nothing on the Switch, where a higher-priority thread of its core preempts it; here it takes a host core. TTYD's three
gfx::ParallelManager workers (main+0xa8e680) spun at 94% of a core each in Rogueport on an iPhone. patch_nso.py now copies every loop that provably cannot leave by
itself (only loads, register and flag arithmetic, direct branches and barriers on every path back to its head, nothing live at the head written; an exclusive add undone
on every path allowed) into the patch area; the copy's back-edges pass a spin gate, where the host polls on for 2 ms and then sleeps with the yield back-off
(dm_spin_wait; DM_SPIN, DM_SPIN_POLL). Mac Rogueport route beside the committed build: frame p95 29-37 -> 18-22 ms, CPU 93-129% -> 38-57%. The x18 scan decodes in
blocks without details, so the rewrite is faster than before (TTYD main 20.5 -> 11.7 s, Tears of the Kingdom 101.5 -> 64.2 s); its sites are unchanged.
2026-10-22 16:32:01 +08:00
seedds 64cdee954c Big Brain Academy: Brain vs. Brain plays on the Mac: profile 0100620012D6E000 (update 1.2.0, SDK 11.4.3)
- NintendoWare Bezel Engine 1.56: nearly all of RomFS is in Bezel Engine archives (.bea, magic SCNE, every entry a zstd frame). shader_split.py reads them (bea_members): 2,125 shaders, most of them the engine's own in System.nx.bea. The 12 geometry shaders of Bezel's line renderer do not build (the main menu chart's outlines are missing); ten pixel shaders that choose their texture handle between two of the game's uniform buffers at run time do not load. The survey names Bezel Engine archives (profile_guess.py, the NSP script's notes)
- nninitStartup picks virtual address memory (a 60 GB StandardAllocator region) or a heap by IsVirtualAddressMemoryEnabled, which the host answered yes for every game. The new profile key sys_resource reports the NPDM's system resource (0 here); the survey writes it for new games, profiles without it keep the 32 MB answer
- SDK 11 calls that reach a service: GetHardwareOpusDecoderWorkBufferSize(int, int), the IR camera (every camera unconnected), LoadCachedNintendoAccountInfo, Enable / DisableIndividualFileDataCache, ldn::GetNetworkConfig and the socket overloads with plain sockaddr
- Movie decoder: the activity previews loop with Flush and Start from four threads. An input index the game held across a flush went back to the free list and was handed out twice; the fifth pending packet overwrote the decoder's mutex and the process died in 2 runs of 5. A send now claims such an index, held indices are withheld for 250 ms after a flush, pend[] cannot overflow, a flush keeps each slot's last picture and an output with no picture leaves the client texture as it was (the black flash at every loop). DM_MOVIE_TRACE=1 logs every decoder call
- The crash handler takes SIGABRT; a command buffer with no GPU start time no longer counts the whole clock as GPU time
- Handheld with Boost (op_mode, perf_mode): handheld, the game reads the touch screen; Boost gives 1920x1080
- 60 fps, sound, previews and saves; the title, enrollment, the menus and activities of every category. About 1.5 GB resident at the first activity, 1.86 GB after 15 minutes of activities (the game's pools as each activity loads). Prewarm list recorded through enrollment and a full activity. Not yet tested on an iPhone
2026-10-22 00:54:39 +08:00
seedds 37c24c6841 STEINS;GATE RE:BOOT plays on the Mac and the iPhone: profile 01008BC024FDE000 (update 1.0.3, SDK 20.5.17)
- M2's ADV engine (m2lib, E-mote characters, PSB archives); 87 H.264 / AAC movies through the SDK's movie library in subsdk0; 487 shaders in five BNSH in main; 60 fps, 1280x720 in both modes by the game's own choice
- nn::audio::SetAudioOutVolume / GetAudioOutVolume: a gain on the samples the host plays; the SDK's versions read the audout session the host never made (null read at the first movie)
- Storage-sharing texture table: a new texture that takes over the storage of an entry under another description evicts it, unless a live texture object still resolves to it or a lookup found it in the last 16 submissions. The game decodes every picture into one reused region of a pool that lives for good: 1.58 GB of GPU memory 24,000 frames into a new game before, about 1.0-1.15 GB over 15 minutes of story after. Captain Toad's opening course unchanged (no takeover). The memory line counts unreferenced entries and takeovers
- DM_PROBE_WATCH=<address>[+<length>]: who writes a guest word, with the page kept protected and every store to it carried out by the fault handler through a writable alias; found the game's render size setter
- Probe backtraces read each frame with vm_read_overwrite (code without frame pointers keeps data in x29)
- Prewarm list recorded from boot through the prologue into the story
2026-10-21 23:18:16 +08:00
seedds e4a88c614a Captain Toad: Treasure Tracker plays on the Mac from its opening course into Episode 1: profile 01009BF0072D4000 (update 1.4.2, SDK 8.2.0)
- Boots with no host change: EPD Tokyo's al / sead / agl engine (as 3D World and Odyssey) on the sdk build Link's Awakening 1.1.0 runs; 60 fps, no red line, about 950 MB resident in a course
- Shaders: 7,258 (bfsha, agl sharcb, effects, layouts, main); the fin fur geometry shader does not build, as in 3D World
- 16x16 magenta blocks on the Mac too: agl's post-effect program samples its own attachment (feedback_snap 54f58a7cab608766, the same program as 3D World's)
- Handheld (op_mode): docked, the game's pointer is the Joy-Con gyro; handheld, the touch screen. 3D pad layout for the camera
- Prewarm list recorded through the opening, the title and Plucky Pass Beginnings
- shader_split.py asks for captured GLSLC outputs only when a module of the profile's defines glslcCompile (this game imports it, but nothing defines it); tables unchanged
- DM_PROBE @*xN+off+add: dump at the dereferenced pointer plus add (@*x0+48+8 = an al::LiveActor's position); the opening course's route was found with it and the stage map's guide rail
2026-10-21 18:50:25 +08:00
seedds 89145e84c6 Six-axis sensor: every connected controller reports one, at rest or shaken; shaking the iPhone shakes it (Space / DM_KEYS Shake on the Mac), and Super Mario Odyssey's swing detection sees it
- hle_hid.c: GetSixAxisSensorStates / State fill every requested entry: a controller lying flat (gravity (0, 0, -1) G, identity direction), samples 5 ms apart on the guest clock and numbered by time; handles per style (two for handheld / dual Joy-Cons, one otherwise) with style, npad id and device in the handle; IsSixAxisSensorAtRest follows the shake
- DM_BTN_SHAKE (dm_input.h, bit 63, never reaches the game's buttons): a side-to-side shake in whole 6 Hz cycles (±3 G, with a matching yaw); a tap is one whole shake, holding keeps shaking
- iPhone (DmGamepad.m): the accelerometer at 60 Hz, a flick above DM_SHAKE_G (1.5 G by default) shakes the controller until 120 ms after the last hit; each detected shake is logged with its peak
- Odyssey 1.4.1 reads all 16 entries per sensor whatever count comes back; on the Mac its motion analyzer (main+0x5d5590) sets the left, right and any-swing flags during a Shake (swing value up to 1.5, threshold 0.2) and none at rest
2026-10-21 18:47:24 +08:00
seedds 7c490ede93 Cuphead's broken sprites and iPhone flicker: the ZCull storage size is never 0
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
2026-10-21 14:08:50 +08:00
seedds 830ecde2a7 Breath of the Wild's world-load crash: our command records were larger than the driver's; plain draws are compact and recordings have no BEGIN
About 1 run in 3 crashed about 3 s after Start (operator new at main+0x155a79c
returned null, written through at main+0x155a7a4). Each model file gets a heap
of its resource size table entry + 0x1008; the file goes in 4 KB aligned and
the model's setup (main+0x155a2ac) takes the rest as a frame heap. Part of the
setup is GPU memory for the model's prerecorded commands, which the game sizes
by recording each mesh's draw and each shape's state for each render pass into
a scratch command buffer and summing nvnCommandBufferGetCommandMemoryUsed. So
our record sizes count against the table: draw records had grown to carry the
indirect and multi-draw fields (0x38 -> 0x50 per recorded draw on 10-18 and
10-20), and every recording began with an 8-byte BEGIN record. Of the 497
models of the world load, Obj_TreeConiferous, Obj_TreeBroadleaf_B and
FldObj_HyliaBridge_A-02 needed one 4 KB page more than their entries allow,
which fit only when the file's alignment pad was small (about 15% of loads each
failed); the others had about 0xd00 to spare for any pad.

- Plain draws leave out the indirect / multi-draw fields (hle_nvn.c rec_draw);
  the replay reads draw records through draw_rec / draw_idx_rec (nvn_obj.h).
- A recording starts with its first command, as in NVN: no BEGIN record, the
  handle is the first record, an empty recording takes only its END.
- 0x30 per recorded draw now; all 510 models of the world load fit whatever
  the pad (probe check in docs/agents/03-bringup.md). World-load A/B on the Mac:
  main 2 crashes of 8 runs, this 0 of 8. Super Mario Bros. Wonder (opening
  course), Age of Calamity (first battle), Monster Hunter Rise and Three Houses
  boots behave as on main. t_nvn 74/74 (6 new checks on recording sizes).
- DM_PROBE heap walk: layouts of other sead versions (DM_PROBE_HEAP), frame
  heaps (DM_PROBE_HEAPFRAME), a heap-name filter (DM_PROBE_HEAPNAME), block
  contents (DM_PROBE_HEAPWORDS), the raw heap object (DM_PROBE_HEAPRAW); each
  hit prints as one block.

Mario's +16 KB resource-heap cut (a model 0x70 bytes short) may have the same
cause; not checked, so the cut stays.
2026-10-21 13:04: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 42eebb19d7 Logs: dropped frames judged by each frame's own hold; the D2R render-scale line only on request
With the frame step at 2 vsyncs every frame stays on screen 33.3 ms, and the on-screen summary counted all of
them as dropped against the 60 fps period ("300 frames, dropped 300" in an iPhone BotW log). Each present's hold now
goes into a ring by present number, and the interval read 8 presents later is judged against its own presents'
holds: dropped = on screen longer than 1.5 x max(target period, hold x 16.7 ms). The [slowframe] diagnostic uses
the same limit. Checked with a scratch test built like t_nvn: step 2 at 33.3 ms 0 dropped, step changes 1 -> 3 -> 2
0, five doubled frames at step 1 5, discarded presents still counted.

The D2R 3D render-scale line ("main build ... has no known addresses") printed for every other game, where it read
like a failure (BotW and TOTK scale through DM_RENDER_SCALE); it now prints only when DM_3D_SCALE is set.
2026-10-20 13:49:33 +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 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 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 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 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 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 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
seedds abcfdf9215 The Legend of Zelda: Breath of the Wild (1.9.0) reaches the Shrine of Resurrection on the Mac, with a 60 fps cut; deferred texture binds read at execution
- Profile 01007EF00011E000 (SDK 20.5.22, NVN 55, main + subsdk0 + sdk): 19,592 shaders, all translated (chain bd3979ea9b3d).
- Loader applies DT_RELR relative relocations (SDK 20; main has 223,994); nvn_slots_got.py and DM_LOADHASH read them too.
- 60 fps: five rewriter cuts lower the GameFrameworkNx dynamic-interval floor from 2 to 1; ksys::VFR scales the
  logic time step by the interval (ratio 0.5 at 60 fps, falls back to 30 with ratio 1 when frames run long).
- Black 3D scene: nvnCommandBufferBindTexturesDeferred reads the handles at execution (bh_fresh); the engine
  records once and fills the handles later, so the lighting composite sampled texture 0.
- A texture attached at two colour slots is attached once (TBDR tile stores raced, albedo kept scattered tiles).
- Cube textures are 6-layer 2D arrays so tex_fit can bind cube-array views (1,512 shaders sample cube arrays).
- NVN functions other than command recording run on the per-thread host stack (Metal compiled on a small guest stack).
- Address-to-pool lookups use the last hit plus a sorted index (1,760 pools): submit time 21 ms -> 7 ms per frame.
- SDK 20: ARUID hook, audio renderer layout (Span GenerateCommand, new offsets, DeviceSink ID 0x14), nfp events,
  VR mode, seven-six-axis sensor, steady clock, launch parameter, stereo vision permission.
- Compressed render targets (BC4 via an RGBA16UI view) are skipped with a red line instead of a Metal abort.
- DM_KEYS takes frame numbers (f2500:A); TEXDUMP saves 2D-array layers; PASSDUMP prints skipped passes.
- NSP script skips empty NSP files and keeps one copy per DLC.
2026-10-16 16:07:15 +08:00
seedds 68cfe6c47e Super Mario 3D World + Bowser's Fury (1.2.2) reaches World 1-1 and Lake Lapcat on the Mac; textures sampled with another type get compatible views
- Profile 010028600EBDA000 (SDK 10.4.0, NVN API 53): 11,080 shaders from Yaz0 SARCs (BNSH, BAHS v9, effects, main).
- Shader key hashes min(0x880, control +0x20) bytes (0x850 / 0x848 controls); other games' tables unchanged.
- Profile keys device_api (NVN API version the game checks) and shared_font (ttf / bfttf at the shared-font address).
- File system hooks run on a per-thread host stack (HLE_FORCE_BIG, hle_bigstack.c): the save thread has 8 KB.
- Washed-out scenes: a one-layer 2D array sampled as texture2d read one value on Metal; tex_fit binds 2D / array / cube views.
- Unwritten colour outputs get a zero write mask; NVN 1D textures are one-row 2D.
- Hooks: InlineKeyboard, settings LanguageCode, ListAllUsers, oe perf / album, nifm SDK 10, prepo session, vi indirect image size;
  nvnQueueAcquireTexture, SamplerCompare, debug groups, TextureGetSwizzle / GetStorageClass.
- NSP script builds nx_extract in a temp folder (an apostrophe in the path broke MSBuild silently); DM_KEYS left stick.
2026-10-16 13:04:49 +08:00
seedds 753dee6400 Metroid Dread (2.1.0) reaches gameplay on the Mac at 60 fps, cutscenes at 30
New profile 010093801237C000 (MercurySteam engine, SDK 11.4.3). A new game
runs through the title, the story recap and the opening cutscene into ZDR,
with movies and music. An unsigned IPA builds from the NSP folder; it has
not been tested on a device yet.

Rewriter and loader
- main has a 1.8 GB bss (the engine's static heap): when an appended patch
  area would start beyond 112 MB, it goes 32 MB before the module base
  (negative PATCH_OFF, IMAGE_SIZE / FILE_IMAGE_LEN). Mac loader, iOS
  embedding (no bss in the executable) and QueryMemory handle both layouts;
  Mario's and Engage's output is byte-identical.
- Observation gates may sit right after a bl as well as at function entries.
- Used memory counts that bss, so SetHeapSize asks for 1.38 GB, not the pool.

Host
- Hooks: oe::FinishStartupLogo, fs::CommitSaveData / DeleteDirectory (SDK 11
  names), hid::detail::SendVibrationValues (vibration graph flush).
- Guest paths resolve "." and ".." (Atk stream paths use ../../).
- Movies: the SDK's own demuxer runs natively; the decoder supports the
  native-texture mode (RGBA into the client's NVNtexture, sync signalled),
  the D1 destructor, copied client events, MediaCodec-like Flush / Stop, and
  an asynchronous input signal after Start.
- Same-core preemption approximation (profile key core_preempt, Dread only)
  plus gates that wait for the movie player's request ring: the game frees
  objects its higher-priority threads still use, relying on them preempting
  the main thread. 12 of 12 runs clean, before about half crashed.
- SleepThread is precise (macOS timer slack made 33 ms sleeps 36.4 ms): the
  game's 30 Hz sound thread drives cutscene frames, now 33.3 ms. Mario's
  frame-time p95 19.7 -> 18.4 ms.
- DM_KEYS takes <key>*<count>/<interval>.

Graphics
- BindProgram binds per stage: Dread's depth prepass unbinds only the
  fragment stage, which used to drop every prepass draw and leave scenes
  black. Composite programs give a vertex-only pipeline.
- WriteTexels writes regions in place (the map writes one texel per call);
  t_nvn checks 15 regions byte for byte.
- Pass dumps print Z lines for dropped draws.
- shader_split.py reads MSHD containers (1,010 shaders, 659 pairs).

Regression: Mario reaches frame 6900 with only the known red line; Engage
plays its opening movie and battle cutscene.
2026-10-15 21:30:44 +08:00
seedds 6c0a14dcb8 Host: saves survive a full disk; free thread blocks; iOS memory headroom
Ported from d2r-mobile (b94670c79, 2eb575877, 704147218), adapted:
- Save transaction: if the work directory cannot be set up at mount
  (leftovers not removable, clone fails, e.g. a full disk) the host
  now mounts the committed saves read-only and tells the front end
  instead of exiting; a reclone after a refused commit clones beside
  the work directory and swaps it in atomically, and on failure keeps
  it tainted for the next try; a failed rename rollback refuses the
  transaction instead of exiting. Creating an existing file or folder
  on a read-only mount answers "already exists", and file / directory
  calls ignore the null handle a failed open leaves. Test switch
  DM_TEST_FS_FAILAT; tests/t_fs.c section 6 (68 of 68 pass). An Engage
  run with the mount clone failing loads its autosave read-only and
  plays on.
- iOS front end: front_save_failed shows a bilingual notice (it only
  logged before).
- thread.c: each guest thread's TLS block, thread-local region and
  thread object (9.5 KB) are freed at thread exit through a key of
  their own (the guest overwrites the TLS / TLR key slots).
  tests/t_thread_leak.c: 600 blocks leaked before, none now.
- Memory line on iOS: os_proc_available_memory, how far the app is
  from being killed for memory.
- The host test Makefile gets the game profile include path; t_fs
  stubs the module table. Docs: 03-bringup memory line fields,
  05-debugging save failures. Mario regression t78 and the iOS compile
  are clean.
2026-10-15 08:12:10 +08:00
seedds 73b1722248 Docs: iPhone fixes, update 1.2.1, prewarm lists; drop host README
- README: status for v0 / 1.2.1 on the iPhone, update 1.2.1 build
  steps, diagnostic env files, finishing the IPA by hand when Xcode
  uses a custom build location, app data folders after a reinstall,
  prewarm lists (pso_user.tsv / pso_manifest.tsv).
- AGENTS.md: per-build knowledge keyed by build ID, shader_pairs.tsv on
  the chain, unsigned IPAs only.
- docs/agents: update extraction and offset porting (01), the
  command-buffer byte, command memory, iOS audio, update imports and
  build-gated D2R code (03), vertex output padding and iOS math mode
  (04), on-device diagnostics and the offline link check (05).
- dm/gfx README / nvn_metal_mapping (en + zh-CN): pairs table, several
  game versions, prewarm lists, DM_PSO_USER, command buffer notes;
  corrected shader key, build script and 8-bit index lines.
- dm/host/README(.zh-CN).md removed: an index of d2r-mobile's D2R
  features; most files it listed no longer exist.
2026-10-14 01:21:04 +08:00
seedds 874001eaee Sound, button icons, fountain water: CPU audio renderer, extension font, ordered copies
- Audio: run the sdk's own audio renderer on the CPU in manual mode (hle_audren.c),
  feed its DeviceSink output to the AudioOut sink; catch-up render on back-to-back
  updates fixes the ApplyLightLimiterEffect crash at the title screen
- Button icons: make_ext_font.py generates a stand-in Nintendo extension font
  (A/B/X/Y, L/R/ZL/ZR/SL/SR, +/-, D-pad, stick arrows); served as shared font type 5;
  the iOS bundle now carries both shared fonts
- Fountain panels: Metal 4 does not order copies inside one encoder; add barriers
  when ranges overlap, so the GPU wave simulation no longer reads a stale uniform
- Zero-fill new colour textures; scissored zero clears; uniform-update snapshots
- Docs: graphics ordering, audio, diagnostics, intermittent-glitch method
2026-10-13 13:16:42 +08:00
seedds 4d8ac53681 Into the opening course: resource heap slack, compute start offset, CPU-written text
- patch_nso.py: Mario main cut gives every resource heap +16 KB; Model/NpcCommonerA.bfres
  came up 0x70 bytes short of its size-table budget while loading Course900
- dmsh_build.py: compute instructions start at code area +0x100, not +0x30; the misaligned
  decode turned 12 particle / post-effect compute shaders into endless loops (GPU hang)
- Linear textures in CPU-visible pools are built on the pool buffer, so the CPU-rasterized
  glyph cache reaches the GPU (menu and dialogue text)
- Host command-memory segments grow on demand; RGB10A2SN vertex format; instanced DrawArrays
- DM_PROBE: thread IDs, register-relative dumps, hit filters, ExpHeap walker
- dm/tools/ghidra/Decompile.java; agent guides and README updated
2026-10-13 08:13:48 +08:00
seedds 02321677e7 Input past the title screen; virtual pools; probes without a debugger
- DM_KEYS scripted controller input ("seconds:button[+button][~hold]")
- NVN virtual memory pools aliased onto their physical pool when every
  MapVirtual request shares one base (Mario's 128 MB pool); previously
  MapVirtual failed and the game treated the pool as unusable
- DM_PROBE ported from d2r-mobile (host/probe.c): brk probes with
  register / backtrace output, plus DM_PROBE_X2 object dumps; used
  because lldb launches now wait on a macOS authorization prompt
- diagnostics: DM_NVN_VLOG (pools, MapVirtual), DM_NVN_CBLOG (command
  memory grants and callbacks), DM_NVN_ALLOCPROF
- docs: new failure patterns and the probe recipe

Open: starting a new game crashes while loading the opening course
(model resource heap overflow for NpcCommonerA.bfres).
2026-10-13 01:26:39 +08:00
seedds 16d3820a72 Mario Wonder renders its title screen on the Mac host
Shaders
- shader_split.py (new): scan RomFS and main for every BNSH and agl BAHS
  container (bfsha, particle effects, layouts, agl/gsys packs, main):
  12,811 shaders, map + compute facts from the control section
- key = sha256(control + code): many shaders share a control section
- NVN 55 binding layout: bound textures at 0x20 + 8*slot, images 0x120
- bindless textures read from uniform buffers (tex_ubo / samp_ubo)
- GL depth mode: translator remaps z' = z/2 + w/2 in vertex shaders
- compute code areas carry a 0x30 header (magic 0x12345679)

Replay
- 8-bit index buffers widened to 16-bit
- texture swizzle through Metal swizzled views
- non-packaged textures shared by storage (fixes a ~13 MB/frame leak
  from transient render targets rebuilt every frame)
- view_t packed to 16 bytes (SDK 16 NVNtextureView; fixes a stack smash)
- TBDR optimizations off by default until they know bindless readers

Host
- alias region 2.875 GB for the 1.75 GB VirtualAddressMemory reservation
- ideal core -2 resolves to the process default core
- shared font served with a BFTTF "decoded" header
- crash reports scan the stack and dump the popped frame

Docs: docs/agents (guides for porting other titles), AGENTS.md
2026-10-13 00:54:49 +08:00