1
0
mirror of https://github.com/ppy/osu.git synced 2026-06-03 14:24:18 +08:00

Merge pull request #33161 from bdach/replay-decode-float-woes

Fix possible replay playback inaccuracy with very large lead-in time
This commit is contained in:
Dean Herbert
2025-05-17 12:31:38 +09:00
committed by GitHub
Unverified
3 changed files with 72 additions and 4 deletions
@@ -0,0 +1,59 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Replays;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring;
using osuTK;
namespace osu.Game.Tests.Visual
{
public partial class TestSceneReplayStability : ReplayStabilityTestScene
{
[Test]
public void TestOutrageouslyLargeLeadInTime()
{
// "graciously borrowed" from https://osu.ppy.sh/beatmapsets/948643#osu/1981090
const double lead_in_time = 2147272727;
const double hit_circle_time = 100;
var beatmap = new OsuBeatmap
{
HitObjects =
{
new HitCircle
{
StartTime = hit_circle_time,
Position = OsuPlayfield.BASE_SIZE / 2
}
},
AudioLeadIn = lead_in_time,
BeatmapInfo =
{
Ruleset = new OsuRuleset().RulesetInfo,
},
};
var replay = new Replay
{
Frames = Enumerable.Range(0, 300).Select(t => new OsuReplayFrame(-lead_in_time + 40 * t, new Vector2(t), t % 2 == 0 ? [] : [OsuAction.LeftButton]))
.Concat([
new OsuReplayFrame(0, OsuPlayfield.BASE_SIZE / 2),
new OsuReplayFrame(hit_circle_time, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton),
new OsuReplayFrame(hit_circle_time + 20, OsuPlayfield.BASE_SIZE / 2),
])
.Cast<ReplayFrame>()
.ToList(),
};
RunTest(beatmap, replay, [HitResult.Great]);
}
}
}
@@ -31,7 +31,7 @@ namespace osu.Game.Scoring.Legacy
private IBeatmap currentBeatmap;
private Ruleset currentRuleset;
private float beatmapOffset;
private long beatmapOffset;
public Score Parse(Stream stream)
{
@@ -262,7 +262,7 @@ namespace osu.Game.Scoring.Legacy
private void readLegacyReplay(Replay replay, StreamReader reader)
{
float lastTime = beatmapOffset;
long lastTime = beatmapOffset;
var legacyFrames = new List<LegacyReplayFrame>();
string[] frames = reader.ReadToEnd().Split(',');
@@ -283,7 +283,7 @@ namespace osu.Game.Scoring.Legacy
// In mania, mouseX encodes the pressed keys in the lower 20 bits
int mouseXParseLimit = currentRuleset.RulesetInfo.OnlineID == 3 ? (1 << 20) - 1 : Parsing.MAX_COORDINATE_VALUE;
float diff = Parsing.ParseFloat(split[0]);
int diff = Parsing.ParseInt(split[0]);
float mouseX = Parsing.ParseFloat(split[1], mouseXParseLimit);
float mouseY = Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE);
@@ -19,7 +19,7 @@ using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual
{
/// <summary>
/// The goal of this abstract test class is to ensure that the process of exporting of a replay does not affect its playback.
/// The goal of this abstract test class is to ensure that the process of exporting and re-importing of a replay does not affect its playback.
/// Use <see cref="RunTest"/> to exercise that property.
/// </summary>
[HeadlessTest]
@@ -51,6 +51,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"push player", () => pushNewPlayer(originalScore));
AddUntilStep(@"wait until player is loaded", () => currentPlayer.IsCurrentScreen());
skipIntroIfPresent();
AddUntilStep(@"wait for completion", () => currentPlayer.GameplayState.HasCompleted);
AddAssert(@"judgement results before encode are correct", () => results.Select(r => r.Type), () => Is.EquivalentTo(expectedResults));
@@ -71,6 +72,7 @@ namespace osu.Game.Tests.Visual
AddStep(@"push player", () => pushNewPlayer(decodedScore));
AddUntilStep(@"Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
skipIntroIfPresent();
AddUntilStep(@"Wait for completion", () => currentPlayer.GameplayState.HasCompleted);
AddAssert(@"judgement results after encode are correct", () => results.Select(r => r.Type), () => Is.EquivalentTo(expectedResults));
}
@@ -90,6 +92,13 @@ namespace osu.Game.Tests.Visual
results.Clear();
}
private void skipIntroIfPresent() =>
AddStep(@"skip intro if present", () =>
{
if (currentPlayer.ChildrenOfType<GameplayClockContainer>().Single().CurrentTime < 0)
currentPlayer.Seek(0);
});
private class TestScoreDecoder : LegacyScoreDecoder
{
private readonly WorkingBeatmap beatmap;