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 LegacyDifficultyControlPoint / LegacyBpmMultiplier #24738

Merged
merged 8 commits into from
Sep 15, 2023

Conversation

peppy
Copy link
Member

@peppy peppy commented Sep 7, 2023

As pointed out in discussion, the flow of data surrounding these legacy pieces is ugly. It only got worse after difficulty data has been moved to hit objects themselves (with the worst code smell being LegacyBpmMultiplier existing in HitObject.

This PR removes all of this, instead locally computing the required precision-adjusted values where necessary. It also moves the still-required GenerateTicks to DifficultyControlPoint itself (we basically need to know if the BPM at point-in-time was NaN, but don't want to convey that in TimingControlPoint, so this is required to exist somewhere).

To make this work, I've also removed the precision limitations of SliderVelocityMultiplier. This doesn't seem to have an adverse effect on the editor luckily, so should play quite well.

This PR fixes a few issues which seemingly went silently unnoticed:

  • SliderVelocityMultiplier precision was not transferred to HitObject when the value is transferred
  • LegacyBpmMultiplier was not being transferred at all in OsuBeatmapConverter, so it was not being used anywhere (potentially affected difficulty calculation and gameplay)

I haven't added test coverage for these two cases because I'm not sure where to start for that.

@peppy peppy added the area:beatmap-parsing .osu file format parsing label Sep 7, 2023
@bdach
Copy link
Collaborator

bdach commented Sep 7, 2023

Pushed a few menial fixups but looks good to me otherwise. That said, I haven't paid this much attention to the investigation, so definitely will want a @smoogipoo confirm on this one.

bdach
bdach previously approved these changes Sep 7, 2023
@bdach bdach requested a review from smoogipoo September 7, 2023 17:01
@bdach
Copy link
Collaborator

bdach commented Sep 11, 2023

Tried to do sheets for this one.

The taiko changes are slightly worrying as I wouldn't expect anything to change there, but I have done zero investigation what that is so far. Could even be that I didn't set the generation up correctly on my machine, so reader beware.

@bdach bdach dismissed their stale review September 11, 2023 21:11

pending check of the above

@smoogipoo
Copy link
Contributor

smoogipoo commented Sep 12, 2023

Fwiw, taiko is where I'd expect most of the differences to surface, because the drumroll conversion code relies on super precise errors.

@peppy
Copy link
Member Author

peppy commented Sep 12, 2023

FWIW I'd expect there to be changes as I fixed several actual bugs. We'll need to confirm they are correct to our expectations as a result.

@bdach
Copy link
Collaborator

bdach commented Sep 12, 2023

Well, there is at least one breakage on the mania side when compared with stable.

/b/2364269 with DT, 10K:

2364269.mp4

Appears to be caused by minuscule fluctuations of beatLength in the pattern generator:

10,16c10,16
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 26279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 27779: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 29279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 30029: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 31341: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 37904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 38091: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
---
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 26279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 27779: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 29279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 30029: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 31341: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 37904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 38091: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
74,79c74,79
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71279: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71654: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72029: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72404: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72779: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 73904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
---
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71279: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71654: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72029: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72404: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72779: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 73904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
99,104c99,104
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86841: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 87779: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89841: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 90779: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
---
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86841: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 87779: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89841: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 90779: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
114,120c114,120
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 26279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 27779: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 29279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 30029: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 31341: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 37904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 38091: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
---
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 26279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 27779: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 29279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 30029: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 31341: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 37904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 38091: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
178,183c178,183
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71279: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71654: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72029: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72404: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72779: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 73904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325684
---
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71279: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 71654: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72029: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72404: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 72779: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 73904: DistanceObjectPatternGenerator calculated beatLength = 249.99999046325686
203,208c203,208
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86841: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 87779: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89279: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89841: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
< osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 90779: DistanceObjectPatternGenerator calculated beatLength = 499.9999809265137
\ No newline at end of file
---
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 86841: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 87779: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89279: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 89841: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
> osu.Game.Rulesets.Objects.Legacy.Osu.ConvertSlider @ 90779: DistanceObjectPatternGenerator calculated beatLength = 499.99998092651373
\ No newline at end of file

The underlying cause is probably that taking the reciprocal of LegacyBpmMultiplier as SliderVelocity is not lossless.

Haven't looked at the taiko cases yet, but I'm not sure doing so is worth the non-trivial effort anymore given that we know that this change technically breaks stuff now... (getting to the bottom of the above took me ~2hrs already).

Basically matching the old code more closely to avoid too much precision
from doing math in a slightly different way.
@peppy
Copy link
Member Author

peppy commented Sep 12, 2023

I've adjusted things to the point it looks to match your output (to within ToString() imprecisions as discussed).

I think we can probably use this as a way to test github actions diffcalc runs, so hold off on the manual run until I give that a try (@smoogipoo has been working on updating the action and I'll re-deploy my local runner).

@bdach
Copy link
Collaborator

bdach commented Sep 14, 2023

Map mentioned above looks to be back to the reference state now.

As discussed elsewhere, going to re-run the sheets overnight to verify.

bdach
bdach previously approved these changes Sep 15, 2023
Comment on lines 32 to 42
switch (rulesetShortName)
{
case "taiko":
case "mania":
bpmMultiplier = sliderVelocityAsBeatLength < 0 ? Math.Clamp((float)-sliderVelocityAsBeatLength, 10, 10000) / 100.0 : 1;
break;

default:
bpmMultiplier = sliderVelocityAsBeatLength < 0 ? Math.Clamp((float)-sliderVelocityAsBeatLength, 10, 1000) / 100.0 : 1;
break;
}
Copy link
Contributor

@smoogipoo smoogipoo Sep 15, 2023

Choose a reason for hiding this comment

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

If this is intended as a method for all rulesets to use, do you think we should instead specify explicit osu and catch branches and leave the default branch as simple/lossless as possible?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Dunno. It may be sunk cost fallacy from all the overnight sheet running but I tend to think that this is okay. The fact that we're using legacy .osu format and encoding slider velocity into files in one of the most opaque ways I can imagine also contributes to this feeling - like if we were redoing this to be simpler, then we'd redefine the actual way the thing is stored to not require the weird division by 100.

Can wait until further work in my opinion.

Copy link
Contributor

@smoogipoo smoogipoo Sep 15, 2023

Choose a reason for hiding this comment

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

I mean it from a different angle - this method is essentially a legacy method that pollutes an interface intended for any ruleset to use.

I can imagine any ruleset consumer would use this as a shortcut for timingPoint.BeatLength * SliderVelocityMultiplier without understanding the consequences of this - the precision and the weird clamping behaviour.

How do you explain the existence and purpose of this method to a ruleset consumer? I believe it should be more properly indicated because the xmldoc doesn't do it justice.

Copy link
Collaborator

@bdach bdach Sep 15, 2023

Choose a reason for hiding this comment

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

I see.

For one, it has to be public API, so that's that (unless we expose osu.Game internals to rulesets, but I'd rather not), but what'd work for you? More xmldoc? Moving to extension method? Marking as deprecated with inline disables?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've moved it to an extension method. I've added more xmldoc, and I've added a throw on non-legacy use.

/// Introduces floating-point errors to post-multiplied beat length for legacy rulesets that depend on it.
/// You should definitely not use this unless you know exactly what you're doing.
/// </summary>
public static double GetPrecisionAdjustedBeatLength(this IHasSliderVelocity hasSliderVelocity, TimingControlPoint timingControlPoint, string rulesetShortName)
Copy link
Contributor

Choose a reason for hiding this comment

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

I still don't like this being an extension method. We've gone from:

I don't see a reason why rulesets wouldn't use this method, and it'll cause precision loss
To:
I don't see a reason why rulesets wouldn't use this method, but it'll crash if they do

My suggestion was very explicit: have this return BeatLength * SliderVelocity for non-legacy rulesets. A second option I would be okay with is this being an explicit static class call LegacyRulesetExtensions.Get...().

I don't know. I don't think I'm being as unreasonable here as I'm being brought to believe. I defer to a 3rd opinion.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would be okay with is this being an explicit static class call LegacyRulesetExtensions.Get...().

This sounds like a reasonable middle ground... @peppy?

Copy link
Member Author

@peppy peppy Sep 15, 2023

Choose a reason for hiding this comment

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

Whatever works. I'm fine with any implementation as long as the method name remains intact, and this only works for legacy rulesets.

We discussed IRL and my rationale against making this work for every ruleset is that it would hide the floating point precision hacks in a seemingly benignly named method. Which is not great for readability and would need inline comments at each usage to make sense (which doesn't make much sense).

@bdach bdach enabled auto-merge September 15, 2023 09:14
@bdach bdach disabled auto-merge September 15, 2023 10:01
@bdach bdach merged commit 1ae8665 into ppy:master Sep 15, 2023
@peppy peppy deleted the no-legacy-difficulty-control-point branch September 15, 2023 11:00
andy840119 added a commit to andy840119/karaoke that referenced this pull request Sep 24, 2023
Here's the breaking change in the IScrollingInfo: ppy/osu#24550
And should remove the LegacyBpmMultiplier: ppy/osu#24738
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:beatmap-parsing .osu file format parsing size/L
Projects
No open projects
Development

Successfully merging this pull request may close these issues.

3 participants