Repository navigation
Ocarina of Time: the host can ask the game for its half rate, so subtitles and movies keep step below 60 fps - #2082
Closed
oliverames wants to merge 2 commits into
Closed
oliverames wants to merge 2 commits into
oliverames wants to merge 2 commits into
Conversation
This was referenced Nov 9, 2026
Open
oliverames
marked this pull request as draft
November 10, 2026 11:54
oliverames
marked this pull request as ready for review
November 10, 2026 12:19
…e, the stub's branches are computed, and a test runs it at both base rates Base 30 in the frame ratio is the framework's half rate, not a cutscene speed: bit 0 of its +0x24f is the mode and bit 1 flips each pass of its frame function, and with both set the draw and the present return at once while the calc tree runs. The game then calcs twice a drawn frame, the ratio's function among them, so each calc takes half the frame's step: step x base / 60 is right, and the step alone (PR seedds#1996) would run that mode at twice its speed. The ratio's unit is 1/60 s whatever the base (the time object's step in seconds is ratio x 1/60). The stub counted the frame's vsyncs at both calcs, so the host's game time would read twice the real time in that mode. It now counts them at the calc that is drawn. Its branch targets are computed from the words emitted instead of fixed offsets. test_oot_frame_step.py runs the emitted stub under Unicorn (optional, as test_astral_fps.py): both bases at steps 1 to 3, the count, x17 lost after a store, and the game's own division at target 0 and before a step is decided. It fails on main's stub (the count) and on PR seedds#1996's (the ratio). Co-authored-by: Zard Studios <96824937+Zard-Studios@users.noreply.github.com> Co-authored-by: dsi-hub <260802842+dsi-hub@users.noreply.github.com>
… holds 2 vsyncs (DM_FPS_HALF_RATE=1) Some of this game counts calcs, not time: its movie player takes one picture a calc and the timeline that fires its subtitles advances once a calc. The frame ratio moves what reads the time object by the frame's real time, so below 60 fps the two fall apart (issue seedds#1922, the cutscenes of PR seedds#1996). The game's own answer to a frame of two vsyncs is its half rate, two calcs a drawn frame. A stub at its request site (main+0x650ea4 on v131072, placed by the function's fingerprint) ORs the host's ask (state +0x44) into the flag the game reads, and fstep_tick asks while the hold is 2 vsyncs or more. Off by default; refused with a red line when the hold is off, where a half-rate frame of one vsync would calc 120 times a second. On an iPhone Duo (the hold on, the movie network off, a new game): the half rate was asked for when the hold went to 2 at the opening movie, the log's game time read 1.002 of real time, and the movie's 154.2 s took about 160 s. test_oot_frame_step.py runs the request stub under Unicorn.
oliverames
force-pushed
the
oot-half-rate-ask
branch
from
November 10, 2026 14:04
fde4b72 to
46ac074
Compare
Contributor
Author
|
Closing this. It still crashes the game after the opening movie whenever the half rate is in effect (6 of 6 runs on the Duo), and #2456 by @PucaVaz now handles the movie side of #1922 another way, by letting the movie player take up to two pictures a frame. The subtitle timing this PR was after isn't covered by #2456, so if that turns out to matter I'll come back to it with a fresh approach. #2071 stays open on its own. Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
As of Nov 10 this crashes the game. Please don't merge it as it is.
With the half rate in effect when the opening movie ends, the game dies a few frames later, every time I've looked:
CRASH sig=11 addr=0 pc=main+0x332260, on the game's main thread, three log lines after the decoder'send of stream after 9254 pictures(the first draws of the next scene). The backtrace is the same in all six:main+0x54fbc main+0x54744 main+0x61070.c3460a1d, and8a56cfddwith main's later host fixes), at a 60 and a 30 fps target, with the allocator cap at 32 and at 8, and with present snapshots on.Where it dies: the function at
main+0x3321d0stores a word through the pointer at its object's+0x1c818(ldr x9, [x19]thenstr w8, [x9], withx19at the object+0x1c818), and that pointer is null. I don't know yet why it's null in the half rate: my guess is that the first drawn frame of the next scene comes before the calc that sets it up, but I haven't shown that. I'm looking at it, and I'll either fix it here or close this.Stacked on #2071: the first commit here is that PR's, and the half rate needs its fix (a half-rate frame's vsyncs counted once). I'll rebase when #2071 lands. This replaces my draft #2072.
What and why
For #1922. Below 60 fps this game's cutscenes come apart since the frame step gave it real time: the subtitles and the movie picture fall behind the music, and at a 30 fps target a movie plays at half speed.
The cause is that part of the game counts calcs, not time. Its movie player takes one picture a calc, and the timeline that fires its subtitles (
PlaySubtitleWithVoicetrigger events) advances once a calc too. The frame ratio moves everything that reads the time object by the frame's real time and leaves those two behind.My first try, #2072, let the movie player take two pictures a calc. On my iPhone Duo that put the picture on real time and left the subtitles where they were, which looked worse than before. "However, there was one particular boy..." came up about 98 s into the opening at about 31 fps, over the nightmare's castle gate. At 53 fps the narration was over by 61 s, over Link asleep, where it belongs.
The game has its own answer for a frame of two vsyncs, and that's what this PR uses: its half rate, two calcs a drawn frame, so everything that counts calcs sees 60 a second at 30 fps.
[ModuleSystem+0xa30], writing 2 (enter) or 1 (leave) to the framework's+0x24e(main+0x650ea4on v131072).+0x44). Without an ask the game's own flag still decides.fstep_tickasks while the hold is 2 vsyncs or more, underDM_FPS_HALF_RATE=1. It's off by default.DM_FPS_HOLD=offa half-rate frame of one vsync would calc 120 times a second, so the switch is refused there with a red line.The reference entry is "A game that counts calcs falls out of step with its own time below 60 fps" in
docs/reference/frame-rate.md, and the game note has the Duo numbers.Checked
test_oot_frame_step.pygains three Unicorn tests of the request stub (the game's flag alone, the host's ask, and no stub when the function isn't found). All 8 in the file pass, and they skip without Unicorn.test_patches.py,test_nso_match.py,test_docs.py,test_env_switches.pyandlint_python.pypass.patch_nso.pyplaces the stub atmain+0x650ea4on the real v131072main.DM_CTL(Reference: a scripted run on a physical iPhone (DM_CTL pushed with devicectl) #2076), so nobody was pressing buttons.Docs
docs/reference/<subsystem>.mdentry, andpython3 dm/tools/docs_symptoms.pywas run (the symptom index)dmsh_chain.py --headerwas run anddm/gfx/nvn/dmsh_chain.his part of the change (no chain tool changed)