fix(vi): a cutscene's cues stay with its movie above a 60 cap (#1742) #1875

Merged
klimovich008 merged 2 commits from fix/movie-frame-count-above-60 into main 2026-11-10 20:27:06 +00:00
klimovich008 commented 2026-11-10 19:11:43 +00:00 (Migrated from github.com)

With the Game frame rate cap above 60, a cutscene that shows a movie runs its narration, its rumble and its end ahead of the picture: at 120 fps the narrator's lines in the new-game prologue start over each other (#1742). This makes the movie's frame count, which those cues are placed by, follow the frames' time. It is a fix for a live bug above a 60 cap; at the default cap of 60 nothing changes.

Why it happens

  • The game's movie player counts its updates while a movie plays (f_83b7c710: a count at +760 of the player, one more each game frame). A cutscene that shows a movie takes that count as its timeline's frame (f_83b5ec10, f_83b5e9a0), and the narration, the rumble and the cutscene's end are placed on that timeline.
  • The movie's picture and sound follow the clock (host/nn_video.c). #1612 made the game's own clock keep real time above 60, but this count is not made from the time step, so at 120 fps it still ran twice as fast as the movie.

What changes

  • nx2/oot_patches.py patch_movie_frames: the count's step in the lifted f_83b7c710 is hos_movie_frames(player) instead of 1. A lift where the place is not found once is left as it is, with a warning (a cap above 60 then behaves as it does now).
  • host/nn_vi.c hos_movie_frames: 1 while the host gives no frame time, which is at a cap of 60, with HOS_FPS_STEP=0, and in a build without #1612's hook. Otherwise the frames' time in 60ths, the fraction carried to that player's next update. At a cap of 60 this costs one load and a branch per movie update.
  • The carry is kept per movie player in a table of 16 places; the place used longest ago is taken for a new player. The game plays one or two movies at a time. With more than 16 at once the counts stay right on average and are no longer exact to one.
  • realtime_step now writes the frame time with atomic stores, because the new reader is on another thread.
  • HOS_FPS_MOVIE=0 keeps the game's own count of one an update, for comparisons.

Tested

Windows 11, Ryzen 7 5700X3D, Radeon RX 9070 XT (Vulkan 1.4, driver 0x8001a0). A new game from an empty save: the prologue movie (154 s) at 1280x720, nothing pressed after New Game. All runs on one binary: main 88b554e + #1696 + this change.

How it was read: the game's sound is dumped (HOS_AUDIO_DUMP); the centre channel carries the narration over the movie's own sound. Each 2 s piece of the first cap-60 run is looked for in the other run, after the two are aligned on the movie's sound. A narration line found at another time shows as a lag (negative: earlier). The first rumble value is the number of the first non-zero value the game sends; it sends about 200 a second.

runs narration lines against the first cap-60 run first rumble value game clock the movie plays to its last picture
cap 60 (default) 2 the reference; the second run 0.00 to +0.82 s #16186, #16258 1.002-1.006 at 60 fps yes
cap 120, this PR 2 -0.02 to +0.48 s; 0.00 to +0.93 s #16104, #16252 1.000 at 120 fps yes
cap 120, HOS_FPS_MOVIE=0 (as main is) 2 0.2, 4.4, 8.7, 14.8, 17.0, 21.4 and 24.6 s early; 0.2, 4.4, 8.7, 14.6, 16.8, 20.5 and 23.7 s early #9964, #10000 (about 31 s early) 1.000 at 120 fps yes
  • The six runs above were made before the review's changes to the host function (the table and the atomic access; for one movie the arithmetic is the same). One more pair on the final code, same base: narration lines +0.00 to +0.60 s and rumble value #16160 with this PR; 0.2 to 24.4 s early and #9868 with HOS_FPS_MOVIE=0. No fatal, device-lost or validation line in any of the eight logs.
  • Steps above 1 (review): the same binary slowed to 36 fps with HOS_FRAME_SLEEP_MS=25, one run each. Under a cap of 120 the count's step is 1 or 2 at every update: all narration pieces found, +0.00 to +0.83 s, first rumble value #16218. Under a cap of 60 the game's own count runs at 36 a second: the lines come 4.1, 9.7, 17.7 and 20.6 s late and the rumble at #24508 (about 41 s late). So no cue is lost to a step of 2, and holding the step at 1 would bring that lateness to caps above 60.
  • Long frames in the movie's window (16 to 168 s), every arm: 2 to 5 frames over 100 ms a run, the worst 281 and 511 ms at cap 60, 471 and 504 ms with this PR, 753 and 274 ms with HOS_FPS_MOVIE=0. Most are in the movie's first seconds. This change neither causes nor cures them.
  • tests/test_movie_frame_count.py (it registers itself for tests/run_all.py): the patch on a synthetic function of the lifted shape (applied once, a second run, the same lines in another function, three other shapes left alone, the build's warning), and hos_movie_frames cut from host/nn_vi.c and run at 60, 120, 240 and 40 fps, with two movies at once, with a movie that plays while others come and go, and with 9 to 377 players for 16 places. Negative controls: it fails on main's oot_patches.py and on main's nn_vi.c, each on its own, and the game's own step of one an update fails the same 120 fps checks. Passes with gcc, MSYS2 clang and MSVC-target clang.
  • The build's log confirms the place is found in the supported game version (the movie's frame count by the frames' time in code_046.c).
  • python -I -B tests/run_all.py on this branch (main 1392ea3, Windows, MSYS2 gcc and g++): 272 passed, 6 failed, 43 skipped. The 6 are nx2/test_nup_out_of_space.py, nx2/test_nup_paths.py, tests/test_fpx_sticky.py, tests/test_precompile_compute_guard.py, tests/test_pipeline_play_cache.py and tests/test_wayland_libdecor_hint.py; all six also failed on this PC on main without this change (checked at 9edb3a5 and 51de681: a path form and headers this PC's compilers do not find). host/nn_vi.c and the other host/*.c syntax-compile as whole files (not nn_codec.c and nn_video.c: my check has no Opus and FFmpeg headers).
  • Rebased since on main 117eae7, which repairs the two source checks that failed in this PR's first CI run (nx2/test_draw_parameters.py, tests/test_antilag.py; they failed on main 1392ea3 too). The new test, test_frame_rate_cap_step.py and test_mark_groups.py pass on the rebased commit.

Not covered

  • Only the prologue was measured. The other cutscenes that show a movie use the same player and count, but I did not run them.
  • #1726 (Link held in a jump in Saria's dialogue after Gohma) is not reproduced: I have no save there. The game has movies for Kokiri events, so a movie cutscene ending early is a possible cause, no more than that.
  • Game logic that counts frames elsewhere (the README names Gohma running into the wall) is not touched. In-engine cutscenes were not the problem: the 95 s in-engine scene after the prologue has its sound within +1.3 to -2.2 s between cap 60 and cap 120 on main without this change.
  • At a cap of 60 under 60 fps the cues are late, as before this change (measured above: the count there is one an update). Giving the count the frames' time at a cap of 60 too would fix that; it is not in this PR.
  • The count follows the game's time, as it does at a cap of 60. The game does not step through a long stall or a rate under 15 fps, while the movie's picture skips ahead, so after such a stall the cues are late by its length, at any cap.
  • Linux was not run (the tests only). The measurement binary is not on current main: main's shader sources have changed since my prepared caches were made. The branch is rebased on current main and its tests rerun there.
  • Left as it is: #1612's lifted step hook still reads the frame time as a plain volatile; its text is matched exactly by the pass that takes the hook out again.

Review

gpt-6.1-sol, five rounds on the source. It found an unsynchronised read of the frame time, a racy first-call initialisation, counts that stood still with more players than table places (twice: first with a start of 0, then with a fixed step between starts for one player in 233), and a playing movie losing its place to newcomers. All are fixed, each with a test that fails on the version before. Last round: none found.

Part of #1742 (the narration and the other cues of cutscenes that show a movie; Gohma and #1726 stay open there). Related: #1726, #1612, #1805.

🤖 Generated with Claude Code

With the Game frame rate cap above 60, a cutscene that shows a movie runs its narration, its rumble and its end ahead of the picture: at 120 fps the narrator's lines in the new-game prologue start over each other (#1742). This makes the movie's frame count, which those cues are placed by, follow the frames' time. It is a fix for a live bug above a 60 cap; at the default cap of 60 nothing changes. ## Why it happens - The game's movie player counts its updates while a movie plays (`f_83b7c710`: a count at +760 of the player, one more each game frame). A cutscene that shows a movie takes that count as its timeline's frame (`f_83b5ec10`, `f_83b5e9a0`), and the narration, the rumble and the cutscene's end are placed on that timeline. - The movie's picture and sound follow the clock (`host/nn_video.c`). #1612 made the game's own clock keep real time above 60, but this count is not made from the time step, so at 120 fps it still ran twice as fast as the movie. ## What changes - `nx2/oot_patches.py` `patch_movie_frames`: the count's step in the lifted `f_83b7c710` is `hos_movie_frames(player)` instead of 1. A lift where the place is not found once is left as it is, with a warning (a cap above 60 then behaves as it does now). - `host/nn_vi.c` `hos_movie_frames`: 1 while the host gives no frame time, which is at a cap of 60, with `HOS_FPS_STEP=0`, and in a build without #1612's hook. Otherwise the frames' time in 60ths, the fraction carried to that player's next update. At a cap of 60 this costs one load and a branch per movie update. - The carry is kept per movie player in a table of 16 places; the place used longest ago is taken for a new player. The game plays one or two movies at a time. With more than 16 at once the counts stay right on average and are no longer exact to one. - `realtime_step` now writes the frame time with atomic stores, because the new reader is on another thread. - `HOS_FPS_MOVIE=0` keeps the game's own count of one an update, for comparisons. ## Tested Windows 11, Ryzen 7 5700X3D, Radeon RX 9070 XT (Vulkan 1.4, driver 0x8001a0). A new game from an empty save: the prologue movie (154 s) at 1280x720, nothing pressed after New Game. All runs on one binary: main 88b554e + #1696 + this change. How it was read: the game's sound is dumped (`HOS_AUDIO_DUMP`); the centre channel carries the narration over the movie's own sound. Each 2 s piece of the first cap-60 run is looked for in the other run, after the two are aligned on the movie's sound. A narration line found at another time shows as a lag (negative: earlier). The first rumble value is the number of the first non-zero value the game sends; it sends about 200 a second. | | runs | narration lines against the first cap-60 run | first rumble value | game clock | the movie plays to its last picture | |---|---|---|---|---|---| | cap 60 (default) | 2 | the reference; the second run 0.00 to +0.82 s | #16186, #16258 | 1.002-1.006 at 60 fps | yes | | cap 120, this PR | 2 | -0.02 to +0.48 s; 0.00 to +0.93 s | #16104, #16252 | 1.000 at 120 fps | yes | | cap 120, `HOS_FPS_MOVIE=0` (as main is) | 2 | 0.2, 4.4, 8.7, 14.8, 17.0, 21.4 and 24.6 s early; 0.2, 4.4, 8.7, 14.6, 16.8, 20.5 and 23.7 s early | #9964, #10000 (about 31 s early) | 1.000 at 120 fps | yes | - The six runs above were made before the review's changes to the host function (the table and the atomic access; for one movie the arithmetic is the same). One more pair on the final code, same base: narration lines +0.00 to +0.60 s and rumble value #16160 with this PR; 0.2 to 24.4 s early and #9868 with `HOS_FPS_MOVIE=0`. No fatal, device-lost or validation line in any of the eight logs. - Steps above 1 (review): the same binary slowed to 36 fps with `HOS_FRAME_SLEEP_MS=25`, one run each. Under a cap of 120 the count's step is 1 or 2 at every update: all narration pieces found, +0.00 to +0.83 s, first rumble value #16218. Under a cap of 60 the game's own count runs at 36 a second: the lines come 4.1, 9.7, 17.7 and 20.6 s late and the rumble at #24508 (about 41 s late). So no cue is lost to a step of 2, and holding the step at 1 would bring that lateness to caps above 60. - Long frames in the movie's window (16 to 168 s), every arm: 2 to 5 frames over 100 ms a run, the worst 281 and 511 ms at cap 60, 471 and 504 ms with this PR, 753 and 274 ms with `HOS_FPS_MOVIE=0`. Most are in the movie's first seconds. This change neither causes nor cures them. - `tests/test_movie_frame_count.py` (it registers itself for `tests/run_all.py`): the patch on a synthetic function of the lifted shape (applied once, a second run, the same lines in another function, three other shapes left alone, the build's warning), and `hos_movie_frames` cut from `host/nn_vi.c` and run at 60, 120, 240 and 40 fps, with two movies at once, with a movie that plays while others come and go, and with 9 to 377 players for 16 places. Negative controls: it fails on main's `oot_patches.py` and on main's `nn_vi.c`, each on its own, and the game's own step of one an update fails the same 120 fps checks. Passes with gcc, MSYS2 clang and MSVC-target clang. - The build's log confirms the place is found in the supported game version (`the movie's frame count by the frames' time in code_046.c`). - `python -I -B tests/run_all.py` on this branch (main 1392ea3, Windows, MSYS2 gcc and g++): 272 passed, 6 failed, 43 skipped. The 6 are `nx2/test_nup_out_of_space.py`, `nx2/test_nup_paths.py`, `tests/test_fpx_sticky.py`, `tests/test_precompile_compute_guard.py`, `tests/test_pipeline_play_cache.py` and `tests/test_wayland_libdecor_hint.py`; all six also failed on this PC on main without this change (checked at 9edb3a5 and 51de681: a path form and headers this PC's compilers do not find). `host/nn_vi.c` and the other `host/*.c` syntax-compile as whole files (not `nn_codec.c` and `nn_video.c`: my check has no Opus and FFmpeg headers). - Rebased since on main 117eae7, which repairs the two source checks that failed in this PR's first CI run (`nx2/test_draw_parameters.py`, `tests/test_antilag.py`; they failed on main 1392ea3 too). The new test, `test_frame_rate_cap_step.py` and `test_mark_groups.py` pass on the rebased commit. ## Not covered - Only the prologue was measured. The other cutscenes that show a movie use the same player and count, but I did not run them. - #1726 (Link held in a jump in Saria's dialogue after Gohma) is not reproduced: I have no save there. The game has movies for Kokiri events, so a movie cutscene ending early is a possible cause, no more than that. - Game logic that counts frames elsewhere (the README names Gohma running into the wall) is not touched. In-engine cutscenes were not the problem: the 95 s in-engine scene after the prologue has its sound within +1.3 to -2.2 s between cap 60 and cap 120 on main without this change. - At a cap of 60 under 60 fps the cues are late, as before this change (measured above: the count there is one an update). Giving the count the frames' time at a cap of 60 too would fix that; it is not in this PR. - The count follows the game's time, as it does at a cap of 60. The game does not step through a long stall or a rate under 15 fps, while the movie's picture skips ahead, so after such a stall the cues are late by its length, at any cap. - Linux was not run (the tests only). The measurement binary is not on current main: main's shader sources have changed since my prepared caches were made. The branch is rebased on current main and its tests rerun there. - Left as it is: #1612's lifted step hook still reads the frame time as a plain volatile; its text is matched exactly by the pass that takes the hook out again. ## Review gpt-6.1-sol, five rounds on the source. It found an unsynchronised read of the frame time, a racy first-call initialisation, counts that stood still with more players than table places (twice: first with a start of 0, then with a fixed step between starts for one player in 233), and a playing movie losing its place to newcomers. All are fixed, each with a test that fails on the version before. Last round: none found. Part of #1742 (the narration and the other cues of cutscenes that show a movie; Gohma and #1726 stay open there). Related: #1726, #1612, #1805. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hckrman101 (Migrated from github.com) reviewed 2026-11-10 19:39:21 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, this is a nicely targeted fix: stepping the movie player's count by the same frame time #1612 already gives the game clock is the right lever, and the measurements (narration and first rumble back within run-to-run noise of cap 60, and HOS_FPS_MOVIE=0 reproducing the drift) are convincing. At a 60 cap hos_frame_seconds_bits is 0, so it returns 1 straight away and the lifted code behaves as before. Detection doesn't hang on a fragile flag either: the hook is just the player's own per-update increment, so pausing, skipping or stopping a movie only changes whether it gets called. The relaxed atomics on the bits and the SRWLOCK around the table are fine (SRWLOCK is already used in this file, so the Linux shim covers it). The test runs the real hos_movie_frames and the real patch function, and declares RUN_ALL_ARGS.

Two things before merge:

  1. Steps above 1 (inline). At a cap above 60, a frame that runs slower than 60 fps gives a step of 2 to 4, because realtime_step clamps at 4/60 and not at 1/60. Then whole values of the count get skipped. Any cue the cutscene fires on frame == N rather than on a range would be lost for good, not just late. In the N64 OoT decomp a lot of the one-shot cutscene commands (rumble, misc, destination/end) test curFrame == startFrame. I haven't checked this game's timeline code, but it's likely the same pattern. You saw 470 to 750 ms frames in the movie's first seconds, so this can happen in practice. Capping n at 1 and dropping the excess (the remainder below 1 stays carried) would give the same behaviour as a 60 cap during a slowdown, where cues are late but every one fires. If you've confirmed the timeline only uses ranges, a line saying so in the comment would settle it.

  2. Fixes #1742 would close the issue with Gohma still open. The issue now also covers Gohma running into the wall at 100 fps, and #1726 was folded into it. The PR says both of those are untouched. Could you change it to Part of #1742 (or split Gohma into its own issue) so it doesn't get closed automatically?

Nit: the PR body still ends with an AI attribution line. Fine for the description, but please keep it out of the commit message on squash.

CI hasn't run on a7cc747 yet (all checks pending, probably waiting on approval for a fork workflow run). Once it runs, failures that also show up on main (the present_commit/present_defer_into link error and the known Python checks) aren't from this PR.

Thanks, this is a nicely targeted fix: stepping the movie player's count by the same frame time #1612 already gives the game clock is the right lever, and the measurements (narration and first rumble back within run-to-run noise of cap 60, and `HOS_FPS_MOVIE=0` reproducing the drift) are convincing. At a 60 cap `hos_frame_seconds_bits` is 0, so it returns 1 straight away and the lifted code behaves as before. Detection doesn't hang on a fragile flag either: the hook is just the player's own per-update increment, so pausing, skipping or stopping a movie only changes whether it gets called. The relaxed atomics on the bits and the SRWLOCK around the table are fine (SRWLOCK is already used in this file, so the Linux shim covers it). The test runs the real `hos_movie_frames` and the real patch function, and declares `RUN_ALL_ARGS`. Two things before merge: 1. **Steps above 1 (inline).** At a cap above 60, a frame that runs slower than 60 fps gives a step of 2 to 4, because `realtime_step` clamps at 4/60 and not at 1/60. Then whole values of the count get skipped. Any cue the cutscene fires on `frame == N` rather than on a range would be lost for good, not just late. In the N64 OoT decomp a lot of the one-shot cutscene commands (rumble, misc, destination/end) test `curFrame == startFrame`. I haven't checked this game's timeline code, but it's likely the same pattern. You saw 470 to 750 ms frames in the movie's first seconds, so this can happen in practice. Capping `n` at 1 and dropping the excess (the remainder below 1 stays carried) would give the same behaviour as a 60 cap during a slowdown, where cues are late but every one fires. If you've confirmed the timeline only uses ranges, a line saying so in the comment would settle it. 2. **`Fixes #1742` would close the issue with Gohma still open.** The issue now also covers Gohma running into the wall at 100 fps, and #1726 was folded into it. The PR says both of those are untouched. Could you change it to `Part of #1742` (or split Gohma into its own issue) so it doesn't get closed automatically? Nit: the PR body still ends with an AI attribution line. Fine for the description, but please keep it out of the commit message on squash. CI hasn't run on a7cc747 yet (all checks pending, probably waiting on approval for a fork workflow run). Once it runs, failures that also show up on main (the `present_commit`/`present_defer_into` link error and the known Python checks) aren't from this PR.
@ -227,0 +265,4 @@
}
carry[i].used = ++calls;
carry[i].part += (double)secs * 60.0;
n = (uint32_t)carry[i].part;
hckrman101 (Migrated from github.com) commented 2026-11-10 19:39:21 +00:00

realtime_step clamps the frame time at 4/60, so below 60 fps at a cap above 60 this can be 2 to 4, and the count skips values. A cue tested with frame == start would then never fire, where at cap 60 it just fires late. Keeping the carry[i].part -= n; line as it is and adding if (n > 1) n = 1; after it (so only the fraction is carried and the excess is dropped) would keep every value hit during a stall and still track the clock at 60+ fps, where n is 0 or 1 anyway.

`realtime_step` clamps the frame time at 4/60, so below 60 fps at a cap above 60 this can be 2 to 4, and the count skips values. A cue tested with `frame == start` would then never fire, where at cap 60 it just fires late. Keeping the `carry[i].part -= n;` line as it is and adding `if (n > 1) n = 1;` after it (so only the fraction is carried and the excess is dropped) would keep every value hit during a stall and still track the clock at 60+ fps, where `n` is 0 or 1 anyway.
klimovich008 (Migrated from github.com) reviewed 2026-11-10 19:57:17 +00:00
@ -227,0 +265,4 @@
}
carry[i].used = ++calls;
carry[i].part += (double)secs * 60.0;
n = (uint32_t)carry[i].part;
klimovich008 (Migrated from github.com) commented 2026-11-10 19:57:17 +00:00

Kept the step as it is and added the comment you offered (244a71e), because the timeline fires on ranges: f_8124cfc0 walks a list sorted by time and fires every event between its last frame and the new one, and the in-engine timelines run the same code with a rate times the time step. Measured on the same binary slowed to 36 fps (HOS_FRAME_SLEEP_MS=25, one run each): under a cap of 120 the step is 1 or 2 at every update and every narration line is found, +0.00 to +0.83 s, with the first rumble value at #16218 (cap 60 at full rate: #16186 and #16258); under a cap of 60 the game's own one an update brings the lines 4.1 to 20.6 s late and the rumble about 41 s late, so a step held at 1 would bring that lateness to caps above 60. The test's 40 fps check fails if the step is held at 1. On your other points: the description now says "Part of #1742", and the commit message carries no attribution line beyond the co-author trailer.

🤖 Addressed by Claude Code

Kept the step as it is and added the comment you offered (244a71e), because the timeline fires on ranges: `f_8124cfc0` walks a list sorted by time and fires every event between its last frame and the new one, and the in-engine timelines run the same code with a rate times the time step. Measured on the same binary slowed to 36 fps (`HOS_FRAME_SLEEP_MS=25`, one run each): under a cap of 120 the step is 1 or 2 at every update and every narration line is found, +0.00 to +0.83 s, with the first rumble value at #16218 (cap 60 at full rate: #16186 and #16258); under a cap of 60 the game's own one an update brings the lines 4.1 to 20.6 s late and the rumble about 41 s late, so a step held at 1 would bring that lateness to caps above 60. The test's 40 fps check fails if the step is held at 1. On your other points: the description now says "Part of #1742", and the commit message carries no attribution line beyond the co-author trailer. _🤖 Addressed by [Claude Code](https://claude.com/claude-code)_
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 20:01:11 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks for the follow-up. Everything from my last review is settled:

  • Steps above 1: you're right, and I checked it locally rather than taking it on trust. The movie cutscene hands the player's frame count to the timeline as the new time, and the timeline then fires every event between the previous time and the new one (events with a length get a range-overlap check). Nothing tests frame == start, so a step of 2 to 4 skips no cue. Your 36 fps measurement agrees, and the new comment explains it well. Holding the step at 1 would just add lag, as you said.
  • Issue link: "Part of #1742" is good. Gohma and #1726 stay open.
  • Nit, still there: the PR body still ends with the generated-by line, and both commits have a Co-Authored-By trailer. Whoever squashes should edit those out of the squash message.

The only change since a7cc747 is a comment. CI on 244a71e is still running; every check passed on a7cc747, so I don't expect anything new. Approving.

Thanks for the follow-up. Everything from my last review is settled: - **Steps above 1:** you're right, and I checked it locally rather than taking it on trust. The movie cutscene hands the player's frame count to the timeline as the new time, and the timeline then fires every event between the previous time and the new one (events with a length get a range-overlap check). Nothing tests `frame == start`, so a step of 2 to 4 skips no cue. Your 36 fps measurement agrees, and the new comment explains it well. Holding the step at 1 would just add lag, as you said. - **Issue link:** "Part of #1742" is good. Gohma and #1726 stay open. - **Nit, still there:** the PR body still ends with the generated-by line, and both commits have a `Co-Authored-By` trailer. Whoever squashes should edit those out of the squash message. The only change since a7cc747 is a comment. CI on 244a71e is still running; every check passed on a7cc747, so I don't expect anything new. Approving.
Commenting is not possible because the repository is archived.
No description provided.