diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
index 0145a1dfef..314e275ca3 100644
--- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
+++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs
@@ -24,11 +24,20 @@ namespace osu.Game.Tests.Visual.Ranking
{
public partial class TestSceneAccuracyCircle : OsuTestScene
{
+ [TestCase(0, ScoreRank.D)]
[TestCase(0.2, ScoreRank.D)]
[TestCase(0.5, ScoreRank.D)]
+ [TestCase(0.699, ScoreRank.D)]
+ [TestCase(0.7, ScoreRank.C)]
[TestCase(0.75, ScoreRank.C)]
+ [TestCase(0.799, ScoreRank.C)]
+ [TestCase(0.80, ScoreRank.B)]
[TestCase(0.85, ScoreRank.B)]
+ [TestCase(0.899, ScoreRank.B)]
+ [TestCase(0.9, ScoreRank.A)]
[TestCase(0.925, ScoreRank.A)]
+ [TestCase(0.949, ScoreRank.A)]
+ [TestCase(0.95, ScoreRank.S)]
[TestCase(0.975, ScoreRank.S)]
[TestCase(0.9999, ScoreRank.S)]
[TestCase(1, ScoreRank.X)]
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
index 8e04bb68fb..3cb09734c5 100644
--- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs
@@ -73,6 +73,11 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
///
private const double virtual_ss_percentage = 0.01;
+ ///
+ /// The width of a in terms of accuracy.
+ ///
+ public const double NOTCH_WIDTH_PERCENTAGE = 0.002;
+
///
/// The easing for the circle filling transforms.
///
@@ -263,7 +268,34 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
using (BeginDelayedSequence(ACCURACY_TRANSFORM_DELAY))
{
- double targetAccuracy = score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH ? 1 : Math.Min(1 - virtual_ss_percentage, score.Accuracy);
+ double targetAccuracy = score.Accuracy;
+
+ // Ensure the gauge overshoots or undershoots a bit so it doesn't land in the gaps of the inner graded circle (caused by `RankNotch`es),
+ // to prevent ambiguity on what grade it's pointing at.
+ double[] notchPercentages = { 0.7, 0.8, 0.9, 0.95 };
+
+ foreach (double p in notchPercentages)
+ {
+ if (targetAccuracy > p - NOTCH_WIDTH_PERCENTAGE / 2 && targetAccuracy < p + NOTCH_WIDTH_PERCENTAGE / 2)
+ {
+ int tippingDirection = targetAccuracy - p >= 0 ? 1 : -1; // We "round up" here to match rank criteria
+ targetAccuracy = p + tippingDirection * (NOTCH_WIDTH_PERCENTAGE / 2);
+ break;
+ }
+ }
+
+ // The final gap between 99.999...% (S) and 100% (SS) is exaggerated by `virtual_ss_percentage`. We don't want to land there either.
+ if (score.Rank == ScoreRank.X || score.Rank == ScoreRank.XH)
+ targetAccuracy = 1;
+ else
+ targetAccuracy = Math.Min(1 - virtual_ss_percentage - NOTCH_WIDTH_PERCENTAGE / 2, targetAccuracy);
+
+ // The accuracy circle gauge visually fills up a bit too much.
+ // This wouldn't normally matter but we want it to align properly with the inner graded circle in the above cases.
+ const double visual_alignment_offset = 0.001;
+
+ if (targetAccuracy < 1 && targetAccuracy >= visual_alignment_offset)
+ targetAccuracy -= visual_alignment_offset;
accuracyCircle.FillTo(targetAccuracy, ACCURACY_TRANSFORM_DURATION, ACCURACY_TRANSFORM_EASING);
diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs
index 7e73767318..32f2eb2fa5 100644
--- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs
+++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankNotch.cs
@@ -41,7 +41,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
Height = AccuracyCircle.RANK_CIRCLE_RADIUS,
- Width = 1f,
+ Width = (float)AccuracyCircle.NOTCH_WIDTH_PERCENTAGE * 360f,
Colour = OsuColour.Gray(0.3f),
EdgeSmoothness = new Vector2(1f)
}