Apply PR #262's device-buffer fix for clicks caused by audio underruns.
Reconfigure on both output-device and hardware sample-rate changes, respect
the device's frame range, and read back the actual buffer size. Avoid
rewriting unchanged sizes when buffer changes notify the format listener.
Remove listeners before teardown, including failed initialization, so they
cannot configure a device that the AudioUnit has already released.
Add hardware-independent regression coverage for switching, rate changes,
scope filtering, limits, synchronous notifications, diagnostic overrides,
absent devices, and listener cleanup. Record the original gameplay evidence
and the additional silent Core Audio validation in the audio reference.
Validation: full host tests, Mac host build, docs checks, and the iOS source
compile check (178 files) passed. Silent USB/speaker switching and speaker
rates of 44.1/96/48 kHz verified 221/480/240 frames. HDMI switching and
gameplay listening after these listener changes remain unvalidated.
Co-authored-by: parsifal295 <130349765+parsifal295@users.noreply.github.com>
The watchdog read the clock before it took g_aos_lock. The return from the background restarts the AudioUnit under that lock and stamps its last callback time (au_go), so a watchdog tick that had read the clock just before waited for the lock and then compared an older time with the newer stamp: the unsigned difference wrapped ("has not called back for 18446744073662 ms", DRAGON QUEST VII Reimagined on an iPhone after 10.7 s in the background) and a running AudioUnit was handed to the silent sink and restarted. The clock is now read after the lock is taken, as every au_go caller does. Mac: 30 s of Luigi's Mansion 3 with real output, no watchdog line
- movie::Buffer::Data() is Base() + Offset() and Capacity() the whole buffer (STEINS;GATE RE:BOOT's subsdk0), so a PCM or picture output whose read offset the client had advanced was written past its end, over the guest heap. GetOutputBuffer resets the range first. Native-texture mode is left as it is: there Data() is the texture object the client points it at. An end-of-stream packet as long as the whole input buffer carries no data, as one with time -1 already did.
- Creating the output AudioUnit takes about 24 KB of stack (measured on the Mac), more than some game threads have left, and OpenAudioOut runs on the game's thread: it now runs on a host thread, once per AudioOut. Not HLE_FORCE_BIG, which would hide the call site the open hooks log.
- CalculateSpanBetween and CalculateStandardUserSystemClockDifferenceByUser go through the time service session, which the host never opens: AbortImpl in the SDKs of STEINS;GATE RE:BOOT and Tears of the Kingdom, which imports both. The host computes what the service does: the span from the steady time points (the network times when the sources differ, else an error), and the user clock difference from the user clock offsets (0 when their sources differ or both have automatic correction on). CalculateSpanBetween is not in the PR.
- Snapshots carry the user clock context of GetSystemClockContext, which stays the same until the host clock is set; worked out per snapshot, it moved by a second at random, which the user difference would report as a clock change.
- t_time: 14 checks on host snapshots and edited copies; it fails without the two hooks.
Mac: Monster Hunter Rise headless for 75 s with real (muted) audio: OpenAudioOut from the game's thread, AAC and H.264 movies through GetOutputBuffer, 60 fps, no movie red lines. No profile with a matching shader cache on this drive calls the two time functions.
Co-authored-by: Raghav Agarwal <88780872+futurist-raghav@users.noreply.github.com>
- 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
- FNA on .NET NativeAOT with SDL3 and FNA3D's NVN driver; 162 GLSLC outputs in shaders.nx.bin
- Rewriter: data blocks in the text segment are left alone (dm/tools/nso_code.py; main starts with 1.7 MB of data that read as x18 sites); the census applies the same rule. The 52 modules of the 19 other games on hand rewrite byte-identically
- Upside-down picture: nvnCommandBufferSetViewportSwizzles is recorded and a NEGATIVE_Y swizzle flips the viewport
- Logo movie: an H.264 decoder made at Configure (parameter sets in csd-0 / csd-1) now gives RGBA in the native texture mode, not NV12
- SDK calls: vi::CreateLayer with a size; AudioIn (no microphone); the EOS NRO stays unloaded and the game plays offline
- Memory for a 5 GB block: getenv answers the new profile key game_env (DOTNET_GCRegionRange: the GC range 3.75 → 1.5 GB); build cuts for malloc's address space (40 → 2.5 GB), the SDK node area and the SDK's 1 GB alignment of large regions
- Modules: NVIDIA's OpenGL and Vulkan drivers are never loaded (profile_guess.py); the survey's hook list reads dm/gfx too
- dm/host/Makefile: a build for another game always relinks (two builds in the same second kept the first game's host)
- 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).
- The idle timer is off while the app is active: only touches reset it, so with a controller or in a cutscene the screen dimmed and locked.
- Open file limit raised from 256 to 4096 at host start; Documents/romfs and temp are kept out of device backups.
- Audio: observers for audio session interruptions (Siri, alarms, calls) and media services resets (dm_audio_reset recreates the AudioUnits); becoming active ends an interruption whose "ended" never came.
- A watchdog switches a started output whose device has not called back for 200 ms to the silent sink and retries the AudioUnit after 1, 2, 4, 8 s; a start that fails falls back the same way instead of aborting. t_audio scenario 3 stops a playing AudioUnit behind the host's back.
- GetCurrentProcessorNumber: a thread with no ideal core reports the lowest core of its mask, not core 0.
- 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.
- 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%).
- hle_audio.c: Start, Stop and the background suspend/resume change an output's consumer only under g_aos_lock and act on an explicit backend (AudioUnit / silent sink). Start used to mark the output started and pick its backend outside the lock: a suspend in between gave one output two silent sinks (or a sink beside the AudioUnit), and a Stop racing a suspend joined a sink that never existed. A stress test (two threads toggling Start/Stop on three outputs while another toggles suspend) hung or crashed in every run before, and runs clean now with never two sinks on one output.
- hle_audren.c: CloseAudioRenderer joins the render thread (dm_guest_thread_join) instead of sleeping 20 ms, and the thread closes its PCM output when it ends (new dm_pcmout_close), so the output no longer keeps running and holding an AudioOut slot.
- hle_svc.c: g_map_lock is held over MapMemory's and UnmapMemory's whole lookup, check and change, and over QueryMemory's reads. Concurrent unmaps deleted through a stale index (map_del moves the last entry): 8 threads mapping and unmapping stack ranges failed about 2,000 maps and unmaps per run and left ranges registered; zero now.
- hle_thread.c: closing a SystemEvent's write handle removes its binding, and signalling looks up and signals under one hold of G. A SystemEvent destroyed and created again at the same address used to keep the dead binding: the new kernel event was never created and signals went to the closed (or recycled) handle. Tested with 2,000 create/destroy rounds and a decoy event taking the freed handle numbers.
- dm/gfx/README.md: the shader-cache key gap (shader_pairs.tsv, profile nvn_*/shader_* rows, paired pixel shader inputs) is recorded, with the workaround, until the next chain change fixes it.
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.
- Magenta 16x16 blocks in Super Mario 3D World 1-1 (iPhone only): the composite shader 66a71a3081b91b9a
renders into the RGB10A2 image while sampling it in the same pass. That is undefined in Metal: the
M4 Pro reads the old content, the A20 Pro sometimes returns whole 16x16 blocks as (1,0,1). Found with
the new device self-check DM_NVN_MAGSCAN (blocks first appear on that attachment, and still appear
with the suspected particle dropped). fb_prepare ends the pass, copies the attachment level into a
snapshot and binds the snapshot for that draw, only for shaders in the new profile key feedback_snap
(doing it for every shader cost BotW 16.6 -> 36.6 ms). DM_NVN_FB_SNAP=0 / all / <keys> overrides.
- The game language now defaults to English (nn::oe::GetDesiredLanguage returns en-US; DM_LANG=zh-Hans
in Documents/env.txt for Chinese).
- Code comments, docstrings and docs are English only; the zh-CN doc twins are removed. README.md has the
English text followed by the same text in Chinese. Files the shader chain version hashes keep their
bilingual comments (editing them changes every game's chain version).
- New profile 01003FC01670C000: sdk and subsdk0 are byte-identical to Metroid Dread's; data is six CRI CPKs; the 751 shaders are GLSLC outputs compiled into main (shader_split.py: shader_main_glslc), built as single-stage programs and paired per draw (shader_separable: every vertex shader is paired with a cover of the pixel shader inputs, so vs_pad lets any pair link)
- Host hooks for SDK 11 calls that reach a service: hid::GetMouseState, fs::CleanDirectoryRecursively, fs::EnableGlobalFileDataCache, oe::SetUserInactivityDetectionTimeExtendedUnsafe, time::ToPosixTime, audio::GetAudioOutName
- StandardAllocator::Allocate(size): on this build only, blocks of 64 KB or more are 64 KB aligned; the engine cuts three ring heaps out of a GPU pool at 64 KB alignment relative to its GPU address, and with the host's 4 KB-aligned address the third heap kept base 0 (null write when the prologue enters its first battle)
- NVN: triangle fans and indexed quads, vertex streams 14 and 15 on Metal slots 14 / 15 (Metal assertion buffer index 31), TextureBuilderSetPackagedTextureData copied into the storage at TextureInitialize, InsertDebugMarker as a no-op
- Mario 1.2.1 sets present interval 2 for single frames after a slow one and advances its time step by the interval; frames were shown for one tick and the game jumped ahead on the iPhone. The display link now keeps a frame for its whole period, and the 30 fps target waits until interval 2 has held for a second (Engage, which asks for 1 with its 60 fps cut, is unchanged). Test switch DM_TEST_PRESENT_LAG
- Survey notes: extra modules are supported, GLSLC outputs in main are counted
- Regression: Mario 1.2.1, Engage and Metroid Dread runs show only their known red lines; t_nvn 63/63; t_present_crop / t_mip_extent build again (t_mip_extent's one failure is the same at a9c56fd)
- Each AudioOut is logged as audio #N: the open with its guest call site, Start / Stop / Close (first 32 per output), and the 2000-callback stats line per output. An Engage phone log had two outputs after a background round trip, one fed and one starved; the combined stats read as "every other callback underruns"
- DM_OE_SCRIPT gains bg=1 / bg=0, the same sequence as the iOS lifecycle callbacks (focus, audio take-over, guest clock freeze, graphics suspend); DM_AUDIO_MUTE=1 silences the real Core Audio path for such tests on the Mac
- The silent sink no longer makes up time the process was suspended (it pulled seconds' worth at once and logged hundreds of underruns)
- Host tests t_audio, t_audio_spsc and t_oe build again (game profile include, stubs) and pass
- docs/agents/03-bringup.md: several outputs, background round trip on the Mac
- patch_nso.py: three more x18 forms from IL2CPP code: an offset jump
table (targets read at rewrite time), blr x18 (original return
address kept for C++ unwinding) and an sp write from x18 (alloca).
Mario's rewrite output stays byte-identical.
- loader.c: symbols resolve globally in load order, also for a
module's references to its own exports (local / non-default
visibility excepted). Engage's main overrides operator new / delete;
sdk bound its own copies and freed Unity's blocks into the SDK heap.
- hle_svc.c: QueryMemory reports each module's Horizon layout (code,
read-only, data, patch area) and other host memory as free, which
the SDK's C++ unwinder (nn::ro::detail::GetExceptionInfo) needs.
- New takeovers: nn::fs::IsMounted, nn::ro (hle_ro.c), prepo
Initialize, SDK 12 audio device names and OpenDefaultAudioOut, an
SDK 12 hid block (single GetNpadState, modes, touch, gestures,
six-axis details, colors, applets), movie::Decoder with no impl
(hle_movie.c) and failing nvdrv opens (hle_nvdrv.c), and NVN getters
/ no-ops Engage calls. nvn.h: TextureGetMemoryPool returns a pointer.
- Makefile: a game stamp, so changing GAME rebuilds the host.
- docs/agents/03-bringup.md: catalogue entries for all of the above.
- Mario 1.2.1 scripted run still reaches the opening course with the
same single red line.
- hle_nvn.c: drop the command memory callback / grant / caller
logging, the overflow details, the +0x10 write log, the one-off
NVN table dump, and the DM_DBG_GRANTS, DM_DBG_CALLSITES,
DM_DBG_CMDMEM_SHRINK and DM_DBG_OLD_USED switches.
- nvn_mtl.m: drop the per-draw NaN probe (DM_DBG_NANPROBE), the
colour target statistics (DM_DBG_TEXSTAT) and the PASSDUMP
completion timing.
- Kept as regular diagnostics: the audio callback line every 2000
callbacks (frames per callback, queued blocks, underruns) and
DM_DBG_AU_FRAMES (docs/agents/03-bringup.md, crackling on iOS);
DM_NVN_SHOT up to 4096 frames (the 05-debugging burst recipe).
- Mac 1.2.1 scripted run reaches the opening course with the same
single red line as before.
Fixes the flicker / black band in the opening course and the crash after
the first scene on the iPhone (A20 Pro), confirmed on the device.
- nvn_obj.h: cmd_t leaves byte +0x10 of NVNcommandBuffer to the game.
Mario's inlined NVN code writes 1 there (main+0x4da5c, main+0xb8dec).
It used to hit the low byte of `end`: the game's 512-byte material
setup blocks shrank and overflowed into host segments,
GetCommandMemoryUsed (cur - begin) returned 0, the game marked the
setups empty and skipped calling them, and draws ran with the previous
vertex buffers (out-of-range vertices, NaN).
- hle_nvn.c: GetCommandMemoryUsed counts bytes across host segments plus
16 for the jump. Host segments are released only when the game
re-grants memory overlapping the recording's guest bytes (the game
keeps calling recorded setup blocks for many frames); the 8-frame
recycling is gone.
- nvn_mtl.m: failed pipelines are cached and reported once with their
vertex / fragment keys instead of being rebuilt on every draw.
- front_ios.m / main.m: the audio session asks for a 5 ms I/O buffer at
boot and on return to the foreground. The default (~21 ms, 1024
frames per callback) outran the 4 x 5 ms the renderer keeps queued:
every callback underran (crackling). Device: 240-frame callbacks,
0 underruns.
Still contains [DEBUG-flk1] instrumentation (tagged, mostly behind
DM_DBG_* switches; audio statistics every 2000 callbacks in
hle_audio.c). To be removed with one grep.
- 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