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

Statistical accuracy PP and difficulty scaling for the osu!taiko ruleset #20963

Merged
merged 42 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
442e68a
Implement taiko deviation estimation
Natelytle Oct 25, 2022
d5b06ae
Fix difficultyvalue acc scaling
Natelytle Oct 25, 2022
607a006
oops
Natelytle Oct 25, 2022
87cba2d
Slight adjustments
Natelytle Oct 25, 2022
7d3338a
LTCA Balancing pass
Natelytle Oct 26, 2022
af919a6
harshen deviation scaling
Natelytle Oct 26, 2022
2940d18
Fix formatting
Natelytle Oct 27, 2022
883790c
Return null instead of infinity
Natelytle Oct 28, 2022
01c79d8
remove other infinity reference
Natelytle Oct 28, 2022
7403c1c
Return null for greatprobability >= 1
Natelytle Oct 29, 2022
16301f0
Fix low end accuracy, buff high end
Natelytle Oct 30, 2022
37c21cd
fix formatting
Natelytle Nov 1, 2022
2ba1634
account for low acc FC deviation
Natelytle Nov 25, 2022
0e4e92b
totalvalue
Natelytle Nov 25, 2022
b579af6
fix dt
Natelytle Nov 25, 2022
e3ef180
fixes
Natelytle Nov 25, 2022
7b5373a
add comments
Natelytle Nov 27, 2022
34533e5
Merge branch 'master' into taikostatacc
smoogipoo Nov 29, 2022
6a27206
bugfix + tests
Natelytle Dec 2, 2022
2b74c4e
tests return a greathitwindow of 0, add check
Natelytle Dec 3, 2022
45e8d18
fix extremely low OD breaking deviation calc
Natelytle Dec 6, 2022
334f60f
Reformat everything to be simpler
Natelytle Feb 22, 2023
d5ac73e
Merge remote-tracking branch 'osumaster/master' into taikostatacc
Natelytle Feb 23, 2023
adf1618
Change accuracy scaling
Natelytle Feb 23, 2023
858afcd
Pass OK hit window as a separate difficulty attribute, fix erfc appro…
Natelytle Mar 21, 2023
9aa11e0
update desmos
Natelytle Mar 21, 2023
7ee9101
Merge remote-tracking branch 'osumaster/master' into taikostatacc
Natelytle Jun 26, 2023
31c8cf0
Buff accuracy scaling
Natelytle Jul 28, 2023
4de0246
Make comments more professional
Natelytle Jul 28, 2023
faddc4f
Merge remote-tracking branch 'osumaster/master' into taikostatacc
Natelytle Jul 28, 2023
e569420
Serialize ok hit window attribute to db
Natelytle Jul 28, 2023
5f0020b
Reduce accuracy scaling
Natelytle Jul 31, 2023
3a609c9
Merge branch 'master' into taikostatacc
smoogipoo Aug 5, 2023
8a26cda
Merge master
Natelytle Mar 10, 2024
caba051
Compute the upper bound on deviation with a 99% confidence interval
Natelytle Mar 10, 2024
6ddb2b7
Include misses in the great window deviation calc
Natelytle Mar 10, 2024
5370595
Fix comment
Natelytle Mar 10, 2024
a9b3416
Remove MathNet.Numerics dependency
Natelytle Mar 10, 2024
1714567
Save deviation calculations to variables
Natelytle May 29, 2024
f8f18b6
Fix naming convention
Natelytle May 29, 2024
2fb22f1
Move the return value for deviation below the local functions
Natelytle Jun 23, 2024
84d6467
Merge branch 'master' into taikostatacc
bdach Oct 7, 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
11 changes: 11 additions & 0 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,23 @@ public class TaikoDifficultyAttributes : DifficultyAttributes
[JsonProperty("great_hit_window")]
public double GreatHitWindow { get; set; }

/// <summary>
/// The perceived hit window for an OK hit inclusive of rate-adjusting mods (DT/HT/etc).
/// </summary>
/// <remarks>
/// Rate-adjusting mods don't directly affect the hit window, but have a perceived effect as a result of adjusting audio timing.
/// </remarks>
[JsonProperty("ok_hit_window")]
public double OkHitWindow { get; set; }

public override IEnumerable<(int attributeId, object value)> ToDatabaseAttributes()
{
foreach (var v in base.ToDatabaseAttributes())
yield return v;

yield return (ATTRIB_ID_DIFFICULTY, StarRating);
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
yield return (ATTRIB_ID_OK_HIT_WINDOW, OkHitWindow);
}

public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> values, IBeatmapOnlineInfo onlineInfo)
Expand All @@ -58,6 +68,7 @@ public override void FromDatabaseAttributes(IReadOnlyDictionary<int, double> val

StarRating = values[ATTRIB_ID_DIFFICULTY];
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
OkHitWindow = values[ATTRIB_ID_OK_HIT_WINDOW];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beat
ColourDifficulty = colourRating,
PeakDifficulty = combinedRating,
GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate,
OkHitWindow = hitWindows.WindowFor(HitResult.Ok) / clockRate,
MaxCombo = beatmap.GetMaxCombo(),
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public class TaikoPerformanceAttributes : PerformanceAttributes
[JsonProperty("effective_miss_count")]
public double EffectiveMissCount { get; set; }

[JsonProperty("estimated_unstable_rate")]
public double? EstimatedUnstableRate { get; set; }

public override IEnumerable<PerformanceDisplayAttribute> GetAttributesForDisplay()
{
foreach (var attribute in base.GetAttributesForDisplay())
Expand Down
81 changes: 71 additions & 10 deletions osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Scoring;
using osu.Game.Utils;

namespace osu.Game.Rulesets.Taiko.Difficulty
{
Expand All @@ -18,7 +19,7 @@ public class TaikoPerformanceCalculator : PerformanceCalculator
private int countOk;
private int countMeh;
private int countMiss;
private double accuracy;
private double? estimatedUnstableRate;

private double effectiveMissCount;

Expand All @@ -35,7 +36,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
countOk = score.Statistics.GetValueOrDefault(HitResult.Ok);
countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh);
countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss);
accuracy = customAccuracy;
estimatedUnstableRate = computeDeviationUpperBound(taikoAttributes) * 10;

// The effectiveMissCount is calculated by gaining a ratio for totalSuccessfulHits and increasing the miss penalty for shorter object counts lower than 1000.
if (totalSuccessfulHits > 0)
Expand Down Expand Up @@ -65,6 +66,7 @@ protected override PerformanceAttributes CreatePerformanceAttributes(ScoreInfo s
Difficulty = difficultyValue,
Accuracy = accuracyValue,
EffectiveMissCount = effectiveMissCount,
EstimatedUnstableRate = estimatedUnstableRate,
Total = totalValue
};
}
Expand All @@ -85,35 +87,94 @@ private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes
difficultyValue *= 1.025;

if (score.Mods.Any(m => m is ModHardRock))
difficultyValue *= 1.050;
difficultyValue *= 1.10;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the changes here already account for hit windows properly, and does not concern SV, why does hard rock specifically need to be buffed here?

Copy link
Member

@Lawtrohux Lawtrohux Oct 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was done in balancing, as mid-range accuracy with HR was pretty underweighted (4x 100 on the limit does not exist HR was worth 20pp less than a HD SS). Though @Natelytle could the model be fit for greater accuracy leniency on super high OD's?

Copy link
Contributor Author

@Natelytle Natelytle Oct 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you can without increasing complexity and decreasing estimation accuracy, I think a HR multiplier buff is a better direction if HR in particular is underweight


if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>))
difficultyValue *= 1.050 * lengthBonus;

return difficultyValue * Math.Pow(accuracy, 2.0);
if (estimatedUnstableRate == null)
return 0;

return difficultyValue * Math.Pow(SpecialFunctions.Erf(400 / (Math.Sqrt(2) * estimatedUnstableRate.Value)), 2.0);
}

private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes, bool isConvert)
{
if (attributes.GreatHitWindow <= 0)
if (attributes.GreatHitWindow <= 0 || estimatedUnstableRate == null)
return 0;

double accuracyValue = Math.Pow(60.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(accuracy, 8.0) * Math.Pow(attributes.StarRating, 0.4) * 27.0;
double accuracyValue = Math.Pow(70 / estimatedUnstableRate.Value, 1.1) * Math.Pow(attributes.StarRating, 0.4) * 100.0;

double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
accuracyValue *= lengthBonus;

// Slight HDFL Bonus for accuracy. A clamp is used to prevent against negative values.
if (score.Mods.Any(m => m is ModFlashlight<TaikoHitObject>) && score.Mods.Any(m => m is ModHidden) && !isConvert)
accuracyValue *= Math.Max(1.0, 1.1 * lengthBonus);
accuracyValue *= Math.Max(1.0, 1.05 * lengthBonus);

return accuracyValue;
}

/// <summary>
/// Computes an upper bound on the player's tap deviation based on the OD, number of circles and sliders,
/// and the hit judgements, assuming the player's mean hit error is 0. The estimation is consistent in that
/// two SS scores on the same map with the same settings will always return the same deviation.
/// </summary>
private double? computeDeviationUpperBound(TaikoDifficultyAttributes attributes)
{
if (totalSuccessfulHits == 0 || attributes.GreatHitWindow <= 0)
return null;

double h300 = attributes.GreatHitWindow;
double h100 = attributes.OkHitWindow;

const double z = 2.32634787404; // 99% critical value for the normal distribution (one-tailed).

// The upper bound on deviation, calculated with the ratio of 300s to objects, and the great hit window.
double? calcDeviationGreatWindow()
{
if (countGreat == 0) return null;

double n = totalHits;

// Proportion of greats hit.
double p = countGreat / n;

// We can be 99% confident that p is at least this value.
double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);

// We can be 99% confident that the deviation is not higher than:
return h300 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}

// The upper bound on deviation, calculated with the ratio of 300s + 100s to objects, and the good hit window.
// This will return a lower value than the first method when the number of 100s is high, but the miss count is low.
double? calcDeviationGoodWindow()
{
if (totalSuccessfulHits == 0) return null;

double n = totalHits;

// Proportion of greats + goods hit.
double p = totalSuccessfulHits / n;

// We can be 99% confident that p is at least this value.
double pLowerBound = (n * p + z * z / 2) / (n + z * z) - z / (n + z * z) * Math.Sqrt(n * p * (1 - p) + z * z / 4);

// We can be 99% confident that the deviation is not higher than:
return h100 / (Math.Sqrt(2) * SpecialFunctions.ErfInv(pLowerBound));
}

double? deviationGreatWindow = calcDeviationGreatWindow();
double? deviationGoodWindow = calcDeviationGoodWindow();

if (deviationGreatWindow is null)
return deviationGoodWindow;

return Math.Min(deviationGreatWindow.Value, deviationGoodWindow!.Value);
}

private int totalHits => countGreat + countOk + countMeh + countMiss;

private int totalSuccessfulHits => countGreat + countOk + countMeh;

private double customAccuracy => totalHits > 0 ? (countGreat * 300 + countOk * 150) / (totalHits * 300.0) : 0;
}
}
1 change: 1 addition & 0 deletions osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class DifficultyAttributes
protected const int ATTRIB_ID_SPEED_NOTE_COUNT = 21;
protected const int ATTRIB_ID_SPEED_DIFFICULT_STRAIN_COUNT = 23;
protected const int ATTRIB_ID_AIM_DIFFICULT_STRAIN_COUNT = 25;
protected const int ATTRIB_ID_OK_HIT_WINDOW = 27;

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