ppy/osu · error · InvalidOperationException

Total score conversion operation returned invalid total of {

Error message

Total score conversion operation returned invalid total of {convertedTotalScoreWithoutMods}

What it means

Thrown by the private convertFromLegacyTotalScore overload after the switch on ruleset legacy ID computes convertedTotalScoreWithoutMods. If the rounded result is negative, the math produced an invalid (negative) score and migration aborts rather than persisting a corrupt value. Sits just before the mod multiplier is applied.

Source

Thrown at osu.Game/Database/StandardisedScoreMigrationTools.cs:347

                    convertedTotalScoreWithoutMods = (long)Math.Round(
                        comboPortion * estimateComboProportionForCatch(attributes.MaxCombo, score.MaxCombo, score.Statistics.GetValueOrDefault(HitResult.Miss))
                        + dropletsPortion * dropletsHit
                        + bonusProportion);
                    break;

                case 3:
                    convertedTotalScoreWithoutMods = (long)Math.Round(
                        150000 * comboProportion
                        + 850000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy)
                        + bonusProportion);
                    break;

                default:
                    return (score.TotalScoreWithoutMods, score.TotalScore);
            }

            if (convertedTotalScoreWithoutMods < 0)
                throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScoreWithoutMods}");

            long convertedTotalScore = (long)Math.Round(convertedTotalScoreWithoutMods * modMultiplier);
            return (convertedTotalScoreWithoutMods, convertedTotalScore);
        }

        /// <summary>
        /// <para>
        /// For catch, the general method of calculating the combo proportion used for other rulesets is generally useless.
        /// This is because in stable score V1, catch has quadratic score progression,
        /// while in stable score V2, score progression is logarithmic up to 200 combo and then linear.
        /// </para>
        /// <para>
        /// This means that applying the naive rescale method to scores with lots of short combos (think 10x 100-long combos on a 1000-object map)
        /// by linearly rescaling the combo portion as given by score V1 leads to horribly underestimating it.
        /// Therefore this method attempts to counteract this by calculating the best case estimate for the combo proportion that takes all of the above into account.
        /// </para>
        /// <para>
        /// The general idea is that aside from the <paramref name="scoreMaxCombo"/> which the player is known to have hit,

View on GitHub (pinned to d9c73e12ad)

Solutions

  1. Inspect the failing score's inputs (comboProportion, score.Accuracy, bonusProportion) for out-of-range or NaN values; treat such scores as non-migratable.
  2. Clamp intermediate values to valid ranges (accuracy in [0,1], proportions in [0,1]) before the formula, or skip the score.
  3. If triggered by a specific ruleset's formula, fix the branch so it cannot go negative and add unit tests covering the edge inputs.

Example fix

// before
if (convertedTotalScoreWithoutMods < 0)
    throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScoreWithoutMods}");

// after (clamp + skip corrupt scores)
convertedTotalScoreWithoutMods = Math.Max(0, convertedTotalScoreWithoutMods);
if (!double.IsFinite(score.Accuracy) || score.Accuracy < 0 || score.Accuracy > 1)
{
    LogForModel(score, "Score accuracy out of range; skipping conversion.");
    return (score.TotalScoreWithoutMods, score.TotalScore);
}
Defensive patterns

Strategy: validation

Validate before calling

if (!double.IsFinite(score.Accuracy) || score.Accuracy < 0 || score.Accuracy > 1
    || double.IsNaN(comboProportion) || double.IsNaN(bonusProportion))
{ /* skip corrupt score, keep legacy totals */ return; }

Type guard

static bool IsValidScoreInput(ScoreInfo s)
    => double.IsFinite(s.Accuracy) && s.Accuracy >= 0 && s.Accuracy <= 1
       && s.MaxCombo >= 0;

Try / catch

try { convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); }
catch (InvalidOperationException ex) when (ex.Message.Contains("invalid total"))
{ /* quarantine score, log inputs for diagnosis */ }

Prevention

When it happens

Trigger: A legacy score whose comboProportion / accuracy / bonusProportion inputs produce a negative sum after rounding — e.g. score.Accuracy or MaxCombo values that are out of expected range, or an arithmetic edge case in the per-ruleset formula branches (cases 1-3).

Common situations: Bulk score migration over old/corrupt score data; scores with anomalous statistics (negative counts, NaN accuracy); a newly added ruleset formula branch with a sign error exposed by real data.

Related errors


AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13). Data as JSON: /api/errors/601f697ea61c46c2. Report an issue: GitHub.