Skip to content

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
seedds:mainfrom
oliverames:oot-movie-two-pictures
Closed

oliverames wants to merge 1 commit into
seedds:mainfrom
oliverames:oot-movie-two-pictures

Conversation

@oliverames

@oliverames oliverames commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

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:

  • The movies are AV1 at 60 pictures a second with no sound track (Event/Movie/*.webm). The game's audio plays the sound beside them.
  • The player's update (main+0x3b5ca60 on 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.
  • The limit is 1, or 2 for a creation argument of 2 or more (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 turns cinc w8,w28,ge into add 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

  • Tests: dm/tools/tests/test_oot_movie.py is 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.py and lint_python.py pass, and patch_nso.py places the cut at main+0x3b5d084 on the real v131072 main.
  • Games run, with title ID and version: Ocarina of Time 0400486024DEC000 v131072, before the cut only. On one 18-minute run the opening movie's 9,254 pictures took 262 s by the log. There were 9,283 frames between the decoder's first picture and its end of stream, so one picture a frame.
  • Devices and OS: iPhone Duo (iPhone19,4), iOS 27.3 beta, docked.
  • On the device with the cut (iPhone Duo, 2026-11-09, one run, a new game on an empty save folder): the opening's 9,254 pictures in about 156 s, 4,555 to 4,854 frames, so the decoder does keep two pictures a calc ready. The subtitles lag the picture, as described at the top.

Docs

  • The finding is in its game note or its docs/reference/<subsystem>.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • A chain tool changed: dmsh_chain.py --header was run and dm/gfx/nvn/dmsh_chain.h is part of the change (no chain tool changed)
  • No game files, keys or data extracted from a game in the change or the attachments

… 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.
@github-actions github-actions Bot added rewriter The NSO rewriter and per-build patches tools Tools and scripts (dm/tools, scripts) docs Documentation game: 0400486024DEC000 A game labels Nov 9, 2026
@oliverames
oliverames marked this pull request as ready for review November 9, 2026 21:20
@oliverames
oliverames marked this pull request as draft November 9, 2026 21:38
@oliverames

Copy link
Copy Markdown
Contributor Author

Closing in favour of #2082. On a device this cut put the movie's picture on real time and left the subtitles behind it, because the subtitle timeline counts calcs as well. #2082 fixes both with the game's own half rate, and its reference entry keeps what I learned about the player's limit here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400486024DEC000 A game rewriter The NSO rewriter and per-build patches tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant