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

Remove estimations where score data is available for osu! difficulty calculations #27691

Merged
merged 41 commits into from
Oct 22, 2024

Conversation

Finadoggie
Copy link
Contributor

@Finadoggie Finadoggie commented Mar 22, 2024

This PR replaces estimations with score data where applicable for Lazer scores. More specifically, it…

  1. Modifies estimations for slider breaks
  2. Removes estimations for slider end drops

For removing slider break estimations:

  • HitResult.Miss includes both misses and slider head misses, accounting for two types of combo breaks
  • HitResult.LargeTickMiss includes dropped reverse arrows and dropped slider ticks
  • For Lazer scores, large tick misses are deliberately capped to avoid large amounts of them nuking a score, as buzz sliders and high slider tick rates can easily reward players with numerous amounts of large tick misses
  • Scores with no large tick misses will have no estimated misses, removing an issue where lazer scores were unfairly punished for theoretical slider breaks that were demonstrably not present

For removing slider end drop estimations:

  • Slider end drops and large tick misses are used in place of estimated slider end drops
  • Calculations which use estimated slider end drops are otherwise left untouched

Notes:

  • Classic mod is not being considered as ppy has previously said that pp for classic mod should match stable

=== original post ===
(contains outdated info)

On non-CL lazer scores, extra slider data means certain estimations are no longer required.

For effective miss count, HitResults.Miss + HitResults.LargeTickMiss is used.

For sliderends dropped, HitResults.SmallTickMiss is used.

To my understanding, LargeTickMiss includes slider ticks and reverse arrows, the two parts that can break your combo, while SmallTickMiss is for sliderends. Please correct me if I am wrong.

Very much open to discussion on if these should be weighed differently
@bdach
Copy link
Collaborator

bdach commented Mar 22, 2024

I am not sure the "available" score data in question can be used. We've previously expressed worry that this was going to spiral into two separate implementations of pp for classic and non-classic scores.

@Finadoggie
Copy link
Contributor Author

My bad, I was not aware of these conversations. Could you drop a link to them so I can read through them?

@bdach
Copy link
Collaborator

bdach commented Mar 22, 2024

#21938 would be a good starting point I guess

@Natelytle
Copy link
Contributor

Stan's decision of splitting the calculator into 2 parts for classic and non-classic is a direction I personally dislike, and the way Fina is doing it here is the way I would approach it myself. I don't see any harm in using the new metrics, and PP spiraling into 2 calculators should only happen if per note judgements ever become available, and prove to give much better results than without them. These new metrics are easily estimable, and the existing estimations can just be made harsher if they prove to give an advantage to stable scores.

@Givikap120
Copy link
Contributor

Why is this only using additional data if slideracc enabled?
Lazer scores with CL mods also have additional data that should be used to get more accurate result.

@Flamiii
Copy link

Flamiii commented Mar 22, 2024

The implications of a change like this are massive. If I set scores with sliderbreaks, I will only ever be punished more on lazer than I would be if I played on stable. I play on lazer as my main client, but a change like this on its own would make me consider moving back to stable because I don't want to play with something like this that is just a clear disadvantage. You could try tuning the estimations for stable scores to be more harsh, but I doubt people would be happy with that and I'm honestly not sure if that's even possible. The complexity to balance this kind of change is so high that I don't think it's worth the trouble to begin with.

@Finadoggie
Copy link
Contributor Author

@Flamiii you’re already at an objective disadvantage if you play on lazer. This arguably helps you since it means miss estimations can no longer assume you may have sliderbroke where you didn’t. Any non-cl 1 miss plays where the break was in the middle are buffed. Same for plays on hard slider maps where you broke but didn’t drop any sliderends.

@Flamiii
Copy link

Flamiii commented Mar 22, 2024

If by "objective disadvantage" you mean slideracc, I play on lazer because I'm certain that will be taken into account in the future (#27063). The scenario I'm thinking of which happens quite a bit is the one where a player misses near the end of a map but also sliderbreaks 2 times afterwards. In stable, this play is counted as a 1 miss and the sliderbreaks only negatively impact your accuracy. With these changes in lazer, this play would be treated as if it was a 3 miss. As far as I know, there really isn't a way to adjust stable's estimation to make this scenario balanced because the relevant information just isn't there. There are some situations where you're punished a bit less, but the scenario I mentioned is extremely common and you would be punished way more for playing on lazer with these changes.

Edit: It's also worth noting that using HitResults.LargeTickMiss to increase the effective miss count means that missing buzz sliders or any fast sliders with many sliderticks will be incredibly punishing. For example, misaiming a buzz slider with 5 repeats and missing it entirely would increase the effective miss count by 6, which would basically kill any score.

@Rekunan
Copy link

Rekunan commented Mar 22, 2024

there really isn't a way to adjust stable's estimation

There actually is a way, if you take a look at the function in question, we could, for example, lower fullComboThreshold to make it assume there were more misses than before, and if you look at the changes in this pr, this function is only used if CL with slideracc is on.

misaiming a buzz slider with 5 repeats ... would basically kill any score

This is a valid point, however, I can assure you that future work will be done to account for this.

@Flamiii
Copy link

Flamiii commented Mar 22, 2024

There actually is a way, if you take a look at the function in question, we could, for example, lower fullComboThreshold to make it assume there were more misses than before, and if you look at the changes in this pr, this function is only used if CL with slideracc is on.

I see. As long as these changes are balanced properly, I have no issues with them.

@Finadoggie Finadoggie closed this Mar 22, 2024
@Finadoggie
Copy link
Contributor Author

shoot I hit the wrong button

@Finadoggie Finadoggie reopened this Mar 22, 2024
@Finadoggie
Copy link
Contributor Author

Finadoggie commented Mar 22, 2024

The scenario I'm thinking of which happens quite a bit is the one where a player misses near the end of a map but also sliderbreaks 2 times afterwards.

Ok but again this is already a thing. Sliderbreaks by missing the slider head are already counted as misses in lazer, so this scenario can already happen without any pp changes.

It's also worth noting that using HitResults.LargeTickMiss to increase the effective miss count means that missing buzz sliders or any fast sliders with many sliderticks will be incredibly punishing. For example, misaiming a buzz slider with 5 repeats and missing it entirely would increase the effective miss count by 6, which would basically kill any score.

I know, I'm more apprehensive about that part and wanted to spark discussion for that specifically.
Also buzz sliders are a lot more forgiving in lazer anyways so a late tap leading to complete score annihilation isn't something I'm worried about.

The crux of the matter is, some scores will be punished by being unable to slip through the cracks. That's inevitable. The gain of scores no longer being undeservingly punished from estimations counteracts this and, at least in my personal opinion, is a fair trade off.

Finadoggie and others added 4 commits March 23, 2024 14:27
As per suggestion by givikap, I was not aware that non-legacy cl scores stored this data
After letting the comments @Flamiii left brew for a while, I realized they were very much right about the buzz slider thing. As such, I've implemented a quick and dirty untested fix that will hopefully have zero unintended side-effects :)

I don't see this as a permanent or final solution yet. There's definitely some potential issues/inaccuracies that could arise with maps like Notch Hell or IOException's Black Rover, but afaik this implementation would not cause any issues that stable doesn't already have.
@Finadoggie Finadoggie marked this pull request as draft April 11, 2024 17:28
@peppy
Copy link
Member

peppy commented Oct 21, 2024

!diffcalc

@Finadoggie
Copy link
Contributor Author

Updated the original post to hopefully make the goals of the PR more clear

@Finadoggie
Copy link
Contributor Author

@smoogipoo multiple people have told me that a unit test is no longer required since I've cleaned up and simplified everything, do you still want me to write one?

slightly miffed by the lack of build errors but oh well
@smoogipoo
Copy link
Contributor

smoogipoo commented Oct 21, 2024

This is honestly harder to read now than before, now that it has 4 separate places individually dealing with "usingClassicSliderAccuracy".

@minisbett
Copy link
Contributor

This is honestly harder to read now than before, now that it has 4 separate places individually dealing with "usingClassicSliderAccuracy".

I honestly think it's fine, at least to me the usingClassicSliderAccuracy checks seem intuitive, even if used in multiple places. Do you think separating calculateEffectiveMisscount for slider-acc and non-slider-acc makes sense?

@pull-request-size pull-request-size bot added size/M and removed size/S labels Oct 21, 2024
@peppy peppy added the next release Pull requests which are almost there. We'll aim to get them in the next release, but no guarantees! label Oct 21, 2024
Comment on lines +78 to +79
{
double fullComboThreshold = attributes.MaxCombo - countSliderEndsDropped;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
{
double fullComboThreshold = attributes.MaxCombo - countSliderEndsDropped;
{
// Consider that full combo is maximum combo minus dropped slider tails since they don't contribute to combo but also don't break it
double fullComboThreshold = attributes.MaxCombo - countSliderEndsDropped;

A comment to stay consistent with the other if-branch would be nice

Copy link
Member

Choose a reason for hiding this comment

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

Repeating the same comment is redundant

Copy link
Contributor

Choose a reason for hiding this comment

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

fair just wanted to mention it incase it is desired

/// <summary>
/// Missed slider ticks that includes missed reverse arrows. Will only be correct on non-classic scores
/// </summary>
private int countSliderTickMiss;
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like renaming this to countLargeTickMiss makes it alot clearer what it is because despite the comment reading it anywhere else in code could put up confusion... I don't see why it shouldn't be named 1:1 after the hit result it represents.

Copy link
Member

Choose a reason for hiding this comment

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

"LargeTick" does not represent anything on its own unless you're aware of what it actually contains

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like it's still a more meaningful term since it matches the hit result and also the "Large Tick Misses" field I added to osu-tools GUI and soon to CLI.

/// <summary>
/// Amount of missed slider tails that don't break combo. Will only be correct on non-classic scores
/// </summary>
private int countSliderEndsDropped;
Copy link
Contributor

Choose a reason for hiding this comment

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

Since slider-related code uses the term "tail" (and the hit result does too), I'd suggest renaming this to countSliderTailMiss or countSliderTailsDropped. I'd prefer to consider "miss" the opposite of hit even for slider tails but calling them "dropped" is arguable

Comment on lines +169 to +184
double estimateImproperlyFollowedDifficultSliders;

if (usingClassicSliderAccuracy)
{
// When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders
int maximumPossibleDroppedSliders = totalImperfectHits;
estimateImproperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
}
else
{
// We add tick misses here since they too mean that the player didn't follow the slider properly
// We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly
estimateImproperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders);
}

double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
double estimateImproperlyFollowedDifficultSliders;
if (usingClassicSliderAccuracy)
{
// When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders
int maximumPossibleDroppedSliders = totalImperfectHits;
estimateImproperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
}
else
{
// We add tick misses here since they too mean that the player didn't follow the slider properly
// We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly
estimateImproperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders);
}
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - estimateImproperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor;
double improperlyFollowedDifficultSliders;
if (usingClassicSliderAccuracy)
{
// When the score is considered classic (regardless if it was made on old client or not) we consider all missing combo to be dropped difficult sliders
int maximumPossibleDroppedSliders = totalImperfectHits;
improperlyFollowedDifficultSliders = Math.Clamp(Math.Min(maximumPossibleDroppedSliders, attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders);
}
else
{
// We add tick misses here since they too mean that the player didn't follow the slider properly
// We however aren't adding misses here because missing slider heads has a harsh penalty by itself and doesn't mean that the rest of the slider wasn't followed properly
improperlyFollowedDifficultSliders = Math.Min(countSliderEndsDropped + countSliderTickMiss, estimateDifficultSliders);
}
double sliderNerfFactor = (1 - attributes.SliderFactor) * Math.Pow(1 - improperlyFollowedDifficultSliders / estimateDifficultSliders, 3) + attributes.SliderFactor;

The variable name is quite long and since it's not always estimated so it doesn't necessarily have a valuable meaning besides making the code more straining to read.

Copy link
Member

@stanriders stanriders Oct 21, 2024

Choose a reason for hiding this comment

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

I prefer longer variable names that encapsulate what it actually is instead of hiding the fact that it can be estimated (and will be for billions of scores)

Copy link
Contributor

Choose a reason for hiding this comment

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

hmm idk, I feel like there isn't much difference between "estimated" when it can or cannot be vs. not saying it when it can or cannot be

Copy link

@tsunyoku
Copy link
Member

we'll need a new sheet following recent commits, 98800fe will have resulted in value changes even before StanR's refactors

@smoogipoo
Copy link
Contributor

!diffcalc

Copy link

@smoogipoo
Copy link
Contributor

@ppy/osu-pp-committee please check above sheet!

@stanriders
Copy link
Member

@smoogipoo all good!

Copy link
Contributor

@apollo-dw apollo-dw left a comment

Choose a reason for hiding this comment

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

one more for luck :]

Copy link
Contributor

@smoogipoo smoogipoo left a comment

Choose a reason for hiding this comment

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

Reads fine to me now

@smoogipoo smoogipoo merged commit 71eb712 into ppy:master Oct 22, 2024
9 of 13 checks passed
@Finadoggie Finadoggie deleted the estimation-removal branch October 24, 2024 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:difficulty next release Pull requests which are almost there. We'll aim to get them in the next release, but no guarantees! size/M
Projects
Status: Deployed
Development

Successfully merging this pull request may close these issues.