mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Keep the time of day at the game's pace under the real-time step, its fraction carried from frame to frame.
The day's update (main+0x3cdf70) adds trunc(2 x frames per update) game seconds a frame. With the set ostep's step, the display interval in 60 Hz frames, that sum lost its fraction every frame: 1 a frame from 61 to 119 fps, 0 above 120 (the clock stood still), and at 60 fps a step a little under one frame dropped a second now and then. Its convert now calls the patch page's +0xf60 (runtime/frame_rate.c frame_rate_day_host), which keeps the fraction per counter and hands out the whole seconds. NX2NAVI_TRACE=perf logs, every 10 s, the seconds given beside those the cut would have given. Measured in Kakariko, 10 s windows: at a 60 fps limit 998 game seconds against 881 from the cut alone; at 144 fps 1,201 against 219 to 972 (the steady 1,200 a 10 s window should have). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,6 +38,14 @@ site ostep main 6e6cc4 1e201828 bd410148 # fdiv s8, s1, s0 -> ldr s8, [x10, #
|
||||
# never differ. x9 is not read in the function and is free across the call that follows; the store keeps x8.
|
||||
site ostep main 3b4eaf4 5e21d800 @page:x9 # scvtf s0, s0 -> adrp x9, page
|
||||
site ostep main 3b4eaf8 1e201820 bd410120 # fdiv s0, s1, s0 -> ldr s0, [x9, #256]
|
||||
# The time of day: its update (main+0x3cdf70) adds trunc(2 * frames per update) game seconds a frame to the seconds of
|
||||
# the day (+0x3c of its object, carried into the day count by the code after it). With the step above below one frame
|
||||
# per update the sum was 1 from 61 to 119 fps (the day at 83% of its pace at 100 fps) and 0 above 120, where the clock
|
||||
# stood still and the sun swung back and forth around a frozen hour; at 60 fps a step a little under one frame lost a
|
||||
# second too. The cut calls the page's +0xf60 instead (runtime/frame_rate.c frame_rate_day_host): the frame's seconds
|
||||
# with the fraction kept for the next frame, so the day keeps the game's pace at any frame rate. x30 is the function's
|
||||
# own saved one, and the call at main+0x3cdfc8 sets it again.
|
||||
site ostep main 3cdfc0 1e380008 952b4be8 # fcvtzs w8, s0 -> bl page+0xf60
|
||||
|
||||
# Physics solver steps: the physics system's step setup (main+0x38aeb80, and the same code in main+0x38afc10) gives the
|
||||
# rigid body world its solver step count for the frame, trunc(frames per update * count + 0.001) with the count at
|
||||
|
||||
@@ -476,6 +476,65 @@ static double ocarina_step(double dt) {
|
||||
return step;
|
||||
}
|
||||
|
||||
// ---- whole frames out of the real-time step (the set ostep's sites that cut it to a whole number, patches-v111.txt)
|
||||
// The game adds the step, or the step times a whole number, to a few counters and cuts the sum to a whole number: at the
|
||||
// console's 60 fps the step is 1 and they count frames. With the step the display interval in 60 Hz frames, under one
|
||||
// above 60 fps, the cut loses the fraction every frame. whole_frames keeps it per counter (by its guest address) and
|
||||
// hands out the whole part, so each counter moves at the game's pace at any frame rate; at a step of exactly one it
|
||||
// gives what the game computes. A value that is not positive, or too large for a count, is cut as fcvtzs cuts it.
|
||||
#define WHOLE_SLOTS 8
|
||||
static struct { uint64_t at; float rem; uint32_t used; } whole_slot[WHOLE_SLOTS];
|
||||
static uint32_t whole_calls;
|
||||
static PlatMutex whole_lock = PLAT_MUTEX_INIT;
|
||||
|
||||
static uint32_t cut_fcvtzs(float f) { // fcvtzs w, s: toward zero, saturating, NaN 0
|
||||
if (f != f) return 0;
|
||||
if (f >= 2147483648.0f) return 0x7fffffffu;
|
||||
if (f <= -2147483648.0f) return 0x80000000u;
|
||||
return (uint32_t)(int32_t)f;
|
||||
}
|
||||
|
||||
static uint32_t whole_frames(uint64_t at, float amount) {
|
||||
if (!(amount > 0.0f) || amount >= 2147483648.0f) return cut_fcvtzs(amount);
|
||||
plat_mutex_lock(&whole_lock);
|
||||
int k = -1, oldest = 0;
|
||||
for (int i = 0; i < WHOLE_SLOTS; i++) {
|
||||
if (whole_slot[i].at == at) { k = i; break; }
|
||||
if (whole_slot[i].used < whole_slot[oldest].used) oldest = i;
|
||||
}
|
||||
if (k < 0) k = oldest, whole_slot[k].at = at, whole_slot[k].rem = 0.0f;
|
||||
whole_slot[k].used = ++whole_calls;
|
||||
float sum = whole_slot[k].rem + amount;
|
||||
uint32_t whole = cut_fcvtzs(sum);
|
||||
whole_slot[k].rem = sum - (float)whole;
|
||||
plat_mutex_unlock(&whole_lock);
|
||||
return whole;
|
||||
}
|
||||
|
||||
// The time of day (main+0x3cdf70, patches-v111.txt): s0 holds twice the step, the game seconds of this frame; w8 gets
|
||||
// the whole ones, the counter being the seconds of the day at +0x3c of the object in x19.
|
||||
// NX2NAVI_TRACE=perf: every 10 s, the game seconds the day was given beside those the cut alone would have given.
|
||||
void frame_rate_day_host(Cpu *c) {
|
||||
uint32_t bits = (uint32_t)c->v[0];
|
||||
float secs;
|
||||
memcpy(&secs, &bits, 4);
|
||||
uint32_t whole = whole_frames(c->x[19] + 0x3c, secs);
|
||||
c->x[8] = whole;
|
||||
if (rt_trace & RT_TRACE_PERF) {
|
||||
static uint64_t t0, given, cut;
|
||||
static double exact;
|
||||
uint64_t now = plat_now_ns();
|
||||
given += whole, cut += cut_fcvtzs(secs), exact += secs;
|
||||
if (!t0) t0 = now;
|
||||
if (now - t0 >= 10000000000ull) {
|
||||
rt_log("frame rate: the time of day got %llu game seconds in %.1f s (the frames' step makes %.1f; cut "
|
||||
"to whole seconds each frame, as the game cuts them, %llu)", (unsigned long long)given,
|
||||
(now - t0) / 1e9, exact, (unsigned long long)cut);
|
||||
t0 = now, given = cut = 0, exact = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The 60 Hz frames the game steps at each update where the set ostep gives them (Ocarina); 1 elsewhere, the game's own
|
||||
// nominal frame. The mouse camera divides by it (mouse_direct.c): the game multiplies camera input by this step.
|
||||
float frame_step_frames(void) {
|
||||
|
||||
@@ -393,6 +393,7 @@ int frame_limit_adjustable(void); // the frame limit set
|
||||
int frame_free_running(void); // no fps60/realtime patch set: the game steps by the clock
|
||||
double frame_step_limit_hz(void); // the frame rate the limit gives (overlay); 0 unknown
|
||||
double frame_step_game_hz(void); // the rate the game draws at under the limit; 0 unknown
|
||||
void frame_rate_day_host(Cpu *c); // the time of day's seconds a frame (set ostep, page +0xf60)
|
||||
float frame_step_frames(void); // the 60 Hz frames the game steps per update (set ostep), else 1
|
||||
void frame_step_flight(float *page, float *game, uint64_t *presents); // flight.c: the step at a host present
|
||||
// rtss_limit.c: RivaTuner Statistics Server takes the frame limit where it runs
|
||||
|
||||
@@ -32,6 +32,10 @@ uint8_t rt_patch_on[RT_PATCH_COUNT > 0 ? RT_PATCH_COUNT : 1]; // read by the g
|
||||
#ifdef RT_PATCH_PAGE
|
||||
#include "debug_tools.h"
|
||||
__attribute__((constructor)) static void freecam_register(void) { rt_register_host(RT_PATCH_PAGE + 0xf70, dbg_freecam_host); }
|
||||
// The time of day's site of the set ostep (patches-v111.txt) calls the page's +0xf60 (frame_rate.c).
|
||||
__attribute__((constructor)) static void day_register(void) {
|
||||
if (RT_V111) rt_register_host(RT_PATCH_PAGE + 0xf60, frame_rate_day_host);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RT_PATCH_COUNT
|
||||
|
||||
Reference in New Issue
Block a user