mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Run the water and floor surface simulations at their own 60 steps a second above 60 fps on 1.1.1.
The physics' two water managers and the game's interactive floor simulation (the Deku Tree's ground webs) step their surfaces in fixed steps of 1/60 s, as many whole steps as the frame's time holds, and drop the fraction, so every frame shorter than 1/60 s ran none and the surfaces stood still above 60 fps. The new set owater (water_steps, on by default) calls the page between the division and the game's cut, which keeps the fraction per object; at 60 fps the count is the game's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -462,12 +462,14 @@ and runs each step as a start of the game on a profile it makes: the export, an
|
||||
exported, its backup and swap at the next start, the zips it refuses and Cancel. No game files.
|
||||
|
||||
A change to the step Ocarina is given each frame (`runtime/frame_rate.c` `ocarina_step`), or to the whole numbers the
|
||||
game's counters get out of it (`whole_take`, `frame_rate_day_host`, `frame_rate_water_host`), passes
|
||||
game's counters get out of it (`whole_take`, `frame_rate_day_host`, `frame_rate_water_host`, `frame_rate_sim_host`), passes
|
||||
`python tests\frame_step_check.py`, which builds `frame_rate.c` with the rest of the runtime stubbed and feeds it
|
||||
display times: a held 60 fps limit gives exactly one frame every frame, a 240 fps limit never less than a quarter
|
||||
frame, uneven frame times one steady step, and game time follows the clock. It calls the two hooks as their patch
|
||||
sites do: at 144 fps each of 64 water objects gets its ripple steps and the day its seconds, and a step of one frame
|
||||
gives what the game computes. No game files.
|
||||
sites do: at 144 fps each of 64 water objects gets its ripple steps and the day its seconds, at 120, 144 and 240 fps
|
||||
the water managers and floors of the set `owater` get 60 fixed steps a second with only s0 changed, and a step of one
|
||||
frame gives what the game computes. It also checks that each of those sites in `recomp/patches-v111.txt` ends in one
|
||||
`bl` to its page slot. No game files.
|
||||
|
||||
A change to how the mixer applies volumes (`runtime/audio_mixer.c` `accumulate_ramp`, the voice and splitter paths
|
||||
of `audio_mixer_render`) or to what it owes the device after a stall (`ring_pull`, `audio_mixer_owed`) passes
|
||||
|
||||
@@ -48,6 +48,10 @@
|
||||
# rounded that step down to nothing: the window stayed until the area was left and its counting sound went on. 1 lets
|
||||
# it count at least one a frame; 0 is the game's own count.
|
||||
# pickup_count = 1
|
||||
# Water surfaces and the springy floors (the Deku Tree's webs) are simulated in fixed steps of 1/60 s, as many as the
|
||||
# frame's time holds, and the game dropped the rest: above 60 fps every frame ran none, so they stood still under Link.
|
||||
# 1 keeps the rest for the next frame, 60 steps a second at any frame rate; 0 is the game's own count. Read at start.
|
||||
# water_steps = 1
|
||||
# A movie's narration and fades follow the picture on screen: 1 counts their time from the shown picture's
|
||||
# timestamp, so they stay with it at any frame rate (with movie_fps = 0 too) and wait when the picture stalls; 0 counts
|
||||
# the game's redraws, as before, which keeps them with the music when the picture stalls. Read at start; needs game
|
||||
|
||||
@@ -79,6 +79,31 @@ site osolver main 38afd10 bd440d02 1e2a1002 # the same three words in the seco
|
||||
site osolver main 38afd1c 1f010800 1f218800
|
||||
site osolver main 38afd20 1e380001 1e280001
|
||||
|
||||
# Fixed steps of the height-field surfaces: the physics' two water managers, phive::InteractiveWaterMgr (its update
|
||||
# main+0x3875f20, the vtable slot at main+0x4c32f90) and phive::ResponsiveWaterMgr (main+0x3882660, the slot at
|
||||
# main+0x4c33a88; the names are those of their postUpdateWorld profiler scopes, and their bodies are named
|
||||
# InteractiveWater_* and ResponsiveWater_* WaterBody, IceBody and SandBody), and the game's interactive floor
|
||||
# simulation built on the same base (main+0x632cb0, the slot at main+0x4c338e0; its object is made at main+0x990040 by
|
||||
# the code that sets up the InteractiveFloorBody, main+0x98fb70; the Deku Tree's ground webs are such a floor, the
|
||||
# "SimulationCarpet" of DgnObj_Spiderweb_Ground's InteractiveFloorControllerExtensionParam, 24 x 24 points) simulate
|
||||
# their surfaces in fixed steps of 1/60 s. Each update takes the frame's seconds (s0, kept in s8), divides them by 1/60
|
||||
# (main+0x3876014, main+0x38827f8, main+0x632cfc) and cuts the quotient to a whole number of steps, n = trunc(seconds *
|
||||
# 60) (fcvtzs into w8, w8 and w22); it keeps n, and min(max(n, 0), 3) as the count of steps it queues, each with a step
|
||||
# of 1/60 s (main+0x38764d8, main+0x3882cb0, main+0x632d40), and skips the whole simulation while n is under 1
|
||||
# (main+0x3876230, main+0x38829e8, main+0x632f20). Nothing keeps the fraction: at 60 fps every update runs one step, at
|
||||
# 30 two, and every update shorter than 1/60 s none, so above 60 fps those surfaces stood still whatever pushed into
|
||||
# them, a web under Link among them. After the division each site calls the page's +0xf20
|
||||
# (runtime/frame_rate.c frame_rate_sim_host): s0 becomes the whole steps in the quotient plus a remainder kept for the
|
||||
# object (x19, the function's x0 at all three), as a float, and the game's own cut and clamp follow. So the surfaces
|
||||
# run 60 steps a second at any frame rate, and an update of exactly 1/60 s gives what the game computes. The host
|
||||
# writes only s0's low lane, which the division wrote; x30 is the function's own, saved by its prologue
|
||||
# (main+0x3875f2c, main+0x388266c, main+0x632cbc) and read only by its return after the epilogue reloads it; nothing
|
||||
# reads or writes the flags.
|
||||
set owater NX2NAVI_WATER_STEPS 1
|
||||
site owater main 3876014 1e201900 1e201900 9458abc3 # fdiv s0, s8, s0 -> fdiv s0, s8, s0; bl page+0xf20 (InteractiveWaterMgr)
|
||||
site owater main 38827f8 1e201900 1e201900 945879ca # the same at ResponsiveWaterMgr's
|
||||
site owater main 632cfc 1e201900 1e201900 9521b889 # the same at the interactive floors'
|
||||
|
||||
# Movie-synchronised event timelines: the movie layer advances its counter at +760 on each draw of a ready
|
||||
# picture. main+0x3b3ec10 reads that counter for the event dispatcher, so rendering a picture twice also
|
||||
# advances voices and overlays twice. Both ready paths reach these sites only after main+0x3b5ce80 checks
|
||||
|
||||
@@ -34,7 +34,7 @@ static int save_ok = 1; // the last cfg_save_to wrote (o
|
||||
// Every name the runtime reads (and the patch sets in generated/), without the prefix. A file key outside this list is
|
||||
// logged as unknown. A new variable goes here too, or its ini key logs a harmless "unknown key" line.
|
||||
static const char *const known[] = {
|
||||
"CORES", "TM_DISPATCH", "NVN2_CS_TABLE", "SKIP_SHADERS", "MOVIE_DEBAND", "MOVIE_FPS", "MOVIE_SKIP", "MOVIE_OVERLAY", "MOVIES", "NPLN", "NVN2_PROGRAMS", "NVN_TRAP", "OCARINA_REALTIME", "ONESHOT_BUDGET", "ONESHOT_WAIT_MS", "OCARINA_STEP", "PHYSICS_STEPS", "PICKUP_COUNT", "MOVIE_PTS", "STEP_STEADY", "VI_HZ", "VI_VRR", "TIME_DUMP", "SHADER_DUMP", "SHOT_AFTER", "SHOT_DIR", "SHOT_EVERY", // Switch 2 port: movie skip (romfs.c), NVN2 proc traps (nvn_procs.c)
|
||||
"CORES", "TM_DISPATCH", "NVN2_CS_TABLE", "SKIP_SHADERS", "MOVIE_DEBAND", "MOVIE_FPS", "MOVIE_SKIP", "MOVIE_OVERLAY", "MOVIES", "NPLN", "NVN2_PROGRAMS", "NVN_TRAP", "OCARINA_REALTIME", "ONESHOT_BUDGET", "ONESHOT_WAIT_MS", "OCARINA_STEP", "PHYSICS_STEPS", "PICKUP_COUNT", "WATER_STEPS", "MOVIE_PTS", "STEP_STEADY", "VI_HZ", "VI_VRR", "TIME_DUMP", "SHADER_DUMP", "SHOT_AFTER", "SHOT_DIR", "SHOT_EVERY", // Switch 2 port: movie skip (romfs.c), NVN2 proc traps (nvn_procs.c)
|
||||
"AA", "AA_LOG", "AB", "AMIIBO_DIR", "ANISO", "APP_POOL", "ASPECT", "ASPECT_PATCH", "ASSUME_FOCUS", "ASTC", "ASTC_BC", "ASTC_TIME", "ASYNC_PREEMPT", "AUDIO", "AUDIO_CATCHUP", "AUDIO_CHANNELS", "AUDIO_DEVICE", "AUDIO_GAME_PRIO", "AUDIO_OFF", "AUDIO_PREFILL_MS", "AUDIO_RAMP", "AUDIO_STATS",
|
||||
"BARRIER_GAME", "MESH_AHEAD", "MESH_FALLBACK", "MESH_FALLBACK_MB", "MESH_FALLBACK_WORKERS", "PROBE_MESHFB_DIR",
|
||||
"BACKGROUND_WINDOW", "BIND_A", "BIND_B", "BIND_DOWN", "BIND_L", "BIND_LCLICK", "BIND_LEFT", "BIND_LSTICK_DOWN", "BIND_LSTICK_LEFT",
|
||||
@@ -102,7 +102,8 @@ static const struct { const char *key, *section; } sections[] = {
|
||||
{"GUEST_OPT", "performance"}, {"PASS_TIMES", "diagnostics"}, {"DUMP_KEY", "diagnostics"},
|
||||
{"FPS_LIMIT", "frame rate"}, {"FPS60", "frame rate"}, {"RTSS", "frame rate"},
|
||||
{"REALTIME", "frame rate"}, {"CLOTH_REALTIME", "frame rate"}, {"HIGHFPS_FIX", "frame rate"},
|
||||
{"PHYSICS_STEPS", "frame rate"}, {"PICKUP_COUNT", "frame rate"}, {"MOVIE_PTS", "frame rate"}, {"STEP_STEADY", "frame rate"},
|
||||
{"PHYSICS_STEPS", "frame rate"}, {"PICKUP_COUNT", "frame rate"}, {"WATER_STEPS", "frame rate"},
|
||||
{"MOVIE_PTS", "frame rate"}, {"STEP_STEADY", "frame rate"},
|
||||
{"FULLSCREEN", "display"}, {"WINDOW_SIZE", "display"}, {"PERF_OVERLAY", "display"}, {"VK_PRESENT", "display"},
|
||||
{"GPU_INDEX", "display"}, {"MONITOR", "display"}, {"OUTPUT_RES", "display"}, {"GAME_ASPECT", "display"}, {"GAME_RES", "display"}, {"RENDER_SCALE", "display"}, {"UPSCALER", "display"},
|
||||
{"SHARPNESS", "display"}, {"STANDIN_FILTER", "display"}, {"SCENE_SHARE", "display"}, {"ASPECT", "display"}, {"FOV", "display"}, {"ANISO", "display"},
|
||||
@@ -242,6 +243,8 @@ static const CfgMeta metas[] = {
|
||||
{"REALTIME", "1", "0|1", RESTART}, {"CLOTH_REALTIME", "1", "0|1", RESTART},
|
||||
{"MODS_REBUILD", "auto", "auto|off", RESTART},
|
||||
{"HIGHFPS_FIX", "1", "0|1", RESTART},
|
||||
// the set owater of patches-v111.txt (frame_rate.c frame_rate_sim_host): water and floor surfaces' fixed steps
|
||||
{"WATER_STEPS", "1", "0|1", RESTART},
|
||||
{"LANG", "", "ja|en-US|fr|de|it|es|zh-CN|ko|nl|pt|ru|zh-TW|en-GB|fr-CA|es-419|zh-Hans|zh-Hant|pt-BR|pl", RESTART},
|
||||
{"OPERATION_MODE", "docked", "docked|handheld", LIVE},
|
||||
{"TEXT_SPEED", "normal", "normal|fast|instant", LIVE}, // text_speed.c: the message window's typing
|
||||
|
||||
@@ -488,7 +488,8 @@ static double ocarina_step(double dt) {
|
||||
// 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.
|
||||
// The water's ripples keep their remainders per water object in a table of their own (frame_rate_water_host).
|
||||
// The water's ripples keep their remainders per water object in a table of their own (frame_rate_water_host), and so do
|
||||
// the fixed steps of the water and floor surfaces (frame_rate_sim_host).
|
||||
#define WHOLE_SLOTS 8
|
||||
#define WATER_SLOTS 64
|
||||
typedef struct { uint64_t at; float rem; uint64_t used; } WholeSlot; // used: whole_calls at the slot's last use
|
||||
@@ -573,6 +574,43 @@ void frame_rate_water_host(Cpu *c) {
|
||||
}
|
||||
}
|
||||
|
||||
// The fixed steps of the physics' height-field surfaces (patches-v111.txt owater): the water managers (main+0x3875f20,
|
||||
// main+0x3882660) and the interactive floors' simulation (main+0x632cb0, the spider webs' sinking floor). Each update
|
||||
// divides its seconds by 1/60 s into s0 and cuts that to the number of steps of exactly 1/60 s it runs (at most 3),
|
||||
// dropping the fraction, so every frame shorter than 1/60 s ran none. The site calls this between the division and
|
||||
// the game's own cut: s0 becomes the whole steps in the quotient plus the remainder kept for the object in x19, as a
|
||||
// float the cut keeps exactly, and only s0 changes (its upper lanes stay zero, as the division left them). A quotient
|
||||
// that is not positive, or too large for a count, stays for the cut. The remainders are in a table of their own: an
|
||||
// object that shares slots with the counters above would lose its fraction to them.
|
||||
// NX2NAVI_TRACE=perf: every 10 s, the steps the objects were given beside those the cut alone would have given.
|
||||
#define SIM_SLOTS 8
|
||||
static WholeSlot sim_slot[SIM_SLOTS];
|
||||
|
||||
void frame_rate_sim_host(Cpu *c) {
|
||||
uint32_t bits = (uint32_t)c->v[0];
|
||||
float steps;
|
||||
memcpy(&steps, &bits, 4);
|
||||
uint32_t whole = whole_take(sim_slot, SIM_SLOTS, c->x[19], steps);
|
||||
if (steps > 0.0f && steps < 2147483648.0f) { // whole_take's own range: outside it the game's cut stands
|
||||
float f = (float)whole;
|
||||
memcpy(&bits, &f, 4);
|
||||
c->v[0] = bits;
|
||||
}
|
||||
if (rt_trace & RT_TRACE_PERF) {
|
||||
static uint64_t t0, asked, given, cut;
|
||||
uint64_t now = plat_now_ns(), n = 0, g = 0, k = 0, t = 0;
|
||||
plat_mutex_lock(&whole_lock);
|
||||
asked++, given += whole, cut += cut_fcvtzs(steps);
|
||||
if (!t0) t0 = now;
|
||||
if (now - t0 >= 10000000000ull) n = asked, g = given, k = cut, t = now - t0, t0 = now, asked = given = cut = 0;
|
||||
plat_mutex_unlock(&whole_lock);
|
||||
if (n)
|
||||
rt_log("frame rate: the water and floor surfaces got %llu fixed steps in %.1f s, asked %llu times (cut to "
|
||||
"whole steps each time, as the game cuts them, %llu)", (unsigned long long)g, t / 1e9,
|
||||
(unsigned long long)n, (unsigned long long)k);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -409,6 +409,7 @@ double frame_step_limit_hz(void); // the frame rate the
|
||||
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)
|
||||
void frame_rate_water_host(Cpu *c); // the water's ripple steps a frame (set ostep, page +0xf50)
|
||||
void frame_rate_sim_host(Cpu *c); // water and floor surfaces' fixed steps (set owater, page +0xf20)
|
||||
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,12 +32,13 @@ 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 and the water renderer's site
|
||||
// its +0xf50 (frame_rate.c).
|
||||
// The time of day's site of the set ostep (patches-v111.txt) calls the page's +0xf60, the water renderer's site its
|
||||
// +0xf50, and the surfaces' fixed-step sites of the set owater its +0xf20 (frame_rate.c).
|
||||
__attribute__((constructor)) static void day_register(void) {
|
||||
if (!RT_V111) return;
|
||||
rt_register_host(RT_PATCH_PAGE + 0xf60, frame_rate_day_host);
|
||||
rt_register_host(RT_PATCH_PAGE + 0xf50, frame_rate_water_host);
|
||||
rt_register_host(RT_PATCH_PAGE + 0xf20, frame_rate_sim_host);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// a load (no update of Link within GAMEPLAY_POLLS polls) start nothing: B closes the pause menu, and that press is the
|
||||
// menu's. Under the game's Swap Jump Controls B is the jump, which a B sprint button keeps from the game while Link
|
||||
// moves: L3 is the one to use then.
|
||||
// Patch page slots: the set's sites call +0xf40 and +0xf30; +0xf50 to +0xff0 belong to other sets (runtime/patches.c,
|
||||
// mouse_direct.c, ov_zstd.c). A slot registered twice ends the start (dispatch.c rt_register_host).
|
||||
// Patch page slots: the set's sites call +0xf40 and +0xf30; +0xf20 and +0xf50 to +0xff0 belong to other sets
|
||||
// (runtime/patches.c, mouse_direct.c, ov_zstd.c). A slot registered twice ends the start (dispatch.c rt_register_host).
|
||||
//
|
||||
// Link's state: the id at [[[[Link +0x18] +0x270] +0x188] +0x1c0] +0x28 (+0x4c when that reads 0x11a, a state that
|
||||
// wraps another), the same field main+0x4aed50 compares with 1 (Walk) before the game starts a sprint.
|
||||
|
||||
@@ -87,7 +87,25 @@ static uint32_t day(uint64_t object, float secs) { // frame_rate_day_host: x19
|
||||
frame_rate_day_host(&cpu);
|
||||
return (uint32_t)cpu.x[8];
|
||||
}
|
||||
|
||||
// frame_rate_sim_host as the surfaces' sites call it (set owater): after the site's division, the update's seconds over
|
||||
// 1/60 s in s0 and the object in x19; then the game's own cut of s0 (fcvtzs) gives the steps. Only s0's low lane may
|
||||
// change: every general register, the other lanes and vector registers and the flags stay as they were.
|
||||
static uint32_t sim(uint64_t object, float seconds) {
|
||||
Cpu c, before;
|
||||
uint8_t *p = (uint8_t *)&c;
|
||||
for (size_t i = 0; i < sizeof c; i++) p[i] = (uint8_t)(i * 37u + 11u);
|
||||
float quotient = seconds / (1.0f / 60.0f); // the site's fdiv s0, s8, s0 (the literal is 1/60 as a float)
|
||||
uint32_t bits;
|
||||
memcpy(&bits, "ient, 4);
|
||||
c.v[0] = bits, c.x[19] = object; // the division wrote s0 and zeroed the lanes above it
|
||||
before = c;
|
||||
frame_rate_sim_host(&c);
|
||||
int same = c.nzcv == before.nzcv && c.sp == before.sp && !memcmp(&c.v[1], &before.v[1], sizeof c.v - sizeof c.v[0]) &&
|
||||
c.v[0] >> 32 == 0 && !memcmp(c.x, before.x, sizeof c.x);
|
||||
CHECK(same, "the surfaces' site changed something besides s0's low lane");
|
||||
memcpy("ient, &c.v[0], 4);
|
||||
return cut_fcvtzs(quotient); // the game's fcvtzs after the site
|
||||
}
|
||||
int main(int argc, char **argv) {
|
||||
int mean = argc > 1 && !strcmp(argv[1], "mean");
|
||||
if (mean) steady = "0";
|
||||
@@ -221,6 +239,32 @@ int main(int argc, char **argv) {
|
||||
for (int i = 0; i < 30; i++) CHECK(water(pond, 2.0f) == 2, "two frames, call %d", i);
|
||||
CHECK(water(pond, 0.5f) == 1, "the half frame kept through the whole ones");
|
||||
}
|
||||
// The water managers and the floors (set owater): at 120, 144 and 240 fps each runs 60 fixed steps a second (the cut
|
||||
// gave none above 60), beside 64 water objects; at 60 and 30 fps one and two steps an update, as the game counts them.
|
||||
{
|
||||
const uint64_t interactive = 0x7400000000ull, responsive = 0x7400001000ull, floor = 0x7400002000ull,
|
||||
first = 0x7500000000ull;
|
||||
const float rates[] = {144.0f, 240.0f, 120.0f};
|
||||
for (int r = 0; r < 3; r++) {
|
||||
uint32_t a = 0, b = 0, c = 0, w = 0;
|
||||
int frames = (int)(rates[r] * 10);
|
||||
for (int f = 0; f < frames; f++) {
|
||||
a += sim(interactive, 1.0f / rates[r]);
|
||||
for (int k = 0; k < 64; k++) w += water(first + 0x1000 * (uint64_t)k, 60.0f / rates[r]);
|
||||
b += sim(responsive, 1.0f / rates[r]);
|
||||
c += sim(floor, 1.0f / rates[r]);
|
||||
}
|
||||
CHECK(a >= 599 && a <= 600 && b >= 599 && b <= 600 && c >= 599 && c <= 600,
|
||||
"%.0f fps: the managers and the floor ran %u, %u and %u steps in 10 s",
|
||||
rates[r], a, b, c);
|
||||
CHECK(w >= 64 * 599, "%.0f fps: the water objects beside the managers ran %u steps", rates[r], w);
|
||||
}
|
||||
for (int i = 0; i < 120; i++) CHECK(sim(interactive, 1.0f / 60.0f) == 1, "60 fps, update %d", i);
|
||||
for (int i = 0; i < 60; i++) CHECK(sim(responsive, 1.0f / 30.0f) == 2, "30 fps, update %d", i);
|
||||
CHECK(sim(interactive, 0.0f) == 0 && sim(interactive, -1.0f / 60.0f) == 0xffffffffu &&
|
||||
sim(interactive, NAN) == 0, "no time, negative time and NaN are cut as fcvtzs cuts");
|
||||
CHECK(sim(interactive, 1.0f / 15.0f) == 4, "four steps for a 15 fps update (the game's clamp to 3 follows)");
|
||||
}
|
||||
printf("frame_step_check: %s\n", fails ? "FAILED" : "ok");
|
||||
return fails != 0;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,16 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
||||
sys.path[:0] = [str(ROOT / 'recomp')]
|
||||
from patches import load # noqa: E402
|
||||
|
||||
PAGE = 0x4ea0000 # the patch page of 1.1.1, main+0x4ea0000
|
||||
# The sites that call frame_rate.c's hosts, as recomp/patches-v111.txt has them: set, module offset, the original word,
|
||||
# the words before the bl (the original's own, where the host comes after it), the page offset the bl reaches.
|
||||
FDIV = 0x1e201900 # fdiv s0, s8, s0
|
||||
HOST_SITES = [('ostep', 0x3cdfc0, 0x1e380008, [], 0xf60), ('ostep', 0x475f8, 0x1e380009, [], 0xf50),
|
||||
('owater', 0x3876014, FDIV, [FDIV], 0xf20), ('owater', 0x38827f8, FDIV, [FDIV], 0xf20),
|
||||
('owater', 0x632cfc, FDIV, [FDIV], 0xf20)]
|
||||
|
||||
|
||||
def compiler():
|
||||
@@ -27,6 +37,22 @@ LAYOUT = '''// A stand-in for the generated layout.h (tests/frame_step_check.py)
|
||||
'''
|
||||
|
||||
|
||||
def check_sites():
|
||||
"""Each host site is its listed words and one bl to its page slot, over its original word."""
|
||||
table = load(ROOT / 'recomp/patches-v111.txt', [('main', 0x7000000000, PAGE)])
|
||||
bad = 0
|
||||
for name, site, original, before, slot in HOST_SITES:
|
||||
(key, words, have), = table.sites['main'][site]
|
||||
bl = words[-1] if isinstance(words[-1], int) else 0
|
||||
target = site + (((bl & 0x3ffffff) ^ 0x2000000) - 0x2000000) * 4
|
||||
ok = (table.sets[key][0] == name and have == original and words[:-1] == before and bl >> 26 == 0x25 and
|
||||
target == PAGE + slot)
|
||||
if not ok:
|
||||
print(f'FAIL: the site main+{site:#x} of set {name} is {words} over {have:#x}')
|
||||
bad += 1
|
||||
return bad
|
||||
|
||||
|
||||
def main():
|
||||
work = ROOT / 'build' / 'test' / 'frame_step'
|
||||
work.mkdir(parents=True, exist_ok=True)
|
||||
@@ -39,7 +65,7 @@ def main():
|
||||
'-I', str(ROOT / 'runtime'), '-I', str(work),
|
||||
str(ROOT / 'tests' / 'frame_step_check.c'), '-o', str(exe), '-lm'], env=env)
|
||||
rc = subprocess.run([str(exe)], env=env).returncode
|
||||
return subprocess.run([str(exe), 'mean'], env=env).returncode or rc
|
||||
return subprocess.run([str(exe), 'mean'], env=env).returncode or rc or check_sites()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user