mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Make a stutter-map episode end at its last frame that stood out once the frames after it settle into a pace.
At 40 fps or less every frame is over 25 ms and so a hitch candidate. A frame that stood out opened an episode with
its hitch flag set, every following paced candidate joined it as a consecutive frame, and the cut after
SM_EPISODE_MAX frames applied only to episodes without a hitch. So the episode grew until the frame rate rose again:
a 150 ms frame followed by 300 frames at 30 fps counted one hitch of about 10 s, and five 120 ms hitches at a steady
30 fps counted as one.
cand_decide now keeps the episode up to its last frame that stood out (frame, summed time, frame count). When more
than SM_EPISODE_MAX paced frames follow that frame, the paced frames are a change of pace and the episode ends at the
frame that stood out and is counted with its time to there. An episode without such a frame is cut as before, and
episode_close is unchanged, so the 87/91/96 ms episode of 2f640fb8 keeps its 274 ms.
tests/stutter_hitch_check.c gets both cases: 150 ms then 300 frames at 30 fps counts one hitch of 150 ms, and five
120 ms hitches at 30 fps count five of 120 ms each. Both failed before the change (0 hitches counted while the run
lasted). stutter_hitch_check and stutter_map_check pass; stutter_map.c compiles with the dev flags and with -Wall
-Wextra without new warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
// stutter in play. An area load is the game's (stutter_area) or the texture pool's (stutter_load: the pool makes an
|
||||
// area's textures, vk_exec_draw.c pool_loading), which comes without the game's when it streams the next area in.
|
||||
// Consecutive long frames are one episode, a hitch counted once with their summed time: a change of pace is a run of
|
||||
// more than SM_EPISODE_MAX long frames none of which stands out from the ones after it. Three frames of 87, 91 and
|
||||
// more than SM_EPISODE_MAX long frames none of which stands out from the ones after it, and an episode ends at its
|
||||
// last frame that stood out once more than SM_EPISODE_MAX such frames follow that one. Three frames of 87, 91 and
|
||||
// 96 ms after an area load (2026-11-13) each counted as a change of pace before, as each was followed by one as long.
|
||||
//
|
||||
// What a hitch went to comes from the frame-time summary's split (perf.c ft_frame: pipeline, translation, texture,
|
||||
@@ -110,6 +111,8 @@ static struct {
|
||||
Cand first;
|
||||
uint64_t last, total; // the last frame in it; the frames' summed intervals
|
||||
int n, hitch, open; // frames in it; one of them stood out from the frames after it (or was over the sure line)
|
||||
uint64_t hit_last, hit_total; // the episode up to its last frame that stood out: that frame, the summed intervals
|
||||
int hit_n; // and the frames to it (0: none stood out)
|
||||
} ep;
|
||||
static int pace_run; // more than SM_EPISODE_MAX long frames, none standing out: a new pace, until it ends
|
||||
static uint64_t pace_last;
|
||||
@@ -279,8 +282,12 @@ static void episode_close(uint64_t now) {
|
||||
}
|
||||
|
||||
// A candidate the SM_AFTER frames after it have been seen for: it stands out (a hitch) or the frames after it are as
|
||||
// long (paced). It joins the episode of the frame before it, or starts one. An episode of more than SM_EPISODE_MAX
|
||||
// frames none of which stood out is a change of pace: they and the paced frames right after them are left out.
|
||||
// long (paced). It joins the episode of the frame before it, or starts one. More than SM_EPISODE_MAX paced frames
|
||||
// after the episode's last frame that stood out (or from its start, when none did) are a change of pace: they and the
|
||||
// paced frames right after them are left out, and an episode with a frame that stood out ends at that frame and is
|
||||
// counted. At 40 fps or less every frame is over SM_HITCH_ANY_NS and so a candidate: without the cut at the last frame
|
||||
// that stood out, the first hitch took in every frame after it until the rate rose, and a 30 fps session counted one
|
||||
// hitch seconds long in place of its five.
|
||||
static void cand_decide(const Cand *c, uint64_t now) {
|
||||
int paced = c->d <= SM_HITCH_SURE_NS && (double)c->after_max * SM_AFTER_RATIO >= (double)c->d;
|
||||
if (pace_run) {
|
||||
@@ -291,12 +298,19 @@ static void cand_decide(const Cand *c, uint64_t now) {
|
||||
pace_run = 0;
|
||||
}
|
||||
if (ep.open && c->frame == ep.last + 1) {
|
||||
ep.last = c->frame, ep.total += c->d, ep.n++, ep.hitch |= !paced;
|
||||
if (!ep.hitch && ep.n > SM_EPISODE_MAX) paced_out += (uint64_t)ep.n, ep.open = 0, pace_run = 1, pace_last = c->frame;
|
||||
ep.last = c->frame, ep.total += c->d, ep.n++;
|
||||
if (!paced) ep.hitch = 1, ep.hit_n = ep.n, ep.hit_last = c->frame, ep.hit_total = ep.total;
|
||||
if (ep.n - ep.hit_n > SM_EPISODE_MAX) {
|
||||
paced_out += (uint64_t)(ep.n - ep.hit_n);
|
||||
if (ep.hitch) ep.n = ep.hit_n, ep.last = ep.hit_last, ep.total = ep.hit_total, episode_close(now);
|
||||
else ep.open = 0;
|
||||
pace_run = 1, pace_last = c->frame;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (ep.open) episode_close(now);
|
||||
ep.first = *c, ep.last = c->frame, ep.total = c->d, ep.n = 1, ep.hitch = !paced, ep.open = 1;
|
||||
ep.hit_n = !paced, ep.hit_last = c->frame, ep.hit_total = paced ? 0 : c->d;
|
||||
}
|
||||
|
||||
void stutter_frame(uint64_t frame, uint64_t now, uint64_t d, const uint64_t *part, const char *const *names, int n,
|
||||
|
||||
@@ -75,6 +75,22 @@ int main(void) {
|
||||
frames(1, 60.0), frames(30, 7.0);
|
||||
expect("a spike right after the pool's load (play)", areas[1].hitch, 0);
|
||||
expect("a spike right after the pool's load (load)", areas[1].load_hitch, 2);
|
||||
// At 30 fps every frame is over SM_HITCH_ANY_NS and so a candidate: a hitch followed by the steady frames ends at
|
||||
// itself, not at the end of the slow run (an episode of 301 frames, 10 s long, before)
|
||||
stutter_area("Lake Hylia");
|
||||
clock_ns += 10000000000ull;
|
||||
frames(200, 7.0);
|
||||
frames(1, 150.0), frames(300, 33.3);
|
||||
expect("150 ms then 300 frames at 30 fps", areas[2].hitch, 1);
|
||||
expect("150 ms then 300 frames at 30 fps (worst)", areas[2].worst_ns, 150000000ull);
|
||||
frames(200, 7.0); // the end of the slow run stands out from the frames after it: one more
|
||||
stutter_area("Gerudo Valley");
|
||||
clock_ns += 10000000000ull;
|
||||
frames(200, 33.3); // a steady 30 fps with a 120 ms hitch every 60 frames: each counts
|
||||
for (int k = 0; k < 5; k++) frames(1, 120.0), frames(59, 33.3);
|
||||
expect("five hitches at 30 fps", areas[3].hitch, 5);
|
||||
expect("five hitches at 30 fps (worst)", areas[3].worst_ns, 120000000ull);
|
||||
expect("five hitches at 30 fps (their time)", areas[3].cause_ns[7], 600000000ull);
|
||||
if (!logged) printf("FAIL nothing was logged\n"), fails++;
|
||||
if (fails) return 1;
|
||||
printf("stutter_hitch_check: all checks passed\n");
|
||||
|
||||
Reference in New Issue
Block a user