Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change S rank to require no miss #26630

Merged
merged 24 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c8521b4
Change S rank to require no miss
peppy Jan 19, 2024
bb1eab5
Update test in line with new rank definitions always being applied
peppy Jan 22, 2024
4eba3b5
Move `BackgroundDataStoreProcessor` to better namespace
peppy Jan 22, 2024
644e7d6
Add migration
peppy Jan 22, 2024
83f9118
Adjust results screen to handle S->A rank adjustment when misses are …
peppy Jan 22, 2024
5bae907
Merge branch 'master' into s-rank-change
bdach Jan 22, 2024
cb8ec48
Make `RankFromScore()`'s dictionary param readonly
bdach Jan 22, 2024
20ed7e1
Fix back-to-front conditional in taiko processor
bdach Jan 22, 2024
45dc9de
Remove remnant of old implementation of showing "A due to misses"
bdach Jan 22, 2024
81a0266
Adjust existing test to fail
bdach Jan 17, 2024
3f31593
Add another failing tests covering recomputation of ranks
bdach Jan 22, 2024
aa8eee0
Move maximum statistics population to `LegacyScoreDecoder`
bdach Jan 17, 2024
2958631
Use lazer accuracy & rank implementations across the board
bdach Jan 17, 2024
db48494
Unify legacy total score / accuracy / rank recomputation flows
bdach Jan 22, 2024
217cbf6
Remove superfluous recomputation of accuracy
bdach Jan 22, 2024
cdd6e71
Remove legacy computation of accuracy & ranks
bdach Jan 22, 2024
d53fb5a
Update assertions to match expected behaviour
bdach Jan 22, 2024
24be6d9
Expand xmldoc
bdach Jan 22, 2024
fdd499a
Fix rank background processing not working (and not bumping score ver…
bdach Jan 22, 2024
4d253eb
Remove redundant assertion
bdach Jan 22, 2024
0cf9067
Apply more correct visual offset adjustment
peppy Jan 23, 2024
cb87d6c
Move transferal of `LegacyTotalScore` back to original spot
bdach Jan 23, 2024
0b5be3c
Remove outdated test
bdach Jan 23, 2024
6c169e3
Do not reprocess ranks for custom rulesets
bdach Jan 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ protected override double GetComboScoreChange(JudgementResult result)
return baseIncrease * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
}

public override ScoreRank RankFromAccuracy(double accuracy)
public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
{
if (accuracy == accuracy_cutoff_x)
return ScoreRank.X;
Expand Down
18 changes: 18 additions & 0 deletions osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// 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.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;

namespace osu.Game.Rulesets.Osu.Scoring
{
Expand All @@ -14,6 +16,22 @@ public OsuScoreProcessor()
{
}

public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
{
ScoreRank rank = base.RankFromScore(accuracy, results);

switch (rank)
{
case ScoreRank.S:
case ScoreRank.X:
if (results.GetValueOrDefault(HitResult.Miss) > 0)
rank = ScoreRank.A;
break;
}

return rank;
}

protected override HitEvent CreateHitEvent(JudgementResult result)
=> base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit);
}
Expand Down
18 changes: 18 additions & 0 deletions osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Scoring;

namespace osu.Game.Rulesets.Taiko.Scoring
{
Expand Down Expand Up @@ -33,6 +35,22 @@ protected override double GetComboScoreChange(JudgementResult result)
* strongScaleValue(result);
}

public override ScoreRank RankFromScore(double accuracy, IReadOnlyDictionary<HitResult, int> results)
{
ScoreRank rank = base.RankFromScore(accuracy, results);

switch (rank)
{
case ScoreRank.S:
case ScoreRank.X:
if (results.GetValueOrDefault(HitResult.Miss) > 0)
rank = ScoreRank.A;
break;
}

return rank;
}

public override int GetBaseScoreForResult(HitResult result)
{
switch (result)
Expand Down
72 changes: 61 additions & 11 deletions osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Legacy;
Expand All @@ -23,6 +22,7 @@
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Replays;
Expand Down Expand Up @@ -59,14 +59,14 @@ public void TestDecodeManiaReplay()
Assert.AreEqual(2, score.ScoreInfo.Statistics[HitResult.Great]);
Assert.AreEqual(1, score.ScoreInfo.Statistics[HitResult.Good]);

Assert.AreEqual(829_931, score.ScoreInfo.TotalScore);
Assert.AreEqual(829_931, score.ScoreInfo.LegacyTotalScore);
Assert.AreEqual(3, score.ScoreInfo.MaxCombo);

Assert.IsTrue(score.ScoreInfo.Mods.Any(m => m is ManiaModClassic));
Assert.IsTrue(score.ScoreInfo.APIMods.Any(m => m.Acronym == "CL"));
Assert.IsTrue(score.ScoreInfo.ModsJson.Contains("CL"));

Assert.IsTrue(Precision.AlmostEquals(0.8889, score.ScoreInfo.Accuracy, 0.0001));
Assert.That((2 * 300d + 1 * 200) / (3 * 305d), Is.EqualTo(score.ScoreInfo.Accuracy).Within(0.0001));
Assert.AreEqual(ScoreRank.B, score.ScoreInfo.Rank);

Assert.That(score.Replay.Frames, Is.Not.Empty);
Expand Down Expand Up @@ -252,7 +252,49 @@ public void TestSoloScoreData()
}

[Test]
public void AccuracyAndRankOfStableScorePreserved()
public void AccuracyOfStableScoreRecomputed()
{
var memoryStream = new MemoryStream();

// local partial implementation of legacy score encoder
// this is done half for readability, half because `LegacyScoreEncoder` forces `LATEST_VERSION`
// and we want to emulate a stable score here
using (var sw = new SerializationWriter(memoryStream, true))
{
sw.Write((byte)3); // ruleset id (mania).
// mania is used intentionally as it is the only ruleset wherein default accuracy calculation is changed in lazer
sw.Write(20240116); // version (anything below `LegacyScoreEncoder.FIRST_LAZER_VERSION` is stable)
sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
sw.Write("username"); // irrelevant to this test
sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
sw.Write((ushort)1); // count300
sw.Write((ushort)0); // count100
sw.Write((ushort)0); // count50
sw.Write((ushort)198); // countGeki (perfects / "rainbow 300s" in mania)
sw.Write((ushort)0); // countKatu
sw.Write((ushort)1); // countMiss
sw.Write(12345678); // total score, irrelevant to this test
sw.Write((ushort)1000); // max combo, irrelevant to this test
sw.Write(false); // full combo, irrelevant to this test
sw.Write((int)LegacyMods.Hidden); // mods
sw.Write(string.Empty); // hp graph, irrelevant
sw.Write(DateTime.Now); // date, irrelevant
sw.Write(Array.Empty<byte>()); // replay data, irrelevant
sw.Write((long)1234); // legacy online ID, irrelevant
}

memoryStream.Seek(0, SeekOrigin.Begin);
var decoded = new TestLegacyScoreDecoder().Parse(memoryStream);

Assert.Multiple(() =>
{
Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 305 + 300) / (200 * 305)));
Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
});
}

[Test]
public void RankOfStableScoreUsesLazerDefinitions()
{
var memoryStream = new MemoryStream();

Expand All @@ -266,12 +308,12 @@ public void AccuracyAndRankOfStableScorePreserved()
sw.Write(string.Empty.ComputeMD5Hash()); // beatmap hash, irrelevant to this test
sw.Write("username"); // irrelevant to this test
sw.Write(string.Empty.ComputeMD5Hash()); // score hash, irrelevant to this test
sw.Write((ushort)198); // count300
sw.Write((ushort)195); // count300
sw.Write((ushort)1); // count100
sw.Write((ushort)0); // count50
sw.Write((ushort)4); // count50
sw.Write((ushort)0); // countGeki
sw.Write((ushort)0); // countKatu
sw.Write((ushort)1); // countMiss
sw.Write((ushort)0); // countMiss
sw.Write(12345678); // total score, irrelevant to this test
sw.Write((ushort)1000); // max combo, irrelevant to this test
sw.Write(false); // full combo, irrelevant to this test
Expand All @@ -287,13 +329,13 @@ public void AccuracyAndRankOfStableScorePreserved()

Assert.Multiple(() =>
{
Assert.That(decoded.ScoreInfo.Accuracy, Is.EqualTo((double)(198 * 300 + 100) / (200 * 300)));
Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A));
// In stable this would be an A because there are over 1% 50s. But that's not a thing in lazer.
Assert.That(decoded.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
});
}

[Test]
public void AccuracyAndRankOfLazerScorePreserved()
public void AccuracyRankAndTotalScoreOfLazerScorePreserved()
{
var ruleset = new OsuRuleset().RulesetInfo;

Expand Down Expand Up @@ -321,8 +363,10 @@ public void AccuracyAndRankOfLazerScorePreserved()

Assert.Multiple(() =>
{
Assert.That(decodedAfterEncode.ScoreInfo.TotalScore, Is.EqualTo(284_537));
Assert.That(decodedAfterEncode.ScoreInfo.LegacyTotalScore, Is.Null);
Assert.That(decodedAfterEncode.ScoreInfo.Accuracy, Is.EqualTo((double)(199 * 300 + 30) / (200 * 300 + 30)));
Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.SH));
Assert.That(decodedAfterEncode.ScoreInfo.Rank, Is.EqualTo(ScoreRank.A));
});
}

Expand Down Expand Up @@ -415,6 +459,12 @@ public TestLegacyScoreDecoder(int beatmapVersion = LegacyBeatmapDecoder.LATEST_V
Ruleset = new OsuRuleset().RulesetInfo,
Difficulty = new BeatmapDifficulty(),
BeatmapVersion = beatmapVersion,
},
// needs to have at least one objects so that `StandardisedScoreMigrationTools` doesn't die
// when trying to recompute total score.
HitObjects =
{
new HitCircle()
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using osu.Framework.Extensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Scoring.Legacy;
Expand Down
19 changes: 11 additions & 8 deletions osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
Expand Down Expand Up @@ -96,6 +97,14 @@ private ScoreInfo createScore(double accuracy, Ruleset ruleset)
{
var scoreProcessor = ruleset.CreateScoreProcessor();

var statistics = new Dictionary<HitResult, int>
{
{ HitResult.Miss, 1 },
{ HitResult.Meh, 50 },
{ HitResult.Good, 100 },
{ HitResult.Great, 300 },
};

return new ScoreInfo
{
User = new APIUser
Expand All @@ -109,15 +118,9 @@ private ScoreInfo createScore(double accuracy, Ruleset ruleset)
TotalScore = 2845370,
Accuracy = accuracy,
MaxCombo = 999,
Rank = scoreProcessor.RankFromAccuracy(accuracy),
Rank = scoreProcessor.RankFromScore(accuracy, statistics),
Date = DateTimeOffset.Now,
Statistics =
{
{ HitResult.Miss, 1 },
{ HitResult.Meh, 50 },
{ HitResult.Good, 100 },
{ HitResult.Great, 300 },
}
Statistics = statistics,
};
}
}
Expand Down
21 changes: 12 additions & 9 deletions osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
Expand Down Expand Up @@ -71,15 +72,16 @@ public void TestScaling()

private int onlineScoreID = 1;

[TestCase(1, ScoreRank.X)]
[TestCase(0.9999, ScoreRank.S)]
[TestCase(0.975, ScoreRank.S)]
[TestCase(0.925, ScoreRank.A)]
[TestCase(0.85, ScoreRank.B)]
[TestCase(0.75, ScoreRank.C)]
[TestCase(0.5, ScoreRank.D)]
[TestCase(0.2, ScoreRank.D)]
public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
[TestCase(1, ScoreRank.X, 0)]
[TestCase(0.9999, ScoreRank.S, 0)]
[TestCase(0.975, ScoreRank.S, 0)]
[TestCase(0.975, ScoreRank.A, 1)]
[TestCase(0.925, ScoreRank.A, 5)]
[TestCase(0.85, ScoreRank.B, 9)]
[TestCase(0.75, ScoreRank.C, 11)]
[TestCase(0.5, ScoreRank.D, 21)]
[TestCase(0.2, ScoreRank.D, 51)]
public void TestResultsWithPlayer(double accuracy, ScoreRank rank, int missCount)
{
TestResultsScreen screen = null;

Expand All @@ -91,6 +93,7 @@ public void TestResultsWithPlayer(double accuracy, ScoreRank rank)
score.HitEvents = TestSceneStatisticsPanel.CreatePositionDistributedHitEvents();
score.Accuracy = accuracy;
score.Rank = rank;
score.Statistics[HitResult.Miss] = missCount;

return screen = createResultsScreen(score);
});
Expand Down
Loading
Loading