// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING). See frame_time.h. // // NFSMW times itself with mftb. The main loop (sub_823B0008) measures each // frame and hands it, in milliseconds as 16.16 fixed point, to the frame // (sub_823AFE20). That quantises it to 1/3600 s (sub_823C6500; the frame's // time for its real-time users, [0x82A39660]), advances RealTimeFrames and a // 4000 Hz tick counter at 0x82A399AC (AdvanceRealTime, 0x823C66C8), and runs // the world simulation in fixed 1/60 s steps from an accumulator over those // ticks (sub_823A2348: floor(elapsed / step) steps, the remainder kept; the // step is stored once, at boot, and tasks copy it). There is no interpolation: // a frame's world state is its steps'. // // So a frame that isn't 1/60 s long shows it. With the vblank locked to the // player's 59.9726 Hz monitor, a frame lasts 16.674 ms and every 36.6 s one // runs two steps: the car jumps, where the presenter used to drop a frame. // And at any rate a frame's measured length jitters (the main thread's // wake-up, the 250 us ticks), so while the accumulator's remainder sits near // a step boundary, frames alternate between no step and two. // // Each guest vblank is made one step instead (NFSMW_GAME_TIME=0 turns this // off). A frame is given whole vblanks of 1/60 s, as the console's 60 Hz // vblank would have given it; the stepper's elapsed time, then k steps give // or take a tick, is rounded to k. The main loop runs ahead of the display: // the game's render thread swaps (VdSwap) during the loop's next frame, and // the command processor lets each swap go at a vblank about two frames after // that, so the loop's frames drift against the vblanks (counted from one // frame's start to the next) while the display shows one frame a vblank from // the queue: a frame the loop is slow with counts 2 or 3, the next ones none. // Each is given one vblank while game time stays within a few vblanks of the // vblanks that fired (game/whole_vblanks.h; given their counts, the world // ran 2-3 steps in one displayed frame and none in the next: GitHub issue // #9). Further behind, the queue ran dry and the display waited, and the // frame gets the rest; a frame slower than the vblank (GPU-bound) gets its // count, its length rounded either way. Game time adds up to the vblanks // that fired, to within those few. It runs at the display's rate over 60 // (over 120, 180 or 240 on such a display, whose refreshes a vblank is 2-4 // of: video/vblank_lock.h): 0.044% slow on a 59.97 Hz monitor, at most 2.5% // (the lock's range). Frames // of more than 6 vblanks (a hitch, loading) keep their measured time; below // 20 fps, where they are the frame rate's own, only those over half a second // do, the rest get 6 (the stepper's own cap) and later frames give back what // that leaves owed, so game time keeps up down to 10 fps. At Frame Rate 30 // (video::GuestVblankDivider) a guest vblank comes every other refresh and is // given 2/60 s: two steps a frame, the game at its speed. #include #include "frame_time.h" #include "whole_vblanks.h" #include #include