From e50bc59219d9060d79a57375f402f24addb01b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Apr 2024 14:01:40 +0200 Subject: [PATCH] Truncate start time of juice stream nested objects Intended as at least a partial solution to https://github.com/ppy/osu/issues/26436. In testing, two of the three replays from the issue are FCs with this change, and the third is improved (and not fully fixed). As it turns out, stable truncates the end time of sliders to integers: https://github.com/peppy/osu-stable-reference/blob/79addff0f5d2a328059d2842d6d2968cfb740956/osu!/GameplayElements/HitObjects/Osu/SliderOsu.cs#L1037 and the start time of all juice stream parts is also truncated to integers: https://github.com/peppy/osu-stable-reference/blob/79addff0f5d2a328059d2842d6d2968cfb740956/osu!/GameplayElements/HitObjects/Fruits/SliderFruits.cs#L86-L166 This matters for replay playback when mappers push limits. For instance, let's take one case from the issue. In one of the maps involved, there was a juice-stream-ending fruit at time 7093.9655 according to lazer, which was also a hyperfruit. The broken replay on this map included one frame at time 7093, with the catcher position before the hyperdash, and one frame at time 7097, with the catcher position *after* the hyperdash. Which meant that the replay handler moved *out of the way* of the hyperfruit at time 7093.9655, deciding that it is *after* the replay frame that was supposed to be catching it. (For reference, the relevant replay playback code is here: https://github.com/ppy/osu/blob/3da5831075b187e967ca300dcba0a6761f07d1dd/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs#L20-L31 Note the handling of `position`. Any frame with an action is important, which means that as long as any key is held, interpolation will not take place.) On stable this is not a thing, because the fruit's end time was being truncated to `int`, therefore moving it back to time 7093 and restoring temporal integrity. This probably doesn't matter in other rulesets that much because the input tolerances in something like osu! or taiko are much higher. catch is rather knife-edge, what with mappers doing the "edge dash" / "pixel jump" stuff (tl;dr: placing a circle just barely outside of hyperdash range, so that a perfect normal dash is required to catch it). Thus, this is applied locally to catch for now until proven necessary to put it elsewhere too. --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 671291ef0e..d92291d570 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Droplet { Samples = dropletSamples, - StartTime = e.Time, + StartTime = (int)e.Time, X = EffectiveX + Path.PositionAt(e.PathProgress).X, }); break; @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Fruit { Samples = this.GetNodeSamples(nodeIndex++), - StartTime = e.Time, + StartTime = (int)e.Time, X = EffectiveX + Path.PositionAt(e.PathProgress).X, }); break;