Quick answer: Replays that desync on different frame rates aren't deterministic. A short test harness that records on 60fps and plays back on 144fps surfaces the bug class quickly.

Your replay system works on the developer machine. Players on high-refresh monitors report 'replays end with the player in the wrong place'.

Record on fixed step, play on fixed step

Both record and playback should drive simulation by accumulator, not frame. while (accum >= step) { tick(); accum -= step; } regardless of render rate.

Sample frame rate range

CI plays the replay at 30, 60, 144, 240 fps. Final game state should hash identically. If not, you're sampling a non-deterministic input somewhere.

Check the obvious culprits

Random seeded per-frame instead of per-tick. Time.deltaTime in gameplay code. Floating-point order dependence in physics. These three account for most desyncs.

Bisect with the harness

When desync appears, halve the replay length, replay, check hash. Binary search to the first diverging tick. The diff there is your bug.

“Replay determinism is a one-time investment with permanent payoff.”

Build the hash-the-end-state harness even before you have replays. It's the rough cut of network sync testing.