Keep the fraction of a frame the ocarina recorder's clock was losing above 60 fps, in a new patch set orecord.

The ocarina manager counts the length of each note and rest in whole 60 Hz frames and adds the time manager's frames
per update to that clock at four sites (main+0x32926c8, main+0x3293410, main+0x3296b08, main+0x3296da4) through
fcvtzu, which cuts the step toward zero. Above 60 fps the step is under one frame, so the clock never moved: a
recording kept its notes without their lengths (the recorder leaves a length of 0 out), the 360 and 120 frame limits
never came, and a recording ended after a fixed 60 frames of its timer instead of following the clock; at the 40 and
45 fps limits it counted one a frame. The set replaces each fcvtzu with nine words that add the fraction carried on
the patch page's +264, store what is left after the cut (NaN and negatives as 0) and convert the sum. Steps of one
and two frames give the game's count with the fraction left at 0, so 30 and 60 fps are unchanged. The words call
nothing and take no lock (one of the functions returns through an unsaved x30), and use only v16-v18, which the three
functions never name. The set is appended so that no existing set's index moves; ocarina_timing = 0 gives the game's
words.

Not reproduced before the change (read from the code). The generator's check of the original words passed against
the 1.1.1 main, the new tests/ocarina_timing_check.py passed (.venv, Unicorn), as did frame_step_check,
text_speed_check, sprint_check, movie_timeline_check, cfg_parse_check, launcher_test and cfg_rows_check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
chrg6
2026-11-13 18:57:56 -05:00
parent a40afdd86d
commit 5f9374e1d9
5 changed files with 393 additions and 1 deletions

View File

@@ -363,3 +363,26 @@ site sprint main c07f3c 39400108 39400108 950a63fd # ldrb w8, [x8] -> ldrb w8,
set text_speed NX2NAVI_TEXT_SPEED_PATCH 1
site text_speed main 774b74 36000074 @page:x9 394d2a6a d100054a 8a0a010a f901012a 39483129 2a140129 36000069 # tbz w20, #0, #0x774b80 -> adrp x9, page; ldrb w10, [x19, #0x34a]; sub x10, x10, #1; and x10, x8, x10; str x10, [x9, #0x200]; ldrb w9, [x9, #0x20c]; orr w9, w9, w20; tbz w9, #0, #0x774b80
site text_speed main afe1d8 bd404e61 bd404e61 @page:x22 f94102db cb13037b dac0137b d346ff7b 9e220362 bd420ac8 1e280842 1f020421 # ldr s1, [x19, #0x4c]; adrp x22, page; ldr x27, [x22, #0x200]; sub x27, x27, x19; clz x27, x27; lsr x27, x27, #6; scvtf s2, x27; ldr s8, [x22, #0x208]; fmul s2, s2, s8; fmadd s1, s1, s2, s1
# The ocarina recorder's clock: the ocarina manager (its update main+0x17bad0, the vtable slot at main+0x4ba19a8)
# keeps at +0x265fc of its object the length of the current note or rest in 60 Hz frames, and adds trunc(frames per
# update), the time manager's +0x10, to it at four sites (fcvtzu w8, s0): main+0x32926c8 and main+0x3293410 in two of
# the manager's update states (main+0x3292690, main+0x32932b0), main+0x3296b08 and main+0x3296da4 in its recording
# function (main+0x3296840, modes 10 and 13 at +0x230). At each recorded note the length goes into the recording as a
# word of its own (twice the length, into +0x300 or +0x498, left out when it is 0) and the clock starts again; the
# clock stops at 360 frames, and at 120 while +0x271 is set, and a recording ends once the float timer at +0x244
# passes min(clock + 60, 420) frames (main+0x329285c). Below one frame per update the cut is 0: above 60 fps the clock
# stood still, so recordings lost their rests, the clock's limits never came and the end of a recording came at a
# fixed 60 frames of that timer; at the 40 and 45 fps limits it counted one a frame. Each site carries the fraction to
# the next on the patch page's +264: s16 = step + fraction, the fraction becomes max(s16 - trunc(s16), 0) and
# w8 = trunc(s16), so the clock counts 60 a second at any frame rate, and a step of exactly one or two frames gives
# what the game computes with the fraction left at 0. A step that is NaN, negative or infinite gives the game's cut and
# leaves the fraction 0. Only the manager's update runs the four sites, so the page word needs no lock.
# main+0x3296840 returns through x30 without saving it, so these sites cannot call the page. x8 is each site's own
# result; s16, s17 and s18 are used nowhere in the three functions, s0 keeps the step, and no word reads or writes the
# flags.
set orecord NX2NAVI_OCARINA_TIMING 1
site orecord main 32926c8 1e390008 @page:x8 bd410910 1e302810 1e25c211 1e313a11 2f00e412 1e326a31 bd010911 1e390208 # fcvtzu w8, s0 -> adrp x8, page; ldr s16, [x8, #264]; fadd s16, s0, s16; frintz s17, s16; fsub s17, s16, s17; movi d18, #0; fmaxnm s17, s17, s18; str s17, [x8, #264]; fcvtzu w8, s16
site orecord main 3293410 1e390008 @page:x8 bd410910 1e302810 1e25c211 1e313a11 2f00e412 1e326a31 bd010911 1e390208 # the same at the other three sites
site orecord main 3296b08 1e390008 @page:x8 bd410910 1e302810 1e25c211 1e313a11 2f00e412 1e326a31 bd010911 1e390208
site orecord main 3296da4 1e390008 @page:x8 bd410910 1e302810 1e25c211 1e313a11 2f00e412 1e326a31 bd010911 1e390208