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

Add difficulty attributes to facilitate conversion from legacy score, and convert existing scores #24072

Merged
merged 44 commits into from
Jul 5, 2023

Conversation

smoogipoo
Copy link
Contributor

@smoogipoo smoogipoo commented Jun 28, 2023

Resolves #23756

  • Adds three new difficulty attributes, for every beatmap/mod/ruleset combo - LegacyAccuracyScore, LegacyComboScore, and LegacyBonusScoreRatio. These are used by osu-queue-score-statistics to import from the high score tables.
  • Adds ScoreInfo.LegacyTotalScore. This is something we have in SoloScoreInfo too, which is stored server side. Its primary purpose is to not mangle ScoreInfo.TotalScore for if we have to re-convert scores again in the future (makes the process a bit simpler).
  • Adds ScoreInfo.Version. This doesn't track the version that's stored in the replay file, but indicates whether a score is out-of-date. Whether the "version" stored in the replay file should track this value is something to be considered for another day because it has wider-reaching implications (e.g. exporting from lazer and importing into stable).
  • Adds a process to BackgroundBeatmapProcessor that converts from ScoreInfo.LegacyTotalScore.
  • Adds the legacy score processors, which simulate osu-stable gameplay mechanics to determine the difficulty attribute values.

Some sample leaderboards can be found here (of top-1000 users only): https://docs.google.com/spreadsheets/d/1Ogpfa5PhMNtKOYwrxt7n1zAQ5w_YVzkm79pC8tB7hpU/edit#gid=0

On my PC, this seems to process at around 30-50 scores per second, and does not process during gameplay:

2023-06-28.16-00-35.mp4

Firstly, this is intended to be a float division.

Secondly, dividing integers by 0 results in an exception, but dividing
non-zero floats by 0 results in +/- infinity which will be clamped to
the upper range.
In particular, this occurs when the beatmap has 1 hitobject (0 drain
length).
osu.Game/OsuGameBase.cs Outdated Show resolved Hide resolved
osu.Game/Scoring/ScoreInfo.cs Outdated Show resolved Hide resolved
/// <remarks>
/// This may not match the version stored in the replay files.
/// </remarks>
public int Version { get; set; } = LegacyScoreEncoder.LATEST_VERSION;
Copy link
Collaborator

Choose a reason for hiding this comment

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

So my main question is... what is this versioning, exactly? It's explicitly not the replay version (and I'm not asking why not, as per OP - the reasons are not given, but I'm sure you have them and they are good), however it's still using the replay versioning scheme. Is this versioning the scoring algorithm itself?

If yes, can we name this ScoreVersion or ScoringVersion or similar explicitly? Because I am 99% confident that with the Version name and the shared versioning scheme someone is going to mistake this for replay version and be very sad later. The prefix will hopefully make stop and think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would just renaming to ScoreVersion be enough for you? I don't really know how to explain it better than I did in the xmldoc, it's intended to represent the version of TotalScore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, so it is the versioning of the scoring algorithm itself, and as such I'd be totally fine with ScoreVersion or TotalScoreVersion as long as you are.

Comment on lines 126 to 127
if (score.ScoreInfo.IsLegacyScore)
score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I kinda think this should be in ScoreImporter.Populate()? As in

diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
index bf592d5988..c6461840aa 100644
--- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
+++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs
@@ -123,9 +123,6 @@ public Score Parse(Stream stream)
 
             PopulateAccuracy(score.ScoreInfo);
 
-            if (score.ScoreInfo.IsLegacyScore)
-                score.ScoreInfo.LegacyTotalScore = score.ScoreInfo.TotalScore;
-
             // before returning for database import, we must restore the database-sourced BeatmapInfo.
             // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception.
             score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo;
diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs
index eb57f9a560..5ada2a410d 100644
--- a/osu.Game/Scoring/ScoreImporter.cs
+++ b/osu.Game/Scoring/ScoreImporter.cs
@@ -89,7 +89,10 @@ protected override void Populate(ScoreInfo model, ArchiveReader? archive, Realm
             if (StandardisedScoreMigrationTools.ShouldMigrateToNewStandardised(model))
                 model.TotalScore = StandardisedScoreMigrationTools.GetNewStandardised(model);
             else if (model.IsLegacyScore)
+            {
+                model.LegacyTotalScore = model.TotalScore;
                 model.TotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(model, beatmaps());
+            }
         }
 
         /// <summary>

Keeps that stuff in one place rather than smeared across. Unless this needs to be strictly here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not fully sold on this, reason being there could be an external consumer of LegacyScoreDecoder that would have to be aware of it not setting LegacyTotalScore, but I've applied the change.

Copy link
Collaborator

@bdach bdach Jun 29, 2023

Choose a reason for hiding this comment

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

reason being there could be an external consumer of LegacyScoreDecoder that would have to be aware of it not setting LegacyTotalScore

I don't really find that argument overly convincing given that the initial way this was written was doing things half-and-half anyway. Like sure, LegacyTotalScore would be set for decoded scores with that initial code, but the decoder didn't do score conversion anyway, so it was just a second place to get the same (probably wrong) data as TotalScore would be post-decode sans-population.

osu.Game/Rulesets/Ruleset.cs Outdated Show resolved Hide resolved
osu.Game/BackgroundBeatmapProcessor.cs Show resolved Hide resolved
osu.Game.Rulesets.Taiko.Tests/TestSceneFlyingHits.cs Outdated Show resolved Hide resolved
osu.Game/Database/StandardisedScoreMigrationTools.cs Outdated Show resolved Hide resolved
Comment on lines +93 to +94
model.LegacyTotalScore = model.TotalScore;
model.TotalScore = StandardisedScoreMigrationTools.ConvertFromLegacyTotalScore(model, beatmaps());
Copy link
Collaborator

@bdach bdach Jun 29, 2023

Choose a reason for hiding this comment

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

Now that I look at this again - shouldn't this be setting model.Version (or, model.ScoreVersion, if that rename happens, I guess) to LegacyScoreEncoder.LATEST_VERSION, too? And probably unconditionally, while we're at it.

There's possibly an argument here that it might be something that the StandardisedScoreMigration classes should be doing themselves, except for the fact that right now they are mostly pure methods, and if you had them set the version, they would cease to be pure. Which is also a problem when you consider that a willy-nilly write to an attached realm model instance may throw if done outside of a transaction.

Copy link
Collaborator

@bdach bdach Jun 29, 2023

Choose a reason for hiding this comment

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

Okay never mind I guess this will happen anyway because of the default value applied to ScoreInfo.Version? Which feels... risky in the future (what if we forget to check after a scoring change N months down the line and accidentally assign a version that is too high for the score? e.g. when reimporting lazer scores?), but works for now, I guess.

@peppy peppy self-requested a review July 4, 2023 05:16
@@ -27,7 +27,6 @@ public override IEnumerable<(int attributeId, object value)> ToDatabaseAttribute
// Todo: osu!catch should not output star rating in the 'aim' attribute.
yield return (ATTRIB_ID_AIM, StarRating);
yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate);
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This attribute hasn't been removed, just moved from each ruleset down to the base DifficultyAttributes class.

The reasoning for why it was this way is that, historically, some rulesets (e.g. mania) didn't store max combo. This has changed over the years and we now store max combo for all rulesets, but this code hadn't been adjusted in line with that.

Comment on lines 980 to 981
// Set a sane default while background processing runs.
score.LegacyTotalScore = score.TotalScore;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a note, it's not a "default" - it's required.

Copy link
Member

Choose a reason for hiding this comment

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

Have adjusted the comment.

@peppy
Copy link
Member

peppy commented Jul 5, 2023

Let's move forward, and get this into dev hands for local testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Scores imported from stable use ScoreV1 value despite standardised scoring enabled
3 participants