8000 Use proportion of difficult sliders to better estimate sliderbreaks on classic accuracy scores by TextAdventurer12 · Pull Request #31234 · ppy/osu · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Use proportion of difficult sliders to better estimate sliderbreaks on classic accuracy scores #31234

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

Merged
merged 19 commits into from
May 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ public class OsuDifficultyAttributes : DifficultyAttributes
[JsonProperty("slider_factor")]
public double SliderFactor { get; set; }

/// <summary>
/// Describes how much of <see cref="AimDifficultStrainCount"/> is contributed to by hitcircles or sliders
/// A value closer to 0.0 indicates most of <see cref="AimDifficultStrainCount"/> is contributed by hitcircles
/// A value closer to Infinity indicates most of <see cref="AimDifficultStrainCount"/> is contributed by sliders
/// </summary>
[JsonProperty("aim_top_weighted_slider_factor")]
public double AimTopWeightedSliderFactor { get; set; }

/// <summary>
/// Describes how much of <see cref="SpeedDifficultStrainCount"/> is contributed to by hitcircles or sliders
/// A value closer to 0.0 indicates most of <see cref="SpeedDifficultStrainCount"/> is contributed by hitcircles
/// A value closer to Infinity indicates most of <see cref="SpeedDifficultStrainCount"/> is contributed by sliders
/// </summary>
[JsonProperty("speed_top_weighted_slider_factor")]
public double SpeedTopWeightedSliderFactor { get; set; }

[JsonProperty("aim_difficult_strain_count")]
public double AimDifficultStrainCount { get; set; }

Expand Down Expand Up @@ -97,6 +113,8 @@ public class OsuDifficultyAttributes : DifficultyAttributes
yield return (ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT, SpeedDifficultStrainCount);
yield return (ATTRIB_ID_SPEED_NOTE_COUNT, SpeedNoteCount);
yield return (ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT, AimDifficultSliderCount);
yield return (ATTRIB_ID_AIM_TOP_WEIGHTED_SLIDER_FACTOR, AimTopWeightedSliderFactor);
yield return (ATTRIB_ID_SPEED_TOP_WEIGHTED_SLIDER_FACTOR, SpeedTopWeightedSliderFactor);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
Expand All @@ -112,6 +130,8 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val
SpeedDifficultStrainCount = values[ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT];
SpeedNoteCount = values[ATTRIB_ID_SPEED_NOTE_COUNT];
AimDifficultSliderCount = values[ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT];
AimTopWeightedSliderFactor = values[ATTRIB_ID_AIM_TOP_WEIGHTED_SLIDER_FACTOR];
SpeedTopWeightedSliderFactor = values[ATTRIB_ID_SPEED_TOP_WEIGHTED_SLIDER_FACTOR];
DrainRate = onlineInfo.DrainRate;
HitCircleCount = onlineInfo.CircleCount;
SliderCount = onlineInfo.SliderCount;
Expand Down
10 changes: 10 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
double aimDifficultStrainCount = aim.CountTopWeightedStrains();
double speedDifficultStrainCount = speed.CountTopWeightedStrains();

double aimNoSlidersTopWeightedSliderCount = aimWithoutSliders.CountTopWeightedSliders();
double aimNoSlidersDifficultStrainCount = aimWithoutSliders.CountTopWeightedStrains();

double aimTopWeightedSliderFactor = aimNoSlidersTopWeightedSliderCount / Math.Max(1, aimNoSlidersDifficultStrainCount - aimNoSlidersTopWeightedSliderCount);

doubl 10000 e speedTopWeightedSliderCount = speed.CountTopWeightedSliders();
double speedTopWeightedSliderFactor = speedTopWeightedSliderCount / Math.Max(1, speedDifficultStrainCount - speedTopWeightedSliderCount);

double difficultSliders = aim.GetDifficultSliders();

double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate;
Expand Down Expand Up @@ -116,6 +124,8 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
SliderFactor = sliderFactor,
AimDifficultStrainCount = aimDifficultStrainCount,
SpeedDifficultStrainCount = speedDifficultStrainCount,
AimTopWeightedSliderFactor = aimTopWeightedSliderFactor,
SpeedTopWeightedSliderFactor = speedTopWeightedSliderFactor,
DrainRate = drainRate,
MaxCombo = beatmap.GetMaxCombo(),
HitCircleCount = hitCircleCount,
Expand Down
24 changes: 22 additions & 2 deletions osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,10 @@ private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attribut
aimValue *= lengthBonus;

if (effectiveMissCount > 0)
aimValue *= calculateMissPenalty(effectiveMissCount, attributes.AimDifficultStrainCount);
{
double estimatedSliderbreaks = calculateEstimatedSliderbreaks(attributes.AimTopWeightedSliderFactor, attributes);
aimValue *= calculateMissPenalty(effectiveMissCount + estimatedSliderbreaks, attributes.AimDifficultStrainCount);
}

// TC bonuses are excluded when blinds is present as the increased visual difficulty is unimportant when notes cannot be seen.
if (score.Mods.Any(m => m is OsuModBlinds))
Expand Down Expand Up @@ -233,7 +236,10 @@ private double computeSpeedValue(ScoreInfo score, OsuDifficultyAttributes attrib
speedValue *= lengthBonus;

if (effectiveMissCount > 0)
speedValue *= calculateMissPenalty(effectiveMissCount, attributes.SpeedDifficultStrainCount);
{
double estimatedSliderbreaks = calculateEstimatedSliderbreaks(attributes.SpeedTopWeightedSliderFactor, attributes);
speedValue *= calculateMissPenalty(effectiveMissCount + estimatedSliderbreaks, attributes.SpeedDifficultStrainCount);
}

// TC bonuses are excluded when blinds is present as the increased visual difficulty is unimportant when notes cannot be seen.
if (score.Mods.Any(m => m is OsuModBlinds))
Expand Down Expand Up @@ -321,6 +327,20 @@ private double computeFlashlightValue(ScoreInfo score, OsuDifficultyAttributes a
return flashlightValue;
}

private double calculateEstimatedSliderbreaks(double topWeightedSliderFactor, OsuDifficultyAttributes attributes)
{
if (!usingClassicSliderAccuracy || countOk == 0)
return 0;

double missedComboPercent = 1.0 - (double)scoreMaxCombo / attributes.MaxCombo;
double estimatedSliderbreaks = Math.Min(countOk, effectiveMissCount * topWeightedSliderFactor);

// scores with more oks are more likely to have sliderbreaks
double okAdjustment = ((countOk - estimatedSliderbreaks) + 0.5) / countOk;

return estimatedSliderbreaks * okAdjustment * DifficultyCalculationUtils.Logistic(missedComboPercent, 0.33, 15);
}

/// <summary>
/// Estimates player's deviation on speed notes using <see cref="calculateDeviation"/>, assuming worst-case.
/// Treats all speed notes as hit circles.
Expand Down
6 changes: 4 additions & 2 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using osu.Game.Rulesets.Osu.Difficulty.Utils;
using osu.Game.Rulesets.Osu.Objects;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills
Expand Down Expand Up @@ -41,9 +42,7 @@ protected override double StrainValueAt(DifficultyHitObject current)
currentStrain += AimEvaluator.EvaluateDifficultyOf(current, IncludeSliders) * skillMultiplier;

if (current.BaseObject is Slider)
{
sliderStrains.Add(currentStrain);
}

return currentStrain;
}
Expand All @@ -54,10 +53,13 @@ public double GetDifficultSliders()
return 0;

double maxSliderStrain = sliderStrains.Max();

if (maxSliderStrain == 0)
return 0;

return sliderStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxSliderStrain * 12.0 - 6.0))));
}

public double CountTopWeightedSliders() => OsuStrainUtils.CountTopWeightedSliders(sliderStrains, DifficultyValue());
}
}
10 changes: 10 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2 B422 ,14 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Evaluators;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Objects;
using System.Linq;
using osu.Game.Rulesets.Osu.Difficulty.Utils;

namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
Expand All @@ -21,6 +24,8 @@ public class Speed : OsuStrainSkill
private double currentStrain;
private double currentRhythm;

private readonly List<double> sliderStrains = new List<double>();

protected override int ReducedSectionCount => 5;

public Speed(Mod[] mods)
Expand All @@ -41,6 +46,9 @@ protected override double StrainValueAt(DifficultyHitObject current)

double totalStrain = currentStrain * currentRhythm;

if (current.BaseObject is Slider)
sliderStrains.Add(totalStrain);

return totalStrain;
}

Expand All @@ -55,5 +63,7 @@ public double RelevantNoteCount()

return ObjectStrains.Sum(strain => 1.0 / (1.0 + Math.Exp(-(strain / maxStrain * 12.0 - 6.0))));
}

public double CountTopWeightedSliders() => OsuStrainUtils.CountTopWeightedSliders(sliderStrains, DifficultyValue());
}
}
26 changes: 26 additions & 0 deletions osu.Game.Rulesets.Osu/Difficulty/Utils/OsuStrainUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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 System.Linq;
using osu.Game.Rulesets.Difficulty.Utils;

C3BE namespace osu.Game.Rulesets.Osu.Difficulty.Utils
{
public static class OsuStrainUtils
{
public static double CountTopWeightedSliders(IReadOnlyCollection<double> sliderStrains, double difficultyValue)
{
if (sliderStrains.Count == 0)
return 0;

double consistentTopStrain = difficultyValue / 10; // What would the top strain be if all strain values were identical

if (consistentTopStrain == 0)
return 0;

// Use a weighted sum of all strains. Constants are arbitrary and give nice values
return sliderStrains.Sum(s => DifficultyCalculationUtils.Logistic(s / consistentTopStrain, 0.88, 10, 1.1));
}
}
}
2 changes: 2 additions & 0 deletions osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class DifficultyAttributes
protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
protected const int ATTRIB_ID_MONO_STAMINA_FACTOR = 29;
protected const int ATTRIB_ID_AIM_DIFFICULT_SLIDER_COUNT = 31;
protected const int ATTRIB_ID_AIM_TOP_WEIGHTED_SLIDER_FACTOR = 33;
protected const int ATTRIB_ID_SPEED_TOP_WEIGHTED_SLIDER_FACTOR = 35;

/// <summary>
/// The mods which were applied to the beatmap.
Expand Down
Loading
0