From 739a2ffeb63a7e5ec16a301d56fbed5a86e3e818 Mon Sep 17 00:00:00 2001 From: Grateful Forest <168700820+gratefulforest@users.noreply.github.com> Date: Mon, 29 Dec 2025 07:13:34 +1030 Subject: [PATCH] jak1: fix balance-plats at high-fps (#4069) Looking at the balance-plats, they showed an immediate visual clue, which was the stuttering of Jak's feet on their surface. This is precisely what happens when you don't include sticky on a collision; it doesn't "pull" Jak along with it, and he repetitively "falls" onto it's surface over and over as it moves downwards. This is a mystery, since this actor does have sticky, in fact, it's not even possible to detect riders without sticky, which these certainly do. This same appears on PS2, so I knew fixing it was not an option. I had an immediate theory of what was happening: since Jak is continuously falling onto it as it moves downwards, he's only riding some frames, and skipping others. A higher frame rate = more moments he's touching. This means that basically, the balance-plat's entire speed is determined by when his feet happen to micro-stutter. This is the hardcoded scalar for it's entire speed. This means I needed to emulate the 60 FPS "falling" at different framerates. I tried adding an fps counter to the actor so that the rider check only ran once every 60th of a second, but I knew adding a variable to the actor just for fps wasn't a good idea. However, I was able to confirm that would work. Luckily, this [comment](https://github.com/open-goal/jak-project/blob/0fa93ce7b83d1621f3fd040625bfa865eadaeef0/goal_src/jak1/engine/game/game-h.gc#L116) gave me an idea, which meant I could use a variable of process drawable, avoiding adding a variable. This unique problem needs a unique solution, I gate rider checks so they only pass if one sixtieth of a second `(seconds 0.01666666)` has passed since the last ride. This makes it act like his feet are stuttering like they do at 60 FPS, avoiding the extra frames where his feet are touching at high framerates that add more speed. I also added the same for the send-to messages, since at high frame rates the passive return to center that runs every frame was stomping the send-to-next velocities which run less often. Then, I managed to refactor it down to a tiny edit, a guard in one place that gates on 1/60th of a second, framerate already 60, or, self grow (since you don't want to double-gate these). ```lisp (when (or (= (-> *pc-settings* target-fps) 60) (time-elapsed? (-> self state-time) (seconds 0.01666666)) (-> self got-grow)) ``` It's just: this guard, `(set-state-time)` on ride, and `(set-state-time)` on grow (limits increased-rate return to center stomping grow). Lastly, there was an issue with the existing multiplies by FPS RATIO, which was a clear double scale of the acceleration (once when setting accelerate, then again when adding the accelerate). I'm guessing since the extra riding frames added so much speed, this double scalar helped slow it down, however when corrected the double scalar made it too slow since it was slowing down for framerate twice. --- goal_src/jak1/levels/swamp/swamp-obs.gc | 65 +++++++++++++------------ 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/goal_src/jak1/levels/swamp/swamp-obs.gc b/goal_src/jak1/levels/swamp/swamp-obs.gc index cafcd0f276..f57e2665f8 100644 --- a/goal_src/jak1/levels/swamp/swamp-obs.gc +++ b/goal_src/jak1/levels/swamp/swamp-obs.gc @@ -340,36 +340,41 @@ ;; og:preserve-this modified for high fps (let ((f30-0 (* -0.025 (+ (-> self y-offset) (-> self y-travel)))) (f28-0 (* -0.025 (- (-> self y-offset) (-> self y-travel))))) - (cond - ((and (-> self root riders) (nonzero? (-> self root riders num-riders))) - ;; has rider, accelerate downwards - (send-event *target* 'no-look-around (seconds 0.25)) - (set! (-> self y-accel) (fmin 4.096 (fmax -4.096 (+ (* DISPLAY_FPS_RATIO -0.2048) (-> self y-accel))))) - (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel)))))) - ;; tell linked platforms to go up - (send-to-all-after (-> self link) 'grow) - (send-to-all-before (-> self link) 'grow)) - ((-> self got-grow) - ;; got 'grow' signal, accelerate upwards - (set! (-> self got-grow) #f) - (set! (-> self y-accel) (fmin 4.096 (fmax -4.096 (+ (* DISPLAY_FPS_RATIO 0.2048) (-> self y-accel))))) - (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel))))))) - ((< (-> self y-offset) 0.0) - ;; accelerate towards neutral position (from below) - (set! (-> self y-accel) - (fmin (fmin 4.096 (fmax -4.096 (+ (* DISPLAY_FPS_RATIO 0.1024) (-> self y-accel)))) (* -0.0001 (-> self y-offset)))) - (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel)))))) - ;; slow down - (set! (-> self y-vel) (* (- 1.0 (* DISPLAY_FPS_RATIO (- 1.0 0.99))) (-> self y-vel)))) - (else - ;; accelerate towards neutral position (from above) - (set! (-> self y-accel) - (fmin 4.096 (fmax (fmax -4.096 (+ (* DISPLAY_FPS_RATIO -0.1024) (-> self y-accel))) (* -0.0001 (-> self y-offset))))) - (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel)))))) - ;; slow down - (set! (-> self y-vel) (* (- 1.0 (* DISPLAY_FPS_RATIO (- 1.0 0.99))) (-> self y-vel)))))) - (+! (-> self y-offset) (* DISPLAY_FPS_RATIO (-> self y-vel))) - (set! (-> self root trans y) (+ (-> self y-init) (-> self y-offset))) + (when (or (= (-> *pc-settings* target-fps) 60) ;; always run untouched if 60 FPS + (time-elapsed? (-> self state-time) (seconds 0.01666666)) ;; added for high-fps, gate riding like 60 fps + (-> self got-grow)) ;; don't gate grow recieve since grow send is gated + (cond + ((and (-> self root riders) (nonzero? (-> self root riders num-riders))) + (set-state-time) ;; added for high-fps, record last ride to gate to 60 FPS + ;; has rider, accelerate downwards + (send-event *target* 'no-look-around (seconds 0.25)) + (set! (-> self y-accel) (fmin 4.096 (fmax -4.096 (+ -0.2048 (-> self y-accel))))) + (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel)))))) + ;; tell linked platforms to go up + (send-to-all-after (-> self link) 'grow) + (send-to-all-before (-> self link) 'grow)) + ((-> self got-grow) + (set-state-time) ;; added for high-fps, gate after grow recieve to stop passive return stomping it at high fps + ;; got 'grow' signal, accelerate upwards + (set! (-> self got-grow) #f) + (set! (-> self y-accel) (fmin 4.096 (fmax -4.096 (+ 0.2048 (-> self y-accel))))) + (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel))))))) + ((< (-> self y-offset) 0.0) + ;; accelerate towards neutral position (from below) + (set! (-> self y-accel) + (fmin (fmin 4.096 (fmax -4.096 (+ 0.1024 (-> self y-accel)))) (* -0.0001 (-> self y-offset)))) + (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel)))))) + ;; slow down + (set! (-> self y-vel) (* (- 1.0 (* DISPLAY_FPS_RATIO (- 1.0 0.99))) (-> self y-vel)))) + (else + ;; accelerate towards neutral position (from above) + (set! (-> self y-accel) + (fmin 4.096 (fmax (fmax -4.096 (+ -0.1024 (-> self y-accel))) (* -0.0001 (-> self y-offset))))) + (set! (-> self y-vel) (fmin f28-0 (fmax f30-0 (+ (-> self y-vel) (* DISPLAY_FPS_RATIO (-> self y-accel)))))) + ;; slow down + (set! (-> self y-vel) (* (- 1.0 (* DISPLAY_FPS_RATIO (- 1.0 0.99))) (-> self y-vel)))))) + (+! (-> self y-offset) (* DISPLAY_FPS_RATIO (-> self y-vel))) + (set! (-> self root trans y) (+ (-> self y-init) (-> self y-offset)))) (suspend))) :post rider-post)