Repository navigation
Ocarina of Time: the movie player takes up to 2 pictures a calc, so movies keep time down to 30 fps - #2072
Closed
oliverames wants to merge 1 commit into
Closed
Ocarina of Time: the movie player takes up to 2 pictures a calc, so movies keep time down to 30 fps#2072oliverames wants to merge 1 commit into
oliverames wants to merge 1 commit into
Conversation
… movie keeps its time down to 30 fps (issue seedds#1922) The movies are AV1 at 60 pictures a second with no sound track; the game's audio plays the sound beside them. The player's update (main+0x3b5ca60, v131072) adds the time object's step to a remainder and advances the whole picture times in it, at most the player's limit (+0x20). The limit is 1, or 2 for a creation argument of 2 or more, and the game passes 1: one picture a calc, right at 60 calcs a second and too few once a frame takes longer than a vsync. At a 30 fps cap or a 2-vsync hold the frame step hands the game a ratio of 2, the player is due 2 pictures and takes 1, so the movie plays at half speed while its sound keeps time (issue seedds#1922, and the half-speed cutscenes of PR seedds#1996). On an iPhone Duo at 26-31 ms a frame the opening's 9,254 pictures (154.2 s) took 262 s. A function-matched cut (MATCH_CUTS, the player's constructor by its fingerprint) makes the limit 2, the player's own value for the larger argument: cinc w8,w28,ge becomes add w8,w28,#1 at main+0x3b5d084. Below 30 fps a movie still falls behind; pictures skipped by the decoder (PR seedds#1379) would reach there too. test_oot_movie.py checks the cut's two instructions (capstone) and runs both around the compare under Unicorn: the limit for arguments 1, 2 and 3 goes from 1, 2, 2 to 2, 2, 2. The cut has not been timed on a device yet.
oliverames
marked this pull request as ready for review
November 9, 2026 21:20
oliverames
marked this pull request as draft
November 9, 2026 21:38
2 of 3 tasks
Contributor
Author
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.
Superseded by #2082, which fixes this with the game's own half rate. Please don't merge this one: the first device run shows the cut isn't the whole fix.
I timed the opening on my iPhone Duo with the cut, from a new game on an empty save folder. The picture now keeps its time: the 9,254 pictures took about 156 s at about 31 fps, which is 1.91 to 2.03 pictures a frame, against 262 s and 1.00 before. But the subtitles ran behind the picture. "However, there was one particular boy..." was on screen about 98 s in, and "who didn't have a fairy" showed up near the end of the movie. So the subtitle timeline looks like it advances once a calc as well, and letting only the picture catch up leaves the two out of step. I'd guess the narration cues follow the same timeline, but I haven't checked that.
I'm looking at where that timeline gets its time. The cleaner fix may be to have the game run its own half-rate mode (two calcs a drawn frame) when the host can't hold 60 fps, so everything that counts calcs sees 60 a second. The analysis below still describes the picture side correctly.
What and why
For #1922. At a 30 fps target the opening movie plays at about half speed while its sound keeps time. I saw the same thing on my iPhone Duo with the target at 60, because the game ran at 26 to 31 ms a frame there. The opening's 154 s took 262 s, and each scene came later against the narration.
The cause is the game's own movie player, not the decoder and not the frame ratio:
Event/Movie/*.webm). The game's audio plays the sound beside them.main+0x3b5ca60on v131072) adds the time object's step to a remainder and advances the whole picture times in it, up to a limit at the player's+0x20. Its queue pops that many decoded pictures and shows the last one, so the player already knows how to skip.main+0x3b5d068/+0x3b5d084), and the game passes 1.One picture a calc is right at 60 calcs a second. At a 2-vsync hold the frame step already gives the game a ratio of 2, so the player is due two pictures and takes one. That's why I don't think #1996's ratio change can help with this symptom.
The change is one function-matched cut in the game's
patches.py(MATCH_CUTS, the player's constructor by its fingerprint). It turnscinc w8,w28,geintoadd w8,w28,#1, which makes the limit 2, the player's own value for the larger argument. A movie should then keep its time down to 30 fps. Below 30 it still falls behind, so #1379's skipping in the decoder would still add something, and the two don't conflict.The reference entry is "A player that takes one picture a calc at most falls behind its sound below 60 fps" in
docs/reference/movies.md, and the game note links to it. I also corrected the note's line about the opening running at "the movie's own 24 fps pace". The movie is 60 pictures a second, and 24 was the game's frame rate at the time.Checked
dm/tools/tests/test_oot_movie.pyis new. It checks the cut's two instructions with capstone and runs both around the compare under Unicorn, where the limit for arguments 1, 2 and 3 goes from 1, 2, 2 to 2, 2, 2. It skips without those packages.test_patches.py,test_nso_match.py,test_docs.pyandlint_python.pypass, andpatch_nso.pyplaces the cut atmain+0x3b5d084on the real v131072main.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)