From 6296161715482c9fff0e4be67dbb143259c0c7ac Mon Sep 17 00:00:00 2001 From: Zard Studios <96824937+Zard-Studios@users.noreply.github.com> Date: Mon, 9 Nov 2026 15:33:41 +0100 Subject: [PATCH] Fix Ocarina of Time frame ratio calculation and enable frame buffering (0400486024DEC000) - patches.py: In cutscenes, Nintendo sets bit 0 of [x21+0x24f], which selects base rate 30.0f in s1 instead of 60.0f. The previous stub calculated (step * base_rate) / 60, resulting in (2 * 30.0) / 60 = 1.0 at 30 fps (or 0.5 at 60 fps). This caused cutscenes and dialog progression to run in half-speed slow motion while audio played in real time. Replace with fmov s8, s0 to set the ratio directly to (float)step and adjust the CBZ forward jump offsets accordingly. - game.tsv: Add present_snap on (as Breath of the Wild) so that swapchain textures are copied immediately into host present snapshots, decoupling the game engine from display link vsync jitter and preventing micro-stutters under the macOS compositor. --- dm/games/0400486024DEC000/game.tsv | 1 + dm/games/0400486024DEC000/patches.py | 15 ++++++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/dm/games/0400486024DEC000/game.tsv b/dm/games/0400486024DEC000/game.tsv index e0c8ec0cdc..5cf5214ac2 100644 --- a/dm/games/0400486024DEC000/game.tsv +++ b/dm/games/0400486024DEC000/game.tsv @@ -40,6 +40,7 @@ shader_note Ocarina of Time shaders # The game steps its time by a frame ratio, its base rate / its frame rate, whatever the frame took: frames longer than a vsync played in slow motion. The ratio takes the host's # frame step instead (MATCH_FRAME_STEP in patches.py, found by its function on any build that keeps it): 1 vsync at 60 fps, 2 when 60 does not hold, so game time = real time fps_target 60 +present_snap on # ---- Graphics ---- # agl wraps its clears in render conditions on Donkey Kong Bananza and Splatoon Raiders; the same library here diff --git a/dm/games/0400486024DEC000/patches.py b/dm/games/0400486024DEC000/patches.py index cade577a2e..e268f3f993 100644 --- a/dm/games/0400486024DEC000/patches.py +++ b/dm/games/0400486024DEC000/patches.py @@ -4,8 +4,8 @@ # The game's frame ratio. Once a frame the function at main+0x6e6be0 (v131072) divides its base rate (60.0, or 30.0 while bit 0 of [x21+0x24f] is set) by the int frame rate at # [x21+0x2c8] (fdiv s8,s1,s0 at +0xe4) and hands the result to the time object (main+0x3b4e300: ratio at +0x10, 1 / ratio at +0x14, round(60 / ratio) at +0x1c), which the game -# steps by; the frame's own time never enters it, so a frame that takes two vsyncs at rate 60 plays at half speed. The stub gives the ratio the host's step instead: step × base / 60 -# (the division's value when the host keeps frames 60 / rate vsyncs), and counts the vsyncs stepped. Until the host has decided a step, and at fps_target 0, the division runs. +# steps by; the frame's own time never enters it, so a frame that takes two vsyncs at rate 60 plays at half speed. The stub gives the ratio the host's step instead: (float)step +# (the ratio when the host keeps frames 60 / rate vsyncs), and counts the vsyncs stepped. Until the host has decided a step, and at fps_target 0, the division runs. # s0 and x16 / x17 are free there (s0 is written again on every path before it is read; the function uses neither x16 nor x17); s1 keeps the base rate. # Lichii (a GPL emulator; only the fact was taken) found the division (its notes: main+0x6E6CC4, 60 / [x21+0x2C8]) def frame_step(ctx): @@ -14,23 +14,20 @@ def frame_step(ctx): fs_site(site, '2818201e') # fdiv s8,s1,s0 start = here(); emit(ADRP(16, here(), FS_STATE_OFF)) for w in (0xb9400211, # ldr w17,[x16] target fps (0 = off) - 0x340001b1, # cbz w17,orig (+13) + 0x34000151, # cbz w17,orig (+10) 0xb9402211, # ldr w17,[x16,#0x20] step in vsyncs - 0x34000171, # cbz w17,orig (+11) not decided yet + 0x34000111, # cbz w17,orig (+8) not decided yet 0x1e230220, # ucvtf s0,w17 0xf9401a10, # ldr x16,[x16,#0x30] 0x8b110210): # add x16,x16,x17 emit(w) emit(ADRP(17, here(), FS_STATE_OFF)) for w in (0xf9001a30, # str x16,[x17,#0x30] vsyncs stepped - 0x1e210808, # fmul s8,s0,s1 step × base rate - 0x52a84e10, # mov w16,#0x42700000 60.0 - 0x1e270200, # fmov s0,w16 - 0x1e201908): # fdiv s8,s8,s0 / 60 + 0x1e204008): # fmov s8,s0 step as ratio emit(w) emit(B(here(), site + 4)) emit(0x1e201828); emit(B(here(), site + 4)) # orig: fdiv s8,s1,s0; back - fs_put(site, [B(site, start)], 'frame ratio: the host\'s step × base rate / 60 instead of base rate / the game\'s frame rate (state page target fps; 0 = the game\'s ratio)') + fs_put(site, [B(site, start)], 'frame ratio: the host\'s step instead of base rate / the game\'s frame rate (state page target fps; 0 = the game\'s ratio)') dynamic_resolution(ctx) # The game's dynamic resolution (game__gfx__DynamicResolutionParam: LoadThreshold, ReduceCounterMax, IncreaseCounterMax and a list of levels, each a ResolutionReduceScale and a