diff --git a/assets b/assets
index 371cce1fd..2719d3fc1 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 371cce1fdc44914ddc3a5327e996cece4e676715
+Subproject commit 2719d3fc1d8f5d0cbafae8d27141d6c471148482
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index a945c10c5..d0009f95b 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -220,6 +220,8 @@ class InitState extends FlxState
       {
         storyMode: false,
         title: "CUM SONG",
+        songId: "cum",
+        difficultyId: "hard",
         isNewHighscore: true,
         scoreData:
           {
@@ -227,7 +229,7 @@ class InitState extends FlxState
             tallies:
               {
                 sick: 130,
-                good: 25,
+                good: 70,
                 bad: 69,
                 shit: 69,
                 missed: 69,
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 0781e59b6..478a13269 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -3122,9 +3122,10 @@ class PlayState extends MusicBeatSubState
     var res:ResultState = new ResultState(
       {
         storyMode: PlayStatePlaylist.isStoryMode,
+        songId: currentChart.song.id,
+        difficultyId: currentDifficulty,
         title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
         prevScoreData: prevScoreData,
-        difficultyId: currentDifficulty,
         scoreData:
           {
             score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index ee7c8eade..79880038d 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -21,6 +21,7 @@ import funkin.audio.FunkinSound;
 import flixel.util.FlxGradient;
 import flixel.util.FlxTimer;
 import funkin.save.Save;
+import funkin.play.scoring.Scoring;
 import funkin.save.Save.SaveScoreData;
 import funkin.graphics.shaders.LeftMaskShader;
 import funkin.play.components.TallyCounter;
@@ -34,7 +35,7 @@ class ResultState extends MusicBeatSubState
 {
   final params:ResultsStateParams;
 
-  final rank:ResultRank;
+  final rank:ScoringRank;
   final songName:FlxBitmapText;
   final difficulty:FlxSprite;
   final clearPercentSmall:ClearPercentCounter;
@@ -64,8 +65,7 @@ class ResultState extends MusicBeatSubState
 
     this.params = params;
 
-    rank = calculateRank(params);
-    // rank = SHIT;
+    rank = Scoring.calculateRank(params.scoreData) ?? SHIT;
 
     // We build a lot of this stuff in the constructor, then place it in create().
     // This prevents having to do `null` checks everywhere.
@@ -99,6 +99,8 @@ class ResultState extends MusicBeatSubState
 
   override function create():Void
   {
+    if (FlxG.sound.music != null) FlxG.sound.music.stop();
+
     // Reset the camera zoom on the results screen.
     FlxG.camera.zoom = 1.0;
 
@@ -327,6 +329,33 @@ class ResultState extends MusicBeatSubState
       }
     };
 
+    new FlxTimer().start(rank.getMusicDelay(), _ -> {
+      if (rank.hasMusicIntro())
+      {
+        // Play the intro music.
+        var introMusic:String = Paths.music(rank.getMusicPath() + '/' + rank.getMusicPath() + '-intro');
+        FunkinSound.load(introMusic, 1.0, false, true, true, () -> {
+          FunkinSound.playMusic(rank.getMusicPath(),
+            {
+              startingVolume: 1.0,
+              overrideExisting: true,
+              restartTrack: true,
+              loop: rank.shouldMusicLoop()
+            });
+        });
+      }
+      else
+      {
+        FunkinSound.playMusic(rank.getMusicPath(),
+          {
+            startingVolume: 1.0,
+            overrideExisting: true,
+            restartTrack: true,
+            loop: rank.shouldMusicLoop()
+          });
+      }
+    });
+
     refresh();
 
     super.create();
@@ -376,7 +405,8 @@ class ResultState extends MusicBeatSubState
 
           displayRankText();
 
-          new FlxTimer().start(2.0, _ -> {
+          // previously 2.0 seconds
+          new FlxTimer().start(0.25, _ -> {
             FlxTween.tween(clearPercentCounter, {alpha: 0}, 0.5,
               {
                 startDelay: 0.5,
@@ -444,28 +474,6 @@ class ResultState extends MusicBeatSubState
   {
     showSmallClearPercent();
 
-    FunkinSound.playMusic(rank.getMusicPath(),
-      {
-        startingVolume: 1.0,
-        overrideExisting: true,
-        restartTrack: true,
-        loop: rank.shouldMusicLoop()
-      });
-
-    FlxG.sound.music.onComplete = () -> {
-      if (rank == SHIT)
-      {
-        FunkinSound.playMusic('bluu',
-          {
-            startingVolume: 0.0,
-            overrideExisting: true,
-            restartTrack: true,
-            loop: true
-          });
-        FlxG.sound.music.fadeIn(10.0, 0.0, 1.0);
-      }
-    }
-
     switch (rank)
     {
       case PERFECT | PERFECT_GOLD:
@@ -478,7 +486,6 @@ class ResultState extends MusicBeatSubState
           bfPerfect.visible = true;
           bfPerfect.playAnimation('');
         }
-
       case EXCELLENT:
         if (bfExcellent == null)
         {
@@ -489,7 +496,6 @@ class ResultState extends MusicBeatSubState
           bfExcellent.visible = true;
           bfExcellent.playAnimation('Intro');
         }
-
       case GREAT:
         if (bfGreat == null)
         {
@@ -500,7 +506,6 @@ class ResultState extends MusicBeatSubState
           bfGreat.visible = true;
           bfGreat.playAnimation('Intro');
         }
-
       case SHIT:
         if (bfShit == null)
         {
@@ -511,7 +516,6 @@ class ResultState extends MusicBeatSubState
           bfShit.visible = true;
           bfShit.playAnimation('Intro');
         }
-
       case GOOD:
         if (bfGood == null)
         {
@@ -521,7 +525,6 @@ class ResultState extends MusicBeatSubState
         {
           bfGood.animation.play('fall');
           bfGood.visible = true;
-
           new FlxTimer().start((1 / 24) * 22, _ -> {
             // plays about 22 frames (at 24fps timing) after bf spawns in
             if (gfGood != null)
@@ -635,154 +638,39 @@ class ResultState extends MusicBeatSubState
 
     if (controls.PAUSE)
     {
-      FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
-      FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
-        {
-          onComplete: _ -> {
-            FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
-          }
-        });
+      if (FlxG.sound.music != null)
+      {
+        FlxTween.tween(FlxG.sound.music, {volume: 0}, 0.8);
+        FlxTween.tween(FlxG.sound.music, {pitch: 3}, 0.1,
+          {
+            onComplete: _ -> {
+              FlxTween.tween(FlxG.sound.music, {pitch: 0.5}, 0.4);
+            }
+          });
+      }
       if (params.storyMode)
       {
         openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
       }
       else
       {
-        openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(null, sticker)));
+        openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
+          {
+            {
+              fromResults:
+                {
+                  oldRank: Scoring.calculateRank(params?.prevScoreData),
+                  newRank: rank,
+                  songId: params.songId,
+                  difficultyId: params.difficultyId
+                }
+            }
+          }, sticker)));
       }
     }
 
     super.update(elapsed);
   }
-
-  public static function calculateRank(params:ResultsStateParams):ResultRank
-  {
-    // Perfect (Platinum) is a Sick Full Clear
-    var isPerfectGold = params.scoreData.tallies.sick == params.scoreData.tallies.totalNotes;
-    if (isPerfectGold) return ResultRank.PERFECT_GOLD;
-
-    // Else, use the standard grades
-
-    // Grade % (only good and sick), 1.00 is a full combo
-    var grade = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes;
-    // Clear % (including bad and shit). 1.00 is a full clear but not a full combo
-    var clear = (params.scoreData.tallies.totalNotesHit) / params.scoreData.tallies.totalNotes;
-
-    if (grade == Constants.RANK_PERFECT_THRESHOLD)
-    {
-      return ResultRank.PERFECT;
-    }
-    else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
-    {
-      return ResultRank.EXCELLENT;
-    }
-    else if (grade >= Constants.RANK_GREAT_THRESHOLD)
-    {
-      return ResultRank.GREAT;
-    }
-    else if (grade >= Constants.RANK_GOOD_THRESHOLD)
-    {
-      return ResultRank.GOOD;
-    }
-    else
-    {
-      return ResultRank.SHIT;
-    }
-  }
-}
-
-enum abstract ResultRank(String)
-{
-  var PERFECT_GOLD;
-  var PERFECT;
-  var EXCELLENT;
-  var GREAT;
-  var GOOD;
-  var SHIT;
-
-  public function getMusicPath():String
-  {
-    switch (abstract)
-    {
-      case PERFECT_GOLD:
-        return 'resultsPERFECT';
-      case PERFECT:
-        return 'resultsPERFECT';
-      case EXCELLENT:
-        return 'resultsNORMAL';
-      case GREAT:
-        return 'resultsNORMAL';
-      case GOOD:
-        return 'resultsNORMAL';
-      case SHIT:
-        return 'resultsSHIT';
-      default:
-        return 'resultsNORMAL';
-    }
-  }
-
-  public function shouldMusicLoop():Bool
-  {
-    switch (abstract)
-    {
-      case PERFECT_GOLD:
-        return true;
-      case PERFECT:
-        return true;
-      case EXCELLENT:
-        return true;
-      case GREAT:
-        return true;
-      case GOOD:
-        return true;
-      case SHIT:
-        return false;
-      default:
-        return false;
-    }
-  }
-
-  public function getHorTextAsset()
-  {
-    switch (abstract)
-    {
-      case PERFECT_GOLD:
-        return 'resultScreen/rankText/rankScrollPERFECT';
-      case PERFECT:
-        return 'resultScreen/rankText/rankScrollPERFECT';
-      case EXCELLENT:
-        return 'resultScreen/rankText/rankScrollEXCELLENT';
-      case GREAT:
-        return 'resultScreen/rankText/rankScrollGREAT';
-      case GOOD:
-        return 'resultScreen/rankText/rankScrollGOOD';
-      case SHIT:
-        return 'resultScreen/rankText/rankScrollLOSS';
-      default:
-        return 'resultScreen/rankText/rankScrollGOOD';
-    }
-  }
-
-  public function getVerTextAsset()
-  {
-    switch (abstract)
-    {
-      case PERFECT_GOLD:
-        return 'resultScreen/rankText/rankTextPERFECT';
-      case PERFECT:
-        return 'resultScreen/rankText/rankTextPERFECT';
-      case EXCELLENT:
-        return 'resultScreen/rankText/rankTextEXCELLENT';
-      case GREAT:
-        return 'resultScreen/rankText/rankTextGREAT';
-      case GOOD:
-        return 'resultScreen/rankText/rankTextGOOD';
-      case SHIT:
-        return 'resultScreen/rankText/rankTextLOSS';
-      default:
-        return 'resultScreen/rankText/rankTextGOOD';
-    }
-  }
 }
 
 typedef ResultsStateParams =
@@ -797,6 +685,8 @@ typedef ResultsStateParams =
    */
   var title:String;
 
+  var songId:String;
+
   /**
    * Whether the displayed score is a new highscore
    */
diff --git a/source/funkin/play/scoring/Scoring.hx b/source/funkin/play/scoring/Scoring.hx
index 744091b44..6155ec879 100644
--- a/source/funkin/play/scoring/Scoring.hx
+++ b/source/funkin/play/scoring/Scoring.hx
@@ -1,5 +1,7 @@
 package funkin.play.scoring;
 
+import funkin.save.Save.SaveScoreData;
+
 /**
  * Which system to use when scoring and judging notes.
  */
@@ -344,4 +346,178 @@ class Scoring
       return 'miss';
     }
   }
+
+  public static function calculateRank(scoreData:Null<SaveScoreData>):Null<ScoringRank>
+  {
+    if (scoreData == null) return null;
+
+    // Perfect (Platinum) is a Sick Full Clear
+    var isPerfectGold = scoreData.tallies.sick == scoreData.tallies.totalNotes;
+    if (isPerfectGold) return ScoringRank.PERFECT_GOLD;
+
+    // Else, use the standard grades
+
+    // Grade % (only good and sick), 1.00 is a full combo
+    var grade = (scoreData.tallies.sick + scoreData.tallies.good) / scoreData.tallies.totalNotes;
+    // Clear % (including bad and shit). 1.00 is a full clear but not a full combo
+    var clear = (scoreData.tallies.totalNotesHit) / scoreData.tallies.totalNotes;
+
+    if (grade == Constants.RANK_PERFECT_THRESHOLD)
+    {
+      return ScoringRank.PERFECT;
+    }
+    else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
+    {
+      return ScoringRank.EXCELLENT;
+    }
+    else if (grade >= Constants.RANK_GREAT_THRESHOLD)
+    {
+      return ScoringRank.GREAT;
+    }
+    else if (grade >= Constants.RANK_GOOD_THRESHOLD)
+    {
+      return ScoringRank.GOOD;
+    }
+    else
+    {
+      return ScoringRank.SHIT;
+    }
+  }
+}
+
+enum abstract ScoringRank(String)
+{
+  var PERFECT_GOLD;
+  var PERFECT;
+  var EXCELLENT;
+  var GREAT;
+  var GOOD;
+  var SHIT;
+
+  /**
+   * Delay in seconds
+   */
+  public function getMusicDelay():Float
+  {
+    switch (abstract)
+    {
+      case PERFECT_GOLD | PERFECT:
+        // return 2.5;
+        return 5.0;
+      case EXCELLENT:
+        return 1.75;
+      default:
+        return 3.5;
+    }
+  }
+
+  public function getMusicPath():String
+  {
+    switch (abstract)
+    {
+      case PERFECT_GOLD:
+        return 'resultsPERFECT';
+      case PERFECT:
+        return 'resultsPERFECT';
+      case EXCELLENT:
+        return 'resultsEXCELLENT';
+      case GREAT:
+        return 'resultsNORMAL';
+      case GOOD:
+        return 'resultsNORMAL';
+      case SHIT:
+        return 'resultsSHIT';
+      default:
+        return 'resultsNORMAL';
+    }
+  }
+
+  public function hasMusicIntro():Bool
+  {
+    switch (abstract)
+    {
+      case EXCELLENT:
+        return true;
+      case SHIT:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  public function getFreeplayRankIconAsset():Null<String>
+  {
+    switch (abstract)
+    {
+      case PERFECT_GOLD:
+        return 'PERFECTSICK';
+      case PERFECT:
+        return 'PERFECT';
+      case EXCELLENT:
+        return 'EXCELLENT';
+      case GREAT:
+        return 'GREAT';
+      case GOOD:
+        return 'GOOD';
+      case SHIT:
+        return 'LOSS';
+      default:
+        return null;
+    }
+  }
+
+  public function shouldMusicLoop():Bool
+  {
+    switch (abstract)
+    {
+      case PERFECT_GOLD | PERFECT | EXCELLENT | GREAT | GOOD:
+        return true;
+      case SHIT:
+        return false;
+      default:
+        return false;
+    }
+  }
+
+  public function getHorTextAsset()
+  {
+    switch (abstract)
+    {
+      case PERFECT_GOLD:
+        return 'resultScreen/rankText/rankScrollPERFECT';
+      case PERFECT:
+        return 'resultScreen/rankText/rankScrollPERFECT';
+      case EXCELLENT:
+        return 'resultScreen/rankText/rankScrollEXCELLENT';
+      case GREAT:
+        return 'resultScreen/rankText/rankScrollGREAT';
+      case GOOD:
+        return 'resultScreen/rankText/rankScrollGOOD';
+      case SHIT:
+        return 'resultScreen/rankText/rankScrollLOSS';
+      default:
+        return 'resultScreen/rankText/rankScrollGOOD';
+    }
+  }
+
+  public function getVerTextAsset()
+  {
+    switch (abstract)
+    {
+      case PERFECT_GOLD:
+        return 'resultScreen/rankText/rankTextPERFECT';
+      case PERFECT:
+        return 'resultScreen/rankText/rankTextPERFECT';
+      case EXCELLENT:
+        return 'resultScreen/rankText/rankTextEXCELLENT';
+      case GREAT:
+        return 'resultScreen/rankText/rankTextGREAT';
+      case GOOD:
+        return 'resultScreen/rankText/rankTextGOOD';
+      case SHIT:
+        return 'resultScreen/rankText/rankTextLOSS';
+      default:
+        return 'resultScreen/rankText/rankTextGOOD';
+    }
+  }
 }
diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx
index 934d6a4aa..7f25a8e01 100644
--- a/source/funkin/save/Save.hx
+++ b/source/funkin/save/Save.hx
@@ -1,15 +1,17 @@
 package funkin.save;
 
 import flixel.util.FlxSave;
-import funkin.save.migrator.SaveDataMigrator;
-import thx.semver.Version;
 import funkin.input.Controls.Device;
+import funkin.play.scoring.Scoring;
+import funkin.play.scoring.Scoring.ScoringRank;
 import funkin.save.migrator.RawSaveData_v1_0_0;
 import funkin.save.migrator.SaveDataMigrator;
+import funkin.save.migrator.SaveDataMigrator;
 import funkin.ui.debug.charting.ChartEditorState.ChartEditorLiveInputStyle;
 import funkin.ui.debug.charting.ChartEditorState.ChartEditorTheme;
-import thx.semver.Version;
 import funkin.util.SerializerUtil;
+import thx.semver.Version;
+import thx.semver.Version;
 
 @:nullSafety
 class Save
@@ -492,6 +494,11 @@ class Save
     return song.get(difficultyId);
   }
 
+  public function getSongRank(songId:String, difficultyId:String = 'normal'):Null<ScoringRank>
+  {
+    return Scoring.calculateRank(getSongScore(songId, difficultyId));
+  }
+
   /**
    * Apply the score the user achieved for a given song on a given difficulty.
    */
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 61c53c7d7..192c6e3ce 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -35,6 +35,8 @@ import funkin.ui.story.Level;
 import funkin.save.Save;
 import funkin.save.Save.SaveScoreData;
 import funkin.ui.AtlasText;
+import funkin.play.scoring.Scoring;
+import funkin.play.scoring.Scoring.ScoringRank;
 import funkin.ui.mainmenu.MainMenuState;
 import funkin.ui.MusicBeatSubState;
 import funkin.ui.transition.LoadingState;
@@ -50,6 +52,34 @@ import funkin.effects.IntervalShake;
 typedef FreeplayStateParams =
 {
   ?character:String,
+
+  ?fromResults:FromResultsParams,
+};
+
+/**
+ * A set of parameters for transitioning to the FreeplayState from the ResultsState.
+ */
+typedef FromResultsParams =
+{
+  /**
+   * The previous rank the song hand, if any. Null if it had no score before.
+   */
+  var ?oldRank:ScoringRank;
+
+  /**
+   * The new rank the song has.
+   */
+  var newRank:ScoringRank;
+
+  /**
+   * The song ID to play the animation on.
+   */
+  var songId:String;
+
+  /**
+   * The difficulty ID to play the animation on.
+   */
+  var difficultyId:String;
 };
 
 /**
@@ -161,10 +191,14 @@ class FreeplayState extends MusicBeatSubState
   var bgDad:FlxSprite;
   var cardGlow:FlxSprite;
 
+  var fromResultsParams:Null<FromResultsParams> = null;
+
   public function new(?params:FreeplayStateParams, ?stickers:StickerSubState)
   {
     currentCharacter = params?.character ?? Constants.DEFAULT_CHARACTER;
 
+    fromResultsParams = params?.fromResults;
+
     if (stickers != null)
     {
       stickerSubState = stickers;
@@ -602,6 +636,11 @@ class FreeplayState extends MusicBeatSubState
 
       cardGlow.visible = true;
       FlxTween.tween(cardGlow, {alpha: 0, "scale.x": 1.2, "scale.y": 1.2}, 0.45, {ease: FlxEase.sineOut});
+
+      if (fromResultsParams != null)
+      {
+        rankAnimStart(fromResultsParams);
+      }
     });
 
     generateSongList(null, false);
@@ -672,6 +711,12 @@ class FreeplayState extends MusicBeatSubState
     // If curSelected is 0, the result will be null and fall back to the rememberedSongId.
     rememberedSongId = grpCapsules.members[curSelected]?.songData?.songId ?? rememberedSongId;
 
+    if (fromResultsParams != null)
+    {
+      rememberedSongId = fromResultsParams.songId;
+      rememberedDifficulty = fromResultsParams.difficultyId;
+    }
+
     for (cap in grpCapsules.members)
     {
       cap.songText.resetText();
@@ -778,8 +823,10 @@ class FreeplayState extends MusicBeatSubState
     return songsToFilter;
   }
 
-  function rankAnimStart()
+  function rankAnimStart(fromResults:Null<FromResultsParams>):Void
   {
+    busy = true;
+
     dj.fistPump();
     // rankCamera.fade(FlxColor.BLACK, 0.5, true);
     rankCamera.fade(0xFF000000, 0.5, true, null, true);
@@ -804,21 +851,21 @@ class FreeplayState extends MusicBeatSubState
 
     new FlxTimer().start(0.5, _ -> {
       grpCapsules.members[curSelected].doLerp = false;
-      rankDisplayNew();
+      rankDisplayNew(fromResults);
     });
   }
 
-  function rankDisplayNew()
+  function rankDisplayNew(fromResults:Null<FromResultsParams>):Void
   {
     grpCapsules.members[curSelected].ranking.alpha = 1;
     grpCapsules.members[curSelected].blurredRanking.alpha = 1;
 
     grpCapsules.members[curSelected].ranking.scale.set(20, 20);
     grpCapsules.members[curSelected].blurredRanking.scale.set(20, 20);
-    // var tempr:Int = FlxG.random.int(0, 4);
 
-    // grpCapsules.members[curSelected].ranking.rank = tempr;
-    grpCapsules.members[curSelected].ranking.animation.play(grpCapsules.members[curSelected].ranking.animation.curAnim.name, true);
+    grpCapsules.members[curSelected].ranking.animation.play(fromResults.newRank.getFreeplayRankIconAsset(), true);
+    // grpCapsules.members[curSelected].ranking.animation.curAnim.name, true);
+
     FlxTween.tween(grpCapsules.members[curSelected].ranking, {"scale.x": 1, "scale.y": 1}, 0.1);
 
     grpCapsules.members[curSelected].blurredRanking.animation.play(grpCapsules.members[curSelected].blurredRanking.animation.curAnim.name, true);
@@ -826,13 +873,13 @@ class FreeplayState extends MusicBeatSubState
 
     new FlxTimer().start(0.1, _ -> {
       trace(grpCapsules.members[curSelected].ranking.rank);
-      switch (grpCapsules.members[curSelected].tempr)
+      switch (fromResultsParams?.newRank)
       {
-        case 0:
+        case SHIT:
           FunkinSound.playOnce(Paths.sound('ranks/rankinbad'));
-        case 4:
+        case PERFECT:
           FunkinSound.playOnce(Paths.sound('ranks/rankinperfect'));
-        case 5:
+        case PERFECT_GOLD:
           FunkinSound.playOnce(Paths.sound('ranks/rankinperfect'));
         default:
           FunkinSound.playOnce(Paths.sound('ranks/rankinnormal'));
@@ -861,31 +908,31 @@ class FreeplayState extends MusicBeatSubState
     });
 
     new FlxTimer().start(0.6, _ -> {
-      rankAnimSlam();
+      rankAnimSlam(fromResults);
       // IntervalShake.shake(grpCapsules.members[curSelected].capsule, 0.3, 1 / 30, 0, 0.3, FlxEase.quartIn);
     });
   }
 
-  function rankAnimSlam()
+  function rankAnimSlam(fromResultsParams:Null<FromResultsParams>)
   {
     // FlxTween.tween(rankCamera, {"zoom": 1.9}, 0.5, {ease: FlxEase.backOut});
     FlxTween.tween(rankBg, {alpha: 0}, 0.5, {ease: FlxEase.expoIn});
 
     // FlxTween.tween(grpCapsules.members[curSelected], {angle: 5}, 0.5, {ease: FlxEase.backIn});
 
-    switch (grpCapsules.members[curSelected].tempr)
+    switch (fromResultsParams?.newRank)
     {
-      case 0:
+      case SHIT:
         FunkinSound.playOnce(Paths.sound('ranks/loss'));
-      case 1:
+      case GOOD:
         FunkinSound.playOnce(Paths.sound('ranks/good'));
-      case 2:
+      case GREAT:
         FunkinSound.playOnce(Paths.sound('ranks/great'));
-      case 3:
+      case EXCELLENT:
         FunkinSound.playOnce(Paths.sound('ranks/excellent'));
-      case 4:
+      case PERFECT:
         FunkinSound.playOnce(Paths.sound('ranks/perfect'));
-      case 5:
+      case PERFECT_GOLD:
         FunkinSound.playOnce(Paths.sound('ranks/perfect'));
       default:
         FunkinSound.playOnce(Paths.sound('ranks/loss'));
@@ -895,7 +942,7 @@ class FreeplayState extends MusicBeatSubState
     new FlxTimer().start(0.5, _ -> {
       funnyCam.shake(0.0045, 0.35);
 
-      if (grpCapsules.members[curSelected].tempr == 0)
+      if (fromResultsParams?.newRank == SHIT)
       {
         dj.pumpFistBad();
       }
@@ -930,6 +977,9 @@ class FreeplayState extends MusicBeatSubState
             IntervalShake.shake(capsule, 0.6, 1 / 24, 0.12, 0, FlxEase.quadOut, function(_) {
               capsule.doLerp = true;
               capsule.cameras = [funnyCam];
+
+              // NOW we can interact with the menu
+              busy = false;
             }, null);
 
             // FlxTween.tween(capsule, {"targetPos.x": capsule.targetPos.x - 50}, 0.6,
@@ -996,7 +1046,10 @@ class FreeplayState extends MusicBeatSubState
   var spamTimer:Float = 0;
   var spamming:Bool = false;
 
-  var busy:Bool = false; // Set to true once the user has pressed enter to select a song.
+  /**
+   * If true, disable interaction with the interface.
+   */
+  var busy:Bool = false;
 
   var originalPos:FlxPoint = new FlxPoint();
 
@@ -1004,19 +1057,20 @@ class FreeplayState extends MusicBeatSubState
   {
     super.update(elapsed);
 
+    #if debug
     if (FlxG.keys.justPressed.T)
     {
-      rankAnimStart();
+      rankAnimStart(fromResultsParams);
     }
 
     if (FlxG.keys.justPressed.H)
     {
-      rankDisplayNew();
+      rankDisplayNew(fromResultsParams);
     }
 
     if (FlxG.keys.justPressed.G)
     {
-      rankAnimSlam();
+      rankAnimSlam(fromResultsParams);
     }
 
     if (FlxG.keys.justPressed.I)
@@ -1039,6 +1093,7 @@ class FreeplayState extends MusicBeatSubState
       confirmTextGlow.y += 1;
       trace(confirmTextGlow.x, confirmTextGlow.y);
     }
+    #end
 
     if (FlxG.keys.justPressed.F)
     {
@@ -1782,6 +1837,8 @@ class FreeplaySongData
 
   public var currentDifficulty(default, set):String = Constants.DEFAULT_DIFFICULTY;
 
+  public var scoringRank:Null<ScoringRank> = null;
+
   var displayedVariations:Array<String> = [Constants.DEFAULT_VARIATION];
 
   function set_currentDifficulty(value:String):String
@@ -1844,6 +1901,8 @@ class FreeplaySongData
     {
       this.albumId = songDifficulty.album;
     }
+
+    this.scoringRank = Save.instance.getSongRank(songId, currentDifficulty);
   }
 }
 
diff --git a/source/funkin/ui/freeplay/SongMenuItem.hx b/source/funkin/ui/freeplay/SongMenuItem.hx
index 536a9cfe6..ad6ea386e 100644
--- a/source/funkin/ui/freeplay/SongMenuItem.hx
+++ b/source/funkin/ui/freeplay/SongMenuItem.hx
@@ -20,6 +20,7 @@ import funkin.graphics.FunkinSprite;
 import flixel.tweens.FlxEase;
 import flixel.tweens.FlxTween;
 import flixel.addons.effects.FlxTrail;
+import funkin.play.scoring.Scoring.ScoringRank;
 import flixel.util.FlxColor;
 
 class SongMenuItem extends FlxSpriteGroup
@@ -70,8 +71,6 @@ class SongMenuItem extends FlxSpriteGroup
 
   var impactThing:FunkinSprite;
 
-  public var tempr:Int;
-
   public function new(x:Float, y:Float)
   {
     super(x, y);
@@ -132,13 +131,10 @@ class SongMenuItem extends FlxSpriteGroup
     // doesn't get added, simply is here to help with visibility of things for the pop in!
     grpHide = new FlxGroup();
 
-    var rank:String = FlxG.random.getObject(ranks);
-
-    tempr = FlxG.random.int(0, 5);
-    ranking = new FreeplayRank(420, 41, tempr);
+    ranking = new FreeplayRank(420, 41);
     add(ranking);
 
-    blurredRanking = new FreeplayRank(ranking.x, ranking.y, tempr);
+    blurredRanking = new FreeplayRank(ranking.x, ranking.y);
     blurredRanking.shader = new GaussianBlurShader(1);
     add(blurredRanking);
     // ranking.loadGraphic(Paths.image('freeplay/ranks/' + rank));
@@ -344,19 +340,19 @@ class SongMenuItem extends FlxSpriteGroup
       });
     add(evilTrail);
 
-    switch (tempr)
+    switch (ranking.rank)
     {
-      case 0:
+      case SHIT:
         evilTrail.color = 0xFF6044FF;
-      case 1:
+      case GOOD:
         evilTrail.color = 0xFFEF8764;
-      case 2:
+      case GREAT:
         evilTrail.color = 0xFFEAF6FF;
-      case 3:
+      case EXCELLENT:
         evilTrail.color = 0xFFFDCB42;
-      case 4:
+      case PERFECT:
         evilTrail.color = 0xFFFF58B4;
-      case 5:
+      case PERFECT_GOLD:
         evilTrail.color = 0xFFFFB619;
     }
   }
@@ -393,6 +389,12 @@ class SongMenuItem extends FlxSpriteGroup
     // diffRatingSprite.visible = false;
   }
 
+  function updateScoringRank(newRank:Null<ScoringRank>):Void
+  {
+    this.ranking.rank = newRank;
+    this.blurredRanking.rank = newRank;
+  }
+
   function set_hsvShader(value:HSVShader):HSVShader
   {
     this.hsvShader = value;
@@ -441,6 +443,7 @@ class SongMenuItem extends FlxSpriteGroup
     if (songData?.songCharacter != null) setCharacter(songData.songCharacter);
     updateBPM(Std.int(songData?.songStartingBpm) ?? 0);
     updateDifficultyRating(songData?.difficultyRating ?? 0);
+    updateScoringRank(songData?.scoringRank);
     // Update opacity, offsets, etc.
     updateSelected();
 
@@ -668,41 +671,53 @@ class SongMenuItem extends FlxSpriteGroup
 
 class FreeplayRank extends FlxSprite
 {
-  public var rank(default, set):Int = 0;
+  public var rank(default, set):Null<ScoringRank> = null;
 
-  var numToRank:Array<String> = ["LOSS", "GOOD", "GREAT", "EXCELLENT", "PERFECT", "PERFECTSICK"];
-
-  function set_rank(val):Int
+  function set_rank(val:Null<ScoringRank>):Null<ScoringRank>
   {
-    animation.play(numToRank[val], true, false);
+    rank = val;
 
-    centerOffsets(false);
-
-    switch (val)
+    if (rank == null)
     {
-      case 0:
-      // offset.x -= 1;
-      case 1:
-        // offset.x -= 1;
-        offset.y -= 8;
-      case 2:
-        // offset.x -= 1;
-        offset.y -= 8;
-      case 3:
-      // offset.y += 5;
-      case 4:
-      // offset.y += 5;
-      default:
-        centerOffsets(false);
+      this.visible = false;
     }
-    updateHitbox();
-    return val;
+    else
+    {
+      this.visible = true;
+
+      animation.play(val.getFreeplayRankIconAsset(), true, false);
+
+      centerOffsets(false);
+
+      switch (val)
+      {
+        case SHIT:
+        // offset.x -= 1;
+        case GOOD:
+          // offset.x -= 1;
+          offset.y -= 8;
+        case GREAT:
+          // offset.x -= 1;
+          offset.y -= 8;
+        case EXCELLENT:
+        // offset.y += 5;
+        case PERFECT:
+        // offset.y += 5;
+        case PERFECT_GOLD:
+        // offset.y += 5;
+        default:
+          centerOffsets(false);
+      }
+      updateHitbox();
+    }
+
+    return rank = val;
   }
 
-  public var baseY:Float = 0;
   public var baseX:Float = 0;
+  public var baseY:Float = 0;
 
-  public function new(x:Float, y:Float, ?initRank:Int = 0)
+  public function new(x:Float, y:Float)
   {
     super(x, y);
 
@@ -717,9 +732,7 @@ class FreeplayRank extends FlxSprite
 
     blend = BlendMode.ADD;
 
-    this.rank = initRank;
-
-    animation.play(numToRank[initRank], true);
+    this.rank = null;
 
     // setGraphicSize(Std.int(width * 0.9));
     scale.set(0.9, 0.9);