Skip to main content
The scoring system handles all aspects of score tracking, from hit result statistics to performance point calculations. It supports both modern and legacy scoring formats.

Score Information

The ScoreInfo class represents a single score (play) on a beatmap:
public class ScoreInfo : RealmObject, IHasGuidPrimaryKey
{
    public Guid ID { get; set; }
    public BeatmapInfo? BeatmapInfo { get; set; }
    public string BeatmapHash { get; set; }
    public RulesetInfo Ruleset { get; set; }
    public long TotalScore { get; set; }
    public double Accuracy { get; set; }
    public ScoreRank Rank { get; set; }
    public DateTimeOffset Date { get; set; }
}

Core Score Properties

  • TotalScore: Total points including mod multipliers
  • TotalScoreWithoutMods: Base score without mod multipliers
  • LegacyTotalScore: Preserved score for legacy plays
public long TotalScore { get; set; }
public long TotalScoreWithoutMods { get; set; }
public long? LegacyTotalScore { get; set; }
  • Accuracy: Percentage accuracy (0.0 to 1.0)
  • MaxCombo: Highest combo achieved
  • Combo: Current combo (during gameplay)
public double Accuracy { get; set; }
public int MaxCombo { get; set; }
public int Combo { get; set; }
  • PP: Performance points awarded
  • Ranked: Whether PP is counted toward player ranking
public double? PP { get; set; }
public bool Ranked { get; set; }

Score Ranks

Scores are assigned letter grades based on accuracy and performance:
1

F Rank

Failed to complete the beatmap.
ScoreRank.F = -1
2

D Rank

Completed with low accuracy.
ScoreRank.D
3

C Rank

Decent performance.
ScoreRank.C
4

B Rank

Good performance.
ScoreRank.B
5

A Rank

Great performance.
ScoreRank.A
6

S Rank

Excellent performance (no misses).
ScoreRank.S
7

SH Rank (Silver S)

S rank achieved with Hidden mod.
ScoreRank.SH
8

X Rank (SS)

Perfect accuracy (100%).
ScoreRank.X
9

XH Rank (Silver SS)

Perfect accuracy with Hidden mod.
ScoreRank.XH

Rank Display

Ranks are typically displayed as letters (D, C, B, A, S, SS) with silver variants for Hidden mod plays.

Hit Statistics

Scores store detailed hit result statistics:
public Dictionary<HitResult, int> Statistics { get; set; }
public Dictionary<HitResult, int> MaximumStatistics { get; set; }

Hit Results

Different hit results contribute to score differently:
  • Great: Perfect timing
  • Perfect: Exact timing (in some modes)
  • Ok/Good: Slightly off timing
  • Meh: Poor timing but still hit
  • Miss: Missed the object entirely
  • SmallTickMiss: Missed a slider tick
  • LargeTickMiss: Missed a large slider tick
  • SliderTailHit: Hit the slider end
  • SmallTickHit: Hit a slider tick
  • LargeTickHit: Hit a large slider tick
  • SmallBonus: Small bonus points (spinner ticks)
  • LargeBonus: Large bonus points (spinner bonus)

Statistics Display

The system provides formatted statistics for display:
public IEnumerable<HitResultDisplayStatistic> GetStatisticsForDisplay()
{
    foreach (var r in Ruleset.CreateInstance().GetHitResultsForDisplay())
    {
        int value = Statistics.GetValueOrDefault(r.result);
        
        // Special handling for different result types
        switch (r.result)
        {
            case HitResult.SmallTickHit:
            case HitResult.LargeTickHit:
            case HitResult.SliderTailHit:
                if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0)
                    yield return new HitResultDisplayStatistic(
                        r.result, value, count, r.displayName);
                break;
                
            default:
                yield return new HitResultDisplayStatistic(
                    r.result, value, null, r.displayName);
                break;
        }
    }
}
Statistics are stored as JSON in the database and deserialized on demand for performance.

Score Versioning

The scoring system has evolved over time. Scores track their version:
public int TotalScoreVersion { get; set; } = LegacyScoreEncoder.LATEST_VERSION;
public bool IsLegacyScore { get; set; }

Version Tracking

The TotalScoreVersion indicates which scoring algorithm was used. This allows:
  • Recalculating old scores with new algorithms
  • Maintaining historical accuracy
  • Gradual migration to new systems
Scores with BackgroundReprocessingFailed set to true have failed conversion and won’t be repeatedly processed:
public bool BackgroundReprocessingFailed { get; set; }

Mods Applied

Scores track which mods were active during the play:
public Mod[] Mods { get; set; }
public APIMod[] APIMods { get; set; }
Mods are stored as JSON and affect:
  • Score multiplier
  • Difficulty
  • PP calculation
  • Rank eligibility
Mods are stored as JSON in the database:
public string ModsJson { get; set; } = string.Empty;
Mods are automatically deserialized when accessed:
var mods = scoreInfo.Mods; // Returns Mod[]
var apiMods = scoreInfo.APIMods; // Returns APIMod[]

User Information

Scores track the player who set them:
public RealmUser RealmUser { get; set; }
public APIUser User { get; set; }
public int UserID => RealmUser.OnlineID;

User Data

The system maintains both realm-stored user data and API user data for compatibility.

Beatmap Association

Scores are linked to beatmaps via hash matching:
public BeatmapInfo? BeatmapInfo { get; set; }
public string BeatmapHash { get; set; } = string.Empty;
Scores may have a null BeatmapInfo if:
  • The beatmap was deleted
  • The beatmap was modified (hash changed)
  • The beatmap isn’t available locally
The BeatmapHash preserves the association even when BeatmapInfo is null.

Online Identifiers

Scores can have multiple online IDs for different systems:
public long OnlineID { get; set; } = -1;        // Modern solo_scores table
public long LegacyOnlineID { get; set; } = -1;  // Legacy osu_scores_*_high tables

Online ID Migration

  • OnlineID: Used by osu!lazer and modern web
  • LegacyOnlineID: Used by osu!stable and legacy systems

Replay Data

Scores can have associated replay files:
public IList<RealmNamedFileUsage> Files { get; } = null!;
public string Hash { get; set; } = string.Empty;
public bool HasOnlineReplay { get; set; }

Replay Access

1

Check Availability

Verify if a replay exists:
if (score.HasOnlineReplay || score.Files.Count > 0)
{
    // Replay available
}
2

Load Replay

Load the replay file from storage using the file hash.
3

Play Replay

Use the replay data to recreate the play.

Hit Events

During gameplay, detailed hit events are tracked:
public List<HitEvent> HitEvents { get; set; } = new List<HitEvent>();
Hit events provide frame-by-frame accuracy data for advanced analysis but are not persisted to the database.

Score State

Scores track various state flags:
public bool Passed { get; set; } = true;  // Whether the play was completed
public bool DeletePending { get; set; }    // Marked for deletion
public int? Position { get; set; }         // Leaderboard position

Pauses

The system tracks when players paused during gameplay:
public IList<int> Pauses { get; } = null!;
Frequent pausing may affect score validity in competitive contexts.

Client Version

Scores record which version of the client was used:
public string ClientVersion { get; set; } = string.Empty;
This helps track:
  • Compatibility issues
  • Feature availability
  • Bug investigation

Score Cloning

Scores can be deep-cloned for various purposes:
public ScoreInfo DeepClone()
{
    var clone = (ScoreInfo)this.Detach().MemberwiseClone();
    
    clone.Statistics = new Dictionary<HitResult, int>(clone.Statistics);
    clone.MaximumStatistics = new Dictionary<HitResult, int>(clone.MaximumStatistics);
    clone.HitEvents = new List<HitEvent>(clone.HitEvents);
    clone.clearAllMods();
    clone.ModsJson = ModsJson;
    
    return clone;
}

Clone Use Cases

  • Displaying scores in UI without modifying originals
  • Simulating score changes
  • Testing score calculations

Score Import/Export

The ScoreManager handles score operations:
Import scores from replay files:
scoreManager.Import("path/to/replay.osr");
Export scores to replay files for sharing.
Download scores from online using ScoreModelDownloader.

Legacy Score Handling

The system supports legacy osu!stable scores:

Legacy Support

  • LegacyScoreDecoder: Reads old replay formats
  • LegacyScoreEncoder: Writes old replay formats
  • DatabasedLegacyScoreDecoder: Handles database-stored legacy scores
public bool IsLegacyScore { get; set; }
public long? LegacyTotalScore { get; set; }

Display Formatting

Scores provide formatted display values:
public LocalisableString DisplayAccuracy => Accuracy.FormatAccuracy();
public override string ToString() => this.GetDisplayTitle();
Use these formatted values in UI to ensure consistent display across the game.

Build docs developers (and LLMs) love