From 9f91c2e25ce76255dc39b90f5b1bfef3cfbfba99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 15 May 2025 10:45:17 +0200 Subject: [PATCH] Emit important replay frames on every judgement - Closes https://github.com/ppy/osu/issues/4287 - Probably closes https://github.com/ppy/osu/issues/25405 (but not retroactively) Up until now, whether or not a replay frame is emitted depended solely on the user's input, i.e. mouse movement or key presses/releases. This, intersected with the replay playback system which is given allowance to perform interpolation between replay frames, leads to potential situations wherein a replay can play inaccurately when a judgement takes place without user input meaningfully changing. One such case is slider ends with their 36ms of judgement leniency; see https://github.com/ppy/osu/issues/25405#issuecomment-2879031106 for details on that. To that end, this commit aims to counteract that issue by *forcing* an important replay frame to be emitted on every new judgement recorded during gameplay. This will only benefit rulesets wherein judgements can occur that are not inherently tied to user input changing, which are going to be osu! as mentioned above, and maybe possibly catch. I don't foresee this doing anything relevant for taiko or mania. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 4 ++++ osu.Game/Rulesets/UI/IHasRecordingHandler.cs | 2 +- osu.Game/Rulesets/UI/ReplayRecorder.cs | 12 +++++++----- osu.Game/Rulesets/UI/RulesetInputManager.cs | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 97c4ee45af..6b2387eb9b 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -300,6 +300,7 @@ namespace osu.Game.Rulesets.UI if (score == null) { + NewResult -= emitImportantFrame; recordingInputManager.Recorder = null; return; } @@ -311,7 +312,10 @@ namespace osu.Game.Rulesets.UI recorder.ScreenSpaceToGamefield = Playfield.ScreenSpaceToGamefield; + NewResult += emitImportantFrame; recordingInputManager.Recorder = recorder; + + void emitImportantFrame(JudgementResult judgementResult) => recordingInputManager.Recorder?.RecordFrame(true); } public override void SetReplayScore(Score replayScore) diff --git a/osu.Game/Rulesets/UI/IHasRecordingHandler.cs b/osu.Game/Rulesets/UI/IHasRecordingHandler.cs index f73398dd98..f2e153e238 100644 --- a/osu.Game/Rulesets/UI/IHasRecordingHandler.cs +++ b/osu.Game/Rulesets/UI/IHasRecordingHandler.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.UI /// public interface IHasRecordingHandler { - public ReplayRecorder? Recorder { set; } + public ReplayRecorder? Recorder { get; set; } } } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index d723c31434..b126ed25f9 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -51,29 +51,29 @@ namespace osu.Game.Rulesets.UI protected override void Update() { base.Update(); - recordFrame(false); + RecordFrame(false); } protected override bool OnMouseMove(MouseMoveEvent e) { - recordFrame(false); + RecordFrame(false); return base.OnMouseMove(e); } public bool OnPressed(KeyBindingPressEvent e) { pressedActions.Add(e.Action); - recordFrame(true); + RecordFrame(true); return false; } public void OnReleased(KeyBindingReleaseEvent e) { pressedActions.Remove(e.Action); - recordFrame(true); + RecordFrame(true); } - private void recordFrame(bool important) + public override void RecordFrame(bool important) { var last = target.Replay.Frames.LastOrDefault(); @@ -98,5 +98,7 @@ namespace osu.Game.Rulesets.UI public abstract partial class ReplayRecorder : Component { public Func ScreenSpaceToGamefield; + + public abstract void RecordFrame(bool important); } } diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 31c7c34572..aa2c740c5b 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.UI public ReplayRecorder? Recorder { + get => recorder; set { if (value == recorder)