From 70c9935789adb794f0cdb4e4ac95e132615f7b71 Mon Sep 17 00:00:00 2001 From: chrg6 Date: Thu, 12 Nov 2026 22:49:19 -0500 Subject: [PATCH] Give the movie timestamp sites a switch of their own, say what changes in a decoder stall, and keep the movie cap's reasons. The two sites that count a movie's event frames from the shown picture's timestamp were part of the real-time step's set, so the only way back to the old count also turned the step off. They are set omovie now, NX2NAVI_MOVIE_PTS (movie_pts in nx2navi.ini), on by default. The comment says what the new count changes at the default movie cap: nothing while the decoder keeps up; in a stall the cues wait with the picture instead of going on with the soundtrack, and step by 2 or more when a picture is dropped. The counter's reader, main+0x3b3ec10, hands it out as a float frame through a virtual call; its callers were not traced, so whether a cue on a dropped picture's tick can be missed is said to be unknown, which is what the switch is for. The movie cap's comment keeps the measurements it rests on, the narration 2.4 times early and the fade at 65 s of 154 at 144 fps, with one sentence on the new sites. The new test is described in prose with what it does not cover, instead of a row in the lifter's table. Co-Authored-By: Claude Opus 5.5 --- docs/building.md | 7 ++++++- nx2navi.ini.example | 5 +++++ recomp/patches-v111.txt | 11 +++++++++-- runtime/cfg.c | 4 ++-- runtime/frame_rate.c | 15 ++++++++++----- 5 files changed, 32 insertions(+), 10 deletions(-) diff --git a/docs/building.md b/docs/building.md index a3952b4d..e59f85c3 100644 --- a/docs/building.md +++ b/docs/building.md @@ -561,6 +561,12 @@ leaves the mode's own exit code and one `exit:` line. The second is what a libra process does when it faults in its detach: an injector's Vulkan layer did so at the end of every shader preparation on a player's PC, and the launcher took each finished preparation for a failed one. No game files, no GPU. +A change to the movie timestamp sites of `recomp/patches-v111.txt` (set `omovie`) passes +`python tests\movie_timeline_check.py --cc ` (`.venv`, Unicorn): it lifts the twelve replacement words on +their own and compares them with Unicorn over repeated, skipped and rounded timestamps, registers, flags and memory. +It does not cover the sites as `gen.py` emits them inside their two functions, nor the add and store after them: +a regenerate of the game code and a compile of the chunk that holds main+0x3b5c7c4 and main+0x3b5c908 does. + A change to the memory gate of the background pipeline builds (`runtime/vk_build_gate.h`, `runtime/vk_pipeline.c` `gate_enter`) passes `python tests\build_gate_check.py []`, which builds the header alone and checks what it admits and refuses and that builds under way count through their reservations. With the executable it also runs @@ -672,7 +678,6 @@ change passes these, all run with `.venv`: | `tests\diff\func.py` | whole functions as the generator emits them, loops, jump tables and calls included (calls go to a stub that checks the registers at the call) | yes | | `tests\flags_check.py` | the function-local flag helpers of `runtime\cpu.h` against its packed ones | no | | `tests\ds3_check.py` | the DualShock 3 report decoding and output report of `runtime\input_ds3.c` | no | -| `tests\movie_timeline_check.py --cc PATH` | both 1.1.1 movie timestamp patches against Unicorn and lifted C, with repeated/skipped pictures, rounded timestamps and preserved registers/flags | no | | `tests\gpa_check.py` | the device address table of `runtime\vk_gpa.c` as a shader reads it: mapping, mirrors and twins, overlapping imports, and blocks given back and taken again while pool addresses climb (no GPU; any Python) | no | | `tests\excl_mt.py` | the exclusive monitor helpers under contention and ABA, on several threads | no | | `tests\jumptables_check.py` | `recomp/jumptables.py` against a Unicorn oracle; must report 0 disagreements, on 1.4.3 too | yes | diff --git a/nx2navi.ini.example b/nx2navi.ini.example index 937dbb7a..a9beb1a8 100644 --- a/nx2navi.ini.example +++ b/nx2navi.ini.example @@ -48,6 +48,11 @@ # 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 +# 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 +# code generated with this release. +# movie_pts = 1 # The step the game is given each frame: 1 keeps it steady (the same value from frame to frame while the frame rate # holds, exactly one 60 Hz frame at a 60 fps limit) and makes up for a late frame over the next second; 0 is the mean # of the last five frame times, which changes with every frame. diff --git a/recomp/patches-v111.txt b/recomp/patches-v111.txt index 8414acdf..79fea458 100644 --- a/recomp/patches-v111.txt +++ b/recomp/patches-v111.txt @@ -22,6 +22,7 @@ set zstd NX2NAVI_ZSTD_NATIVE 1 set osolver NX2NAVI_PHYSICS_STEPS 1 set opickup NX2NAVI_PICKUP_COUNT 1 set freecam NX2NAVI_PATCH_FREECAM 0 +set omovie NX2NAVI_MOVIE_PTS 1 # Real-time step: the framework's per-frame update (main+0x6e6be0) gives the time manager **[main+0x4c7e890] its # nominal frame through main+0x3b4e300 (+0x10 frames per update, +0x14 the inverse, +0x18 seconds, +0x1c the rate), @@ -86,8 +87,14 @@ site osolver main 38afd20 1e380001 1e280001 # x8 = layer->controller->player->surface->timestamp in ns. Clamp negative timestamps to zero and convert to # the nearest 60 Hz timeline tick (WebM timestamps can be rounded to milliseconds). The original add of one # counts the first picture as frame one. x9 is scratch on both paths, and the replacement preserves flags. -site ostep main 3b5c7c4 b942fa88 f9400a88 f9400108 f940f508 f9406108 8aa8fd08 d2800789 9b097d08 528ca009 72a3b9a9 8b090108 d37ff929 9ac90908 -site ostep main 3b5c908 b942fa88 f9400a88 f9400108 f940f508 f9406108 8aa8fd08 d2800789 9b097d08 528ca009 72a3b9a9 8b090108 d37ff929 9ac90908 +# At the movie cap (runtime/frame_rate.c movie_cap_hz, 60 fps) the two counts are the same while the decoder keeps up. +# They differ when no new picture is ready for a redraw: the old count went on, so cues kept pace with the soundtrack, +# which plays on its own; this count waits with the picture and goes on with it, a step of 2 or more when the decoder +# drops a picture. main+0x3b3ec10 hands the counter out as a float frame (scvtf) through a virtual call, and its +# readers were not traced, so whether they compare frames as ranges or exact values is not known: if exactly, a cue +# on a dropped picture's tick would be missed. NX2NAVI_MOVIE_PTS=0 (movie_pts in nx2navi.ini) gives the old count. +site omovie main 3b5c7c4 b942fa88 f9400a88 f9400108 f940f508 f9406108 8aa8fd08 d2800789 9b097d08 528ca009 72a3b9a9 8b090108 d37ff929 9ac90908 +site omovie main 3b5c908 b942fa88 f9400a88 f9400108 f940f508 f9406108 8aa8fd08 d2800789 9b097d08 528ca009 72a3b9a9 8b090108 d37ff929 9ac90908 # Pick-up window count: the window that shows a pick-up above the character (its update is main+0xa67e50) moves the # number it shows (+0x360) to the new total (+0x35c) by trunc(frames per update * max(1, difference / 50)) a frame, and diff --git a/runtime/cfg.c b/runtime/cfg.c index fdb29d38..f476a23b 100644 --- a/runtime/cfg.c +++ b/runtime/cfg.c @@ -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", "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", "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,7 @@ 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"}, {"STEP_STEADY", "frame rate"}, + {"PHYSICS_STEPS", "frame rate"}, {"PICKUP_COUNT", "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"}, {"ASPECT", "display"}, {"FOV", "display"}, {"ANISO", "display"}, diff --git a/runtime/frame_rate.c b/runtime/frame_rate.c index bdd3a205..40b607a1 100644 --- a/runtime/frame_rate.c +++ b/runtime/frame_rate.c @@ -193,11 +193,16 @@ static void frame_limit_live(void) { else rt_log("frame rate: limit at the monitor's refresh rate (60 until the window measures it)"); } -// NX2NAVI_MOVIE_FPS (default 60; 0 = off): the game's frame rate while a movie plays (ov_movie2.c ov_movie_live). -// Movie-synchronised events use the selected picture's timestamp with the 1.1.1 ostep patches enabled; redrawing -// a picture no longer advances that timeline. Keep the 60 Hz pacing policy for the 60 fps movies and for builds -// without those patches. It applies in both directions: a lower gameplay limit must not slow the movie's updates -// relative to its audio. A player can turn this pacing override off independently of the timestamp patches. +// NX2NAVI_MOVIE_FPS (default 60; 0 = off): the game's frame rate while a movie plays (ov_movie2.c ov_movie_live). A +// movie's event counts the game's updates as 60 Hz frames whatever the step: at 144 fps the intro's narration lines +// started 2.4 times too early, each over the one before, and its closing fade to black came at 65 s of the movie's +// 154, hiding the whole dream. The movies are 60 fps, so the cap shows the same pictures. It is the rate in both +// directions: under a lower limit (40 fps, or 60 shown with frame generation, which is 30 of the game's) the movie ran +// at that fraction of its speed and ended many seconds behind its sound, so the game is paced at the movie's rate +// while one plays, above the limit too. +// With set omovie of recomp/patches-v111.txt (NX2NAVI_MOVIE_PTS, on by default) the event count follows the shown +// picture's timestamp instead of the redraws, so with the cap off the cues no longer run ahead; the cap stays for the +// pacing in both directions and for builds without those sites. static int movie_cap_hz(void) { static int hz = -1; if (hz < 0) {