mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 08:32:57 +08:00
Fix osu! standardised score estimation algorithm violating basic invariants
As it turns out, the "lower" and "upper" estimates of the combo portion of the score being converted were misnomers. In selected cases (scores with high accuracy but combo being lower than max by more than a few objects) the janky score-based math could overestimate the count of remaining objects in a map. For instance, in one case the numbers worked out something like this: - Accuracy: practically 100% - Max combo on beatmap: 571x - Max combo for score: 551x The score-based estimation attempts to extract a "remaining object count" from score, by doing something along of sqrt(571^2 - 551^2). That comes out to _almost 150_. Which leads to the estimation overshooting the total max combo count on the beatmap by some hundred objects. To curtail this nonsense, enforce some basic invariants: - Neither estimate is allowed to exceed maximum achievable - Ensure that lower estimate is really lower and upper is really upper by just looking at the values and making sure that is so rather than just saying that it is.
This commit is contained in:
parent
b740481eaf
commit
08609e19d6
@ -415,7 +415,7 @@ namespace osu.Game.Database
|
||||
|
||||
// Calculate how many times the longest combo the user has achieved in the play can repeat
|
||||
// without exceeding the combo portion in score V1 as achieved by the player.
|
||||
// This is a pessimistic estimate; it intentionally does not operate on object count and uses only score instead.
|
||||
// This it intentionally does not operate on object count and uses only score instead.
|
||||
double maximumOccurrencesOfLongestCombo = Math.Floor(comboPortionInScoreV1 / comboPortionFromLongestComboInScoreV1);
|
||||
double comboPortionFromRepeatedLongestCombosInScoreV1 = maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInScoreV1;
|
||||
|
||||
@ -426,13 +426,12 @@ namespace osu.Game.Database
|
||||
// ...and then based on that raw combo length, we calculate how much this last combo is worth in standardised score.
|
||||
double remainingComboPortionInStandardisedScore = Math.Pow(remainingCombo, 1 + ScoreProcessor.COMBO_EXPONENT);
|
||||
|
||||
double lowerEstimateOfComboPortionInStandardisedScore
|
||||
double scoreBasedEstimateOfComboPortionInStandardisedScore
|
||||
= maximumOccurrencesOfLongestCombo * comboPortionFromLongestComboInStandardisedScore
|
||||
+ remainingComboPortionInStandardisedScore;
|
||||
|
||||
// Compute approximate upper estimate new score for that play.
|
||||
// This time, divide the remaining combo among remaining objects equally to achieve longest possible combo lengths.
|
||||
// There is no rigorous proof that doing this will yield a correct upper bound, but it seems to work out in practice.
|
||||
remainingComboPortionInScoreV1 = comboPortionInScoreV1 - comboPortionFromLongestComboInScoreV1;
|
||||
double remainingCountOfObjectsGivingCombo = maximumLegacyCombo - score.MaxCombo - score.Statistics.GetValueOrDefault(HitResult.Miss);
|
||||
// Because we assumed all combos were equal, `remainingComboPortionInScoreV1`
|
||||
@ -449,7 +448,17 @@ namespace osu.Game.Database
|
||||
// we can skip adding the 1 and just multiply by x ^ 0.5.
|
||||
remainingComboPortionInStandardisedScore = remainingCountOfObjectsGivingCombo * Math.Pow(lengthOfRemainingCombos, ScoreProcessor.COMBO_EXPONENT);
|
||||
|
||||
double upperEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore;
|
||||
double objectCountBasedEstimateOfComboPortionInStandardisedScore = comboPortionFromLongestComboInStandardisedScore + remainingComboPortionInStandardisedScore;
|
||||
|
||||
// Enforce some invariants on both of the estimates.
|
||||
// In rare cases they can produce invalid results.
|
||||
scoreBasedEstimateOfComboPortionInStandardisedScore =
|
||||
Math.Clamp(scoreBasedEstimateOfComboPortionInStandardisedScore, 0, maximumAchievableComboPortionInStandardisedScore);
|
||||
objectCountBasedEstimateOfComboPortionInStandardisedScore =
|
||||
Math.Clamp(objectCountBasedEstimateOfComboPortionInStandardisedScore, 0, maximumAchievableComboPortionInStandardisedScore);
|
||||
|
||||
double lowerEstimateOfComboPortionInStandardisedScore = Math.Min(scoreBasedEstimateOfComboPortionInStandardisedScore, objectCountBasedEstimateOfComboPortionInStandardisedScore);
|
||||
double upperEstimateOfComboPortionInStandardisedScore = Math.Max(scoreBasedEstimateOfComboPortionInStandardisedScore, objectCountBasedEstimateOfComboPortionInStandardisedScore);
|
||||
|
||||
// Approximate by combining lower and upper estimates.
|
||||
// As the lower-estimate is very pessimistic, we use a 30/70 ratio
|
||||
|
Loading…
Reference in New Issue
Block a user