diff --git a/.vscode/launch.json b/.vscode/launch.json
index b8fdb64d1..6dc1dc008 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -3,13 +3,13 @@
   "configurations": [
     {
       // Launch in native/CPP on Windows/OSX/Linux
-      "name": "Lime",
+      "name": "Lime Build+Debug",
       "type": "lime",
       "request": "launch"
     },
     {
-      // Launch in native/CPP on Windows/OSX/Linux (without compiling)
-      "name": "Debug",
+      // Launch in native/CPP on Windows/OSX/Linux
+      "name": "Lime Debug (No Build)",
       "type": "lime",
       "request": "launch",
       "preLaunchTask": null
diff --git a/.vscode/settings.json b/.vscode/settings.json
index a8a67245b..26fe0b042 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -155,6 +155,11 @@
       "target": "hl",
       "args": ["-debug", "-DDIALOGUE"]
     },
+    {
+      "label": "Windows / Debug (Results Screen Test)",
+      "target": "windows",
+      "args": ["-debug", "-DRESULTS"]
+    },
     {
       "label": "Windows / Debug (Straight to Chart Editor)",
       "target": "windows",
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a852ca82d..10bbfe5f7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,28 @@ All notable changes will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+## [0.4.0] - 2024-05-??
+### Added
+- 2 new Erect remixes, Eggnog and Satin Panties. Check them out from
+- Improvements to the Freeplay screen, with song difficulty ratings and player rank displays.
+- Reworked the Results screen, with additional animations and audio based on your performance.
+- Added a Charter field to the chart format, to allow for crediting the creator of a level's chart.
+  - You can see who charted a song from the Pause menu.
+### Changed
+- Tweaked the charts for several songs:
+  - Winter Horrorland
+  - Stress
+  - Lit Up
+- Custom note styles are now properly supported for songs; add new notestyles via JSON, then select it for use from the Chart Editor Metadata toolbox. (thanks Keoiki!)
+- Health icons now support a Winning frame without requiring a spritesheet, simply include a third frame in the icon file. (thanks gamerbross!)
+  - Remember that for more complex behaviors such as animations or transitions, you should use an XML file to define each frame.
+### Fixed
+- Fixed a bug where pressing the volume keys would stop the Toy commercial (thanks gamerbross!)
+- Fixed a bug where the Chart Editor would crash when losing (thanks gamerbross!)
+- Made improvements to compiling documentation (thanks gedehari!)
+- Fixed a crash on Linux caused by an old version of hxCodec (thanks Noobz4Life!)
+- Optimized animation handling for characters (thanks richTrash21!)
+
 ## [0.3.3] - 2024-05-14
 ### Changed
 - Cleaned up some code in `PlayAnimationSongEvent.hx` (thanks BurgerBalls!)
diff --git a/assets b/assets
index 57a862595..8a8239cb5 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 57a862595af16a5808b384ec1a22d5b35afc4cd6
+Subproject commit 8a8239cb50b5277fb0cfce041b3d8a9dfc780c35
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 00d34fadb..a945c10c5 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -214,6 +214,30 @@ class InitState extends FlxState
     #elseif STAGEBUILD
     // -DSTAGEBUILD
     FlxG.switchState(() -> new funkin.ui.debug.stage.StageBuilderState());
+    #elseif RESULTS
+    // -DRESULTS
+    FlxG.switchState(() -> new funkin.play.ResultState(
+      {
+        storyMode: false,
+        title: "CUM SONG",
+        isNewHighscore: true,
+        scoreData:
+          {
+            score: 1_234_567,
+            tallies:
+              {
+                sick: 130,
+                good: 25,
+                bad: 69,
+                shit: 69,
+                missed: 69,
+                combo: 69,
+                maxCombo: 69,
+                totalNotesHit: 140,
+                totalNotes: 200 // 0,
+              }
+          },
+      }));
     #elseif ANIMDEBUG
     // -DANIMDEBUG
     FlxG.switchState(() -> new funkin.ui.debug.anim.DebugBoundingState());
diff --git a/source/funkin/play/PlayState.hx b/source/funkin/play/PlayState.hx
index 43dd485cf..a95166e21 100644
--- a/source/funkin/play/PlayState.hx
+++ b/source/funkin/play/PlayState.hx
@@ -2809,6 +2809,7 @@ class PlayState extends MusicBeatSubState
     deathCounter = 0;
 
     var isNewHighscore = false;
+    var prevScoreData:Null<SaveScoreData> = Save.instance.getSongScore(currentSong.id, currentDifficulty);
 
     if (currentSong != null && currentSong.validScore)
     {
@@ -2828,7 +2829,6 @@ class PlayState extends MusicBeatSubState
               totalNotesHit: Highscore.tallies.totalNotesHit,
               totalNotes: Highscore.tallies.totalNotes,
             },
-          accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
         };
 
       // adds current song data into the tallies for the level (story levels)
@@ -2865,7 +2865,7 @@ class PlayState extends MusicBeatSubState
               score: PlayStatePlaylist.campaignScore,
               tallies:
                 {
-                  // TODO: Sum up the values for the whole level!
+                  // TODO: Sum up the values for the whole week!
                   sick: 0,
                   good: 0,
                   bad: 0,
@@ -2876,7 +2876,6 @@ class PlayState extends MusicBeatSubState
                   totalNotesHit: 0,
                   totalNotes: 0,
                 },
-              accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
             };
 
           if (Save.instance.isLevelHighScore(PlayStatePlaylist.campaignId, PlayStatePlaylist.campaignDifficulty, data))
@@ -2962,11 +2961,11 @@ class PlayState extends MusicBeatSubState
       {
         if (rightGoddamnNow)
         {
-          moveToResultsScreen(isNewHighscore);
+          moveToResultsScreen(isNewHighscore, prevScoreData);
         }
         else
         {
-          zoomIntoResultsScreen(isNewHighscore);
+          zoomIntoResultsScreen(isNewHighscore, prevScoreData);
         }
       }
     }
@@ -3040,7 +3039,7 @@ class PlayState extends MusicBeatSubState
   /**
    * Play the camera zoom animation and then move to the results screen once it's done.
    */
-  function zoomIntoResultsScreen(isNewHighscore:Bool):Void
+  function zoomIntoResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void
   {
     trace('WENT TO RESULTS SCREEN!');
 
@@ -3080,7 +3079,7 @@ class PlayState extends MusicBeatSubState
     FlxTween.tween(camHUD, {alpha: 0}, 0.6,
       {
         onComplete: function(_) {
-          moveToResultsScreen(isNewHighscore);
+          moveToResultsScreen(isNewHighscore, prevScoreData);
         }
       });
 
@@ -3113,7 +3112,7 @@ class PlayState extends MusicBeatSubState
   /**
    * Move to the results screen right goddamn now.
    */
-  function moveToResultsScreen(isNewHighscore:Bool):Void
+  function moveToResultsScreen(isNewHighscore:Bool, ?prevScoreData:SaveScoreData):Void
   {
     persistentUpdate = false;
     vocals.stop();
@@ -3125,6 +3124,8 @@ class PlayState extends MusicBeatSubState
       {
         storyMode: PlayStatePlaylist.isStoryMode,
         title: PlayStatePlaylist.isStoryMode ? ('${PlayStatePlaylist.campaignTitle}') : ('${currentChart.songName} by ${currentChart.songArtist}'),
+        prevScoreData: prevScoreData,
+        difficultyId: currentDifficulty,
         scoreData:
           {
             score: PlayStatePlaylist.isStoryMode ? PlayStatePlaylist.campaignScore : songScore,
@@ -3140,7 +3141,6 @@ class PlayState extends MusicBeatSubState
                 totalNotesHit: talliesToUse.totalNotesHit,
                 totalNotes: talliesToUse.totalNotes,
               },
-            accuracy: Highscore.tallies.totalNotesHit / Highscore.tallies.totalNotes,
           },
         isNewHighscore: isNewHighscore
       });
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index df3134b9d..ee7c8eade 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -24,6 +24,7 @@ import funkin.save.Save;
 import funkin.save.Save.SaveScoreData;
 import funkin.graphics.shaders.LeftMaskShader;
 import funkin.play.components.TallyCounter;
+import funkin.play.components.ClearPercentCounter;
 
 /**
  * The state for the results screen after a song or week is finished.
@@ -36,6 +37,7 @@ class ResultState extends MusicBeatSubState
   final rank:ResultRank;
   final songName:FlxBitmapText;
   final difficulty:FlxSprite;
+  final clearPercentSmall:ClearPercentCounter;
 
   final maskShaderSongName:LeftMaskShader = new LeftMaskShader();
   final maskShaderDifficulty:LeftMaskShader = new LeftMaskShader();
@@ -51,6 +53,7 @@ class ResultState extends MusicBeatSubState
 
   var bfPerfect:Null<FlxAtlasSprite> = null;
   var bfExcellent:Null<FlxAtlasSprite> = null;
+  var bfGreat:Null<FlxAtlasSprite> = null;
   var bfGood:Null<FlxSprite> = null;
   var gfGood:Null<FlxSprite> = null;
   var bfShit:Null<FlxAtlasSprite> = null;
@@ -77,6 +80,10 @@ class ResultState extends MusicBeatSubState
     difficulty = new FlxSprite(555);
     difficulty.zIndex = 1000;
 
+    clearPercentSmall = new ClearPercentCounter(FlxG.width / 2 + 300, FlxG.height / 2 - 100, 100, true);
+    clearPercentSmall.zIndex = 1000;
+    clearPercentSmall.visible = false;
+
     bgFlash = FlxGradient.createGradientFlxSprite(FlxG.width, FlxG.height, [0xFFFFEB69, 0xFFFFE66A], 90);
 
     resultsAnim = FunkinSprite.createSparrow(-200, -10, "resultScreen/results");
@@ -109,7 +116,7 @@ class ResultState extends MusicBeatSubState
     var soundSystem:FlxSprite = FunkinSprite.createSparrow(-15, -180, 'resultScreen/soundSystem');
     soundSystem.animation.addByPrefix("idle", "sound system", 24, false);
     soundSystem.visible = false;
-    new FlxTimer().start(0.4, _ -> {
+    new FlxTimer().start(0.3, _ -> {
       soundSystem.animation.play("idle");
       soundSystem.visible = true;
     });
@@ -118,7 +125,7 @@ class ResultState extends MusicBeatSubState
 
     switch (rank)
     {
-      case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
+      case PERFECT | PERFECT_GOLD:
         bfPerfect = new FlxAtlasSprite(370, -180, Paths.animateAtlas("resultScreen/results-bf/resultsPERFECT", "shared"));
         bfPerfect.visible = false;
         bfPerfect.zIndex = 500;
@@ -145,7 +152,20 @@ class ResultState extends MusicBeatSubState
           }
         });
 
-      case GOOD | GREAT:
+      case GREAT:
+        bfGreat = new FlxAtlasSprite(640, 200, Paths.animateAtlas("resultScreen/results-bf/resultsGREAT", "shared"));
+        bfGreat.visible = false;
+        bfGreat.zIndex = 500;
+        add(bfGreat);
+
+        bfGreat.onAnimationFinish.add((animName) -> {
+          if (bfGreat != null)
+          {
+            bfGreat.playAnimation('Loop Start');
+          }
+        });
+
+      case GOOD:
         gfGood = FunkinSprite.createSparrow(625, 325, 'resultScreen/results-bf/resultsGOOD/resultGirlfriendGOOD');
         gfGood.animation.addByPrefix("clap", "Girlfriend Good Anim", 24, false);
         gfGood.visible = false;
@@ -183,22 +203,7 @@ class ResultState extends MusicBeatSubState
         });
     }
 
-    var diffSpr:String = switch (PlayState.instance.currentDifficulty)
-    {
-      case 'easy':
-        'difEasy';
-      case 'normal':
-        'difNormal';
-      case 'hard':
-        'difHard';
-      case 'erect':
-        'difErect';
-      case 'nightmare':
-        'difNightmare';
-      case _:
-        'difNormal';
-    }
-
+    var diffSpr:String = 'dif${params?.difficultyId ?? 'Normal'}';
     difficulty.loadGraphic(Paths.image("resultScreen/" + diffSpr));
     add(difficulty);
 
@@ -208,7 +213,7 @@ class ResultState extends MusicBeatSubState
     speedOfTween.x = -1.0 * Math.cos(angleRad);
     speedOfTween.y = -1.0 * Math.sin(angleRad);
 
-    timerThenSongName();
+    timerThenSongName(1.0, false);
 
     songName.shader = maskShaderSongName;
     difficulty.shader = maskShaderDifficulty;
@@ -218,24 +223,40 @@ class ResultState extends MusicBeatSubState
 
     var blackTopBar:FlxSprite = new FlxSprite().loadGraphic(Paths.image("resultScreen/topBarBlack"));
     blackTopBar.y = -blackTopBar.height;
-    FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut, startDelay: 0.5});
+    FlxTween.tween(blackTopBar, {y: 0}, 0.4, {ease: FlxEase.quartOut});
     blackTopBar.zIndex = 1010;
     add(blackTopBar);
 
     resultsAnim.animation.addByPrefix("result", "results instance 1", 24, false);
-    resultsAnim.animation.play("result");
+    resultsAnim.visible = false;
     resultsAnim.zIndex = 1200;
     add(resultsAnim);
+    new FlxTimer().start(0.3, _ -> {
+      resultsAnim.visible = true;
+      resultsAnim.animation.play("result");
+    });
 
     ratingsPopin.animation.addByPrefix("idle", "Categories", 24, false);
     ratingsPopin.visible = false;
     ratingsPopin.zIndex = 1200;
     add(ratingsPopin);
+    new FlxTimer().start(1.0, _ -> {
+      ratingsPopin.visible = true;
+      ratingsPopin.animation.play("idle");
+    });
 
     scorePopin.animation.addByPrefix("score", "tally score", 24, false);
     scorePopin.visible = false;
     scorePopin.zIndex = 1200;
     add(scorePopin);
+    new FlxTimer().start(1.0, _ -> {
+      scorePopin.visible = true;
+      scorePopin.animation.play("score");
+      scorePopin.animation.finishCallback = anim -> {
+        score.visible = true;
+        score.animateNumbers();
+      };
+    });
 
     highscoreNew.frames = Paths.getSparrowAtlas("resultScreen/highscoreNew");
     highscoreNew.animation.addByPrefix("new", "NEW HIGHSCORE", 24);
@@ -285,13 +306,26 @@ class ResultState extends MusicBeatSubState
     for (ind => rating in ratingGrp.members)
     {
       rating.visible = false;
-      new FlxTimer().start((0.3 * ind) + 0.55, _ -> {
+      new FlxTimer().start((0.3 * ind) + 1.20, _ -> {
         rating.visible = true;
         FlxTween.tween(rating, {curNumber: rating.neededNumber}, 0.5, {ease: FlxEase.quartOut});
       });
     }
 
-    startRankTallySequence();
+    ratingsPopin.animation.finishCallback = anim -> {
+      startRankTallySequence();
+
+      if (params.isNewHighscore ?? false)
+      {
+        highscoreNew.visible = true;
+        highscoreNew.animation.play("new");
+        FlxTween.tween(highscoreNew, {y: highscoreNew.y + 10}, 0.8, {ease: FlxEase.quartOut});
+      }
+      else
+      {
+        highscoreNew.visible = false;
+      }
+    };
 
     refresh();
 
@@ -304,48 +338,60 @@ class ResultState extends MusicBeatSubState
 
   function startRankTallySequence():Void
   {
-    clearPercentTarget = Math.floor((params.scoreData.tallies.totalNotesHit) / params.scoreData.tallies.totalNotes * 100);
-    // clearPercentTarget = 97;
+    var clearPercentFloat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) / params.scoreData.tallies.totalNotes * 100;
+    clearPercentTarget = Math.floor(clearPercentFloat);
+    // Prevent off-by-one errors.
 
-    var clearPercentText = new FlxText(FlxG.width / 2, FlxG.height / 2, 0, 'CLEAR: ${clearPercentLerp}%');
-    clearPercentText.setFormat(Paths.font('vcr.ttf'), 64, FlxColor.BLACK, FlxTextAlign.RIGHT);
-    clearPercentText.zIndex = 1000;
-    add(clearPercentText);
+    clearPercentLerp = Std.int(Math.max(0, clearPercentTarget - 36));
 
-    rankTallyTimer = new FlxTimer().start(1 / 24, _ -> {
-      // Tick up.
-      if (clearPercentLerp < clearPercentTarget)
+    trace('Clear percent target: ' + clearPercentFloat + ', round: ' + clearPercentTarget);
+
+    var clearPercentCounter:ClearPercentCounter = new ClearPercentCounter(FlxG.width / 2 + 300, FlxG.height / 2 - 100, clearPercentLerp);
+    FlxTween.tween(clearPercentCounter, {curNumber: clearPercentTarget}, 1.5,
       {
-        clearPercentLerp++;
+        ease: FlxEase.quartOut,
+        onUpdate: _ -> {
+          // Only play the tick sound if the number increased.
+          if (clearPercentLerp != clearPercentCounter.curNumber)
+          {
+            clearPercentLerp = clearPercentCounter.curNumber;
+            FunkinSound.playOnce(Paths.sound('scrollMenu'));
+          }
+        },
+        onComplete: _ -> {
+          // Play confirm sound.
+          FunkinSound.playOnce(Paths.sound('confirmMenu'));
 
-        clearPercentText.text = 'CLEAR: ${clearPercentLerp}%';
-        FunkinSound.playOnce(Paths.sound('scrollMenu'));
-      }
+          // Flash background.
+          bgFlash.visible = true;
+          FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
 
-      // Don't overshoot.
-      if (clearPercentLerp > clearPercentTarget)
-      {
-        clearPercentLerp = clearPercentTarget;
-      }
+          // Just to be sure that the lerp didn't mess things up.
+          clearPercentCounter.curNumber = clearPercentTarget;
 
-      if (clearPercentLerp == clearPercentTarget)
-      {
-        if (rankTallyTimer != null)
-        {
-          rankTallyTimer.destroy();
-          rankTallyTimer = null;
+          clearPercentCounter.flash(true);
+          new FlxTimer().start(0.4, _ -> {
+            clearPercentCounter.flash(false);
+          });
+
+          displayRankText();
+
+          new FlxTimer().start(2.0, _ -> {
+            FlxTween.tween(clearPercentCounter, {alpha: 0}, 0.5,
+              {
+                startDelay: 0.5,
+                ease: FlxEase.quartOut,
+                onComplete: _ -> {
+                  remove(clearPercentCounter);
+                }
+              });
+
+            afterRankTallySequence();
+          });
         }
-
-        // Play confirm sound.
-        FunkinSound.playOnce(Paths.sound('confirmMenu'));
-
-        new FlxTimer().start(1.0, _ -> {
-          remove(clearPercentText);
-
-          afterRankTallySequence();
-        });
-      }
-    }, 0); // 0 = Loop until stopped
+      });
+    clearPercentCounter.zIndex = 450;
+    add(clearPercentCounter);
 
     if (ratingsPopin == null)
     {
@@ -353,18 +399,15 @@ class ResultState extends MusicBeatSubState
     }
     else
     {
-      ratingsPopin.animation.play("idle");
-      ratingsPopin.visible = true;
+      // ratingsPopin.animation.play("idle");
+      // ratingsPopin.visible = true;
 
       ratingsPopin.animation.finishCallback = anim -> {
-        scorePopin.animation.play("score");
-        scorePopin.animation.finishCallback = anim -> {
-          score.visible = true;
-          score.animateNumbers();
-        };
-        scorePopin.visible = true;
+        // scorePopin.animation.play("score");
 
-        if (params.isNewHighscore)
+        // scorePopin.visible = true;
+
+        if (params.isNewHighscore ?? false)
         {
           highscoreNew.visible = true;
           highscoreNew.animation.play("new");
@@ -380,8 +423,27 @@ class ResultState extends MusicBeatSubState
     refresh();
   }
 
+  function displayRankText():Void
+  {
+    var rankTextVert:FunkinSprite = FunkinSprite.create(FlxG.width - 64, 100, rank.getVerTextAsset());
+    rankTextVert.zIndex = 2000;
+    add(rankTextVert);
+
+    for (i in 0...10)
+    {
+      var rankTextBack:FunkinSprite = FunkinSprite.create(FlxG.width / 2 - 80, 50, rank.getHorTextAsset());
+      rankTextBack.y += (rankTextBack.height * i / 2) + 10;
+      rankTextBack.zIndex = 100;
+      add(rankTextBack);
+    }
+
+    refresh();
+  }
+
   function afterRankTallySequence():Void
   {
+    showSmallClearPercent();
+
     FunkinSound.playMusic(rank.getMusicPath(),
       {
         startingVolume: 1.0,
@@ -406,7 +468,7 @@ class ResultState extends MusicBeatSubState
 
     switch (rank)
     {
-      case PERFECT | PERFECT_GOLD | PERFECT_PLATINUM:
+      case PERFECT | PERFECT_GOLD:
         if (bfPerfect == null)
         {
           trace("Could not build PERFECT animation!");
@@ -415,17 +477,6 @@ class ResultState extends MusicBeatSubState
         {
           bfPerfect.visible = true;
           bfPerfect.playAnimation('');
-
-          new FlxTimer().start((1 / 24) * 12, _ -> {
-            bgFlash.visible = true;
-            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
-            new FlxTimer().start((1 / 24) * 2, _ ->
-              {
-                // bgFlash.alpha = 0.5;
-
-                // bgFlash.visible = false;
-              });
-          });
         }
 
       case EXCELLENT:
@@ -437,17 +488,17 @@ class ResultState extends MusicBeatSubState
         {
           bfExcellent.visible = true;
           bfExcellent.playAnimation('Intro');
+        }
 
-          new FlxTimer().start((1 / 24) * 12, _ -> {
-            bgFlash.visible = true;
-            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
-            new FlxTimer().start((1 / 24) * 2, _ ->
-              {
-                // bgFlash.alpha = 0.5;
-
-                // bgFlash.visible = false;
-              });
-          });
+      case GREAT:
+        if (bfGreat == null)
+        {
+          trace("Could not build GREAT animation!");
+        }
+        else
+        {
+          bfGreat.visible = true;
+          bfGreat.playAnimation('Intro');
         }
 
       case SHIT:
@@ -459,20 +510,9 @@ class ResultState extends MusicBeatSubState
         {
           bfShit.visible = true;
           bfShit.playAnimation('Intro');
-
-          new FlxTimer().start((1 / 24) * 12, _ -> {
-            bgFlash.visible = true;
-            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
-            new FlxTimer().start((1 / 24) * 2, _ ->
-              {
-                // bgFlash.alpha = 0.5;
-
-                // bgFlash.visible = false;
-              });
-          });
         }
 
-      case GREAT | GOOD:
+      case GOOD:
         if (bfGood == null)
         {
           trace("Could not build GOOD animation!");
@@ -482,17 +522,6 @@ class ResultState extends MusicBeatSubState
           bfGood.animation.play('fall');
           bfGood.visible = true;
 
-          new FlxTimer().start((1 / 24) * 12, _ -> {
-            bgFlash.visible = true;
-            FlxTween.tween(bgFlash, {alpha: 0}, 0.4);
-            new FlxTimer().start((1 / 24) * 2, _ ->
-              {
-                // bgFlash.alpha = 0.5;
-
-                // bgFlash.visible = false;
-              });
-          });
-
           new FlxTimer().start((1 / 24) * 22, _ -> {
             // plays about 22 frames (at 24fps timing) after bf spawns in
             if (gfGood != null)
@@ -510,7 +539,7 @@ class ResultState extends MusicBeatSubState
     }
   }
 
-  function timerThenSongName():Void
+  function timerThenSongName(timerLength:Float = 3.0, autoScroll:Bool = true):Void
   {
     movingSongStuff = false;
 
@@ -521,21 +550,47 @@ class ResultState extends MusicBeatSubState
     difficulty.y = -difficulty.height;
     FlxTween.tween(difficulty, {y: diffYTween}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.8});
 
+    if (clearPercentSmall != null)
+    {
+      clearPercentSmall.x = (difficulty.x + difficulty.width) + 60;
+      clearPercentSmall.y = -clearPercentSmall.height;
+      FlxTween.tween(clearPercentSmall, {y: 122 - 5}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.8});
+    }
+
     songName.y = -songName.height;
     var fuckedupnumber = (10) * (songName.text.length / 15);
-    FlxTween.tween(songName, {y: diffYTween - 35 - fuckedupnumber}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.9});
-    songName.x = (difficulty.x + difficulty.width) + 20;
+    FlxTween.tween(songName, {y: diffYTween - 25 - fuckedupnumber}, 0.5, {ease: FlxEase.expoOut, startDelay: 0.9});
+    songName.x = clearPercentSmall.x + clearPercentSmall.width - 30;
 
-    new FlxTimer().start(3, _ -> {
+    new FlxTimer().start(timerLength, _ -> {
       var tempSpeed = FlxPoint.get(speedOfTween.x, speedOfTween.y);
 
       speedOfTween.set(0, 0);
       FlxTween.tween(speedOfTween, {x: tempSpeed.x, y: tempSpeed.y}, 0.7, {ease: FlxEase.quadIn});
 
-      movingSongStuff = true;
+      movingSongStuff = (autoScroll);
     });
   }
 
+  function showSmallClearPercent():Void
+  {
+    if (clearPercentSmall != null)
+    {
+      add(clearPercentSmall);
+      clearPercentSmall.visible = true;
+      clearPercentSmall.flash(true);
+      new FlxTimer().start(0.4, _ -> {
+        clearPercentSmall.flash(false);
+      });
+
+      clearPercentSmall.curNumber = clearPercentTarget;
+      clearPercentSmall.zIndex = 1000;
+      refresh();
+    }
+
+    movingSongStuff = true;
+  }
+
   var movingSongStuff:Bool = false;
   var speedOfTween:FlxPoint = FlxPoint.get(-1, 1);
 
@@ -543,7 +598,8 @@ class ResultState extends MusicBeatSubState
   {
     super.draw();
 
-    songName.clipRect = FlxRect.get(Math.max(0, 540 - songName.x), 0, FlxG.width, songName.height);
+    songName.clipRect = FlxRect.get(Math.max(0, 520 - songName.x), 0, FlxG.width, songName.height);
+
     // PROBABLY SHOULD FIX MEMORY FREE OR WHATEVER THE PUT() FUNCTION DOES !!!! FEELS LIKE IT STUTTERS!!!
 
     // if (songName != null && songName.frame != null)
@@ -559,8 +615,10 @@ class ResultState extends MusicBeatSubState
     {
       songName.x += speedOfTween.x;
       difficulty.x += speedOfTween.x;
+      clearPercentSmall.x += speedOfTween.x;
       songName.y += speedOfTween.y;
       difficulty.y += speedOfTween.y;
+      clearPercentSmall.y += speedOfTween.y;
 
       if (songName.x + songName.width < 100)
       {
@@ -600,33 +658,29 @@ class ResultState extends MusicBeatSubState
   public static function calculateRank(params:ResultsStateParams):ResultRank
   {
     // Perfect (Platinum) is a Sick Full Clear
-    var isPerfectPlat = (params.scoreData.tallies.sick + params.scoreData.tallies.good) == params.scoreData.tallies.totalNotes
-      && params.scoreData.tallies.sick / params.scoreData.tallies.totalNotes >= Constants.RANK_PERFECT_PLAT_THRESHOLD;
-    if (isPerfectPlat) return ResultRank.PERFECT_PLATINUM;
-
-    // Perfect (Gold) is an 85% Sick Full Clear
-    var isPerfectGold = (params.scoreData.tallies.sick + params.scoreData.tallies.good) == params.scoreData.tallies.totalNotes
-      && params.scoreData.tallies.sick / params.scoreData.tallies.totalNotes >= Constants.RANK_PERFECT_GOLD_THRESHOLD;
+    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 (clear == Constants.RANK_PERFECT_THRESHOLD)
+    if (grade == Constants.RANK_PERFECT_THRESHOLD)
     {
       return ResultRank.PERFECT;
     }
-    else if (clear >= Constants.RANK_EXCELLENT_THRESHOLD)
+    else if (grade >= Constants.RANK_EXCELLENT_THRESHOLD)
     {
       return ResultRank.EXCELLENT;
     }
-    else if (clear >= Constants.RANK_GREAT_THRESHOLD)
+    else if (grade >= Constants.RANK_GREAT_THRESHOLD)
     {
       return ResultRank.GREAT;
     }
-    else if (clear >= Constants.RANK_GOOD_THRESHOLD)
+    else if (grade >= Constants.RANK_GOOD_THRESHOLD)
     {
       return ResultRank.GOOD;
     }
@@ -639,7 +693,6 @@ class ResultState extends MusicBeatSubState
 
 enum abstract ResultRank(String)
 {
-  var PERFECT_PLATINUM;
   var PERFECT_GOLD;
   var PERFECT;
   var EXCELLENT;
@@ -651,8 +704,6 @@ enum abstract ResultRank(String)
   {
     switch (abstract)
     {
-      case PERFECT_PLATINUM:
-        return 'resultsPERFECT';
       case PERFECT_GOLD:
         return 'resultsPERFECT';
       case PERFECT:
@@ -665,6 +716,8 @@ enum abstract ResultRank(String)
         return 'resultsNORMAL';
       case SHIT:
         return 'resultsSHIT';
+      default:
+        return 'resultsNORMAL';
     }
   }
 
@@ -672,8 +725,6 @@ enum abstract ResultRank(String)
   {
     switch (abstract)
     {
-      case PERFECT_PLATINUM:
-        return true;
       case PERFECT_GOLD:
         return true;
       case PERFECT:
@@ -690,6 +741,48 @@ enum abstract ResultRank(String)
         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 =
@@ -707,10 +800,21 @@ typedef ResultsStateParams =
   /**
    * Whether the displayed score is a new highscore
    */
-  var isNewHighscore:Bool;
+  var ?isNewHighscore:Bool;
+
+  /**
+   * The difficulty ID of the song/week we just played.
+   * @default Normal
+   */
+  var ?difficultyId:String;
 
   /**
    * The score, accuracy, and judgements.
    */
   var scoreData:SaveScoreData;
+
+  /**
+   * The previous score data, used for rank comparision.
+   */
+  var ?prevScoreData:SaveScoreData;
 };
diff --git a/source/funkin/play/components/ClearPercentCounter.hx b/source/funkin/play/components/ClearPercentCounter.hx
new file mode 100644
index 000000000..d296b0b0b
--- /dev/null
+++ b/source/funkin/play/components/ClearPercentCounter.hx
@@ -0,0 +1,137 @@
+package funkin.play.components;
+
+import funkin.graphics.FunkinSprite;
+import funkin.graphics.shaders.PureColor;
+import flixel.FlxSprite;
+import flixel.group.FlxGroup.FlxTypedGroup;
+import flixel.group.FlxSpriteGroup.FlxTypedSpriteGroup;
+import flixel.math.FlxMath;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+import flixel.text.FlxText.FlxTextAlign;
+import funkin.util.MathUtil;
+import flixel.util.FlxColor;
+
+/**
+ * Numerical counters used to display the clear percent.
+ */
+class ClearPercentCounter extends FlxTypedSpriteGroup<FlxSprite>
+{
+  public var curNumber(default, set):Int = 0;
+
+  var numberChanged:Bool = false;
+
+  function set_curNumber(val:Int):Int
+  {
+    numberChanged = true;
+    return curNumber = val;
+  }
+
+  var small:Bool = false;
+  var flashShader:PureColor;
+
+  public function new(x:Float, y:Float, startingNumber:Int = 0, small:Bool = false)
+  {
+    super(x, y);
+
+    flashShader = new PureColor(FlxColor.WHITE);
+    flashShader.colorSet = true;
+
+    curNumber = startingNumber;
+
+    this.small = small;
+
+    var clearPercentText:FunkinSprite = FunkinSprite.create(0, 0, 'resultScreen/clearPercent/clearPercentText${small ? 'Small' : ''}');
+    clearPercentText.x = small ? 40 : 0;
+    add(clearPercentText);
+
+    drawNumbers();
+  }
+
+  /**
+   * Make the counter flash turn white or stop being all white.
+   * @param enabled Whether the counter should be white.
+   */
+  public function flash(enabled:Bool):Void
+  {
+    for (member in members)
+    {
+      member.shader = enabled ? flashShader : null;
+    }
+  }
+
+  var tmr:Float = 0;
+
+  override function update(elapsed:Float)
+  {
+    super.update(elapsed);
+
+    if (numberChanged) drawNumbers();
+  }
+
+  function drawNumbers()
+  {
+    var seperatedScore:Array<Int> = [];
+    var tempCombo:Int = Math.round(curNumber);
+
+    while (tempCombo != 0)
+    {
+      seperatedScore.push(tempCombo % 10);
+      tempCombo = Math.floor(tempCombo / 10);
+    }
+
+    if (seperatedScore.length == 0) seperatedScore.push(0);
+
+    seperatedScore.reverse();
+
+    for (ind => num in seperatedScore)
+    {
+      var digitIndex = ind + 1;
+      // If there's only one digit, move it to the right
+      // If there's three digits, move them all to the left
+      var digitOffset = (seperatedScore.length == 1) ? 1 : (seperatedScore.length == 3) ? -1 : 0;
+      var digitSize = small ? 32 : 72;
+      var digitHeightOffset = small ? -4 : 0;
+
+      var xPos = (digitIndex - 1 + digitOffset) * (digitSize * this.scale.x);
+      xPos += small ? -24 : 0;
+      var yPos = (digitIndex - 1 + digitOffset) * (digitHeightOffset * this.scale.y);
+      yPos += small ? 0 : 72;
+
+      if (digitIndex >= members.length)
+      {
+        // Three digits = LLR because the 1 and 0 won't be the same anyway.
+        var variant:Bool = (seperatedScore.length == 3) ? (digitIndex >= 2) : (digitIndex >= 1);
+        // var variant:Bool = (seperatedScore.length % 2 != 0) ? (digitIndex % 2 == 0) : (digitIndex % 2 == 1);
+        var numb:ClearPercentNumber = new ClearPercentNumber(xPos, yPos, num, variant, this.small);
+        numb.scale.set(this.scale.x, this.scale.y);
+        add(numb);
+      }
+      else
+      {
+        members[digitIndex].animation.play(Std.string(num));
+        // Reset the position of the number
+        members[digitIndex].x = xPos + this.x;
+        members[digitIndex].y = yPos + this.y;
+      }
+    }
+  }
+}
+
+class ClearPercentNumber extends FlxSprite
+{
+  public function new(x:Float, y:Float, digit:Int, variant:Bool, small:Bool)
+  {
+    super(x, y);
+
+    frames = Paths.getSparrowAtlas('resultScreen/clearPercent/clearPercentNumber${small ? 'Small' : variant ? 'Right' : 'Left'}');
+
+    for (i in 0...10)
+    {
+      animation.addByPrefix('$i', 'number $i 0', 24, false);
+    }
+
+    animation.play('$digit');
+    updateHitbox();
+  }
+}
diff --git a/source/funkin/save/Save.hx b/source/funkin/save/Save.hx
index 7b2d3f511..711b9fcf4 100644
--- a/source/funkin/save/Save.hx
+++ b/source/funkin/save/Save.hx
@@ -847,11 +847,6 @@ typedef SaveScoreData =
    * The count of each judgement hit.
    */
   var tallies:SaveScoreTallyData;
-
-  /**
-   * The accuracy percentage.
-   */
-  var accuracy:Float;
 }
 
 typedef SaveScoreTallyData =
diff --git a/source/funkin/save/migrator/SaveDataMigrator.hx b/source/funkin/save/migrator/SaveDataMigrator.hx
index 3ed59e726..9e308cb10 100644
--- a/source/funkin/save/migrator/SaveDataMigrator.hx
+++ b/source/funkin/save/migrator/SaveDataMigrator.hx
@@ -118,7 +118,7 @@ class SaveDataMigrator
     var scoreDataEasy:SaveScoreData =
       {
         score: inputSaveData.songScores.get('${levelId}-easy') ?? 0,
-        accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0,
+        // accuracy: inputSaveData.songCompletion.get('${levelId}-easy') ?? 0.0,
         tallies:
           {
             sick: 0,
@@ -137,7 +137,7 @@ class SaveDataMigrator
     var scoreDataNormal:SaveScoreData =
       {
         score: inputSaveData.songScores.get('${levelId}') ?? 0,
-        accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0,
+        // accuracy: inputSaveData.songCompletion.get('${levelId}') ?? 0.0,
         tallies:
           {
             sick: 0,
@@ -156,7 +156,7 @@ class SaveDataMigrator
     var scoreDataHard:SaveScoreData =
       {
         score: inputSaveData.songScores.get('${levelId}-hard') ?? 0,
-        accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0,
+        // accuracy: inputSaveData.songCompletion.get('${levelId}-hard') ?? 0.0,
         tallies:
           {
             sick: 0,
@@ -178,7 +178,6 @@ class SaveDataMigrator
     var scoreDataEasy:SaveScoreData =
       {
         score: 0,
-        accuracy: 0,
         tallies:
           {
             sick: 0,
@@ -196,14 +195,13 @@ class SaveDataMigrator
     for (songId in songIds)
     {
       scoreDataEasy.score = Std.int(Math.max(scoreDataEasy.score, inputSaveData.songScores.get('${songId}-easy') ?? 0));
-      scoreDataEasy.accuracy = Math.max(scoreDataEasy.accuracy, inputSaveData.songCompletion.get('${songId}-easy') ?? 0.0);
+      // scoreDataEasy.accuracy = Math.max(scoreDataEasy.accuracy, inputSaveData.songCompletion.get('${songId}-easy') ?? 0.0);
     }
     result.setSongScore(songIds[0], 'easy', scoreDataEasy);
 
     var scoreDataNormal:SaveScoreData =
       {
         score: 0,
-        accuracy: 0,
         tallies:
           {
             sick: 0,
@@ -221,14 +219,13 @@ class SaveDataMigrator
     for (songId in songIds)
     {
       scoreDataNormal.score = Std.int(Math.max(scoreDataNormal.score, inputSaveData.songScores.get('${songId}') ?? 0));
-      scoreDataNormal.accuracy = Math.max(scoreDataNormal.accuracy, inputSaveData.songCompletion.get('${songId}') ?? 0.0);
+      // scoreDataNormal.accuracy = Math.max(scoreDataNormal.accuracy, inputSaveData.songCompletion.get('${songId}') ?? 0.0);
     }
     result.setSongScore(songIds[0], 'normal', scoreDataNormal);
 
     var scoreDataHard:SaveScoreData =
       {
         score: 0,
-        accuracy: 0,
         tallies:
           {
             sick: 0,
@@ -246,7 +243,7 @@ class SaveDataMigrator
     for (songId in songIds)
     {
       scoreDataHard.score = Std.int(Math.max(scoreDataHard.score, inputSaveData.songScores.get('${songId}-hard') ?? 0));
-      scoreDataHard.accuracy = Math.max(scoreDataHard.accuracy, inputSaveData.songCompletion.get('${songId}-hard') ?? 0.0);
+      // scoreDataHard.accuracy = Math.max(scoreDataHard.accuracy, inputSaveData.songCompletion.get('${songId}-hard') ?? 0.0);
     }
     result.setSongScore(songIds[0], 'hard', scoreDataHard);
   }
diff --git a/source/funkin/ui/freeplay/FreeplayState.hx b/source/funkin/ui/freeplay/FreeplayState.hx
index 9e89b728d..c02199dcf 100644
--- a/source/funkin/ui/freeplay/FreeplayState.hx
+++ b/source/funkin/ui/freeplay/FreeplayState.hx
@@ -1012,7 +1012,7 @@ class FreeplayState extends MusicBeatSubState
     {
       var songScore:SaveScoreData = Save.instance.getSongScore(grpCapsules.members[curSelected].songData.songId, currentDifficulty);
       intendedScore = songScore?.score ?? 0;
-      intendedCompletion = songScore?.accuracy ?? 0.0;
+      intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
       rememberedDifficulty = currentDifficulty;
     }
     else
@@ -1210,7 +1210,7 @@ class FreeplayState extends MusicBeatSubState
     {
       var songScore:SaveScoreData = Save.instance.getSongScore(daSongCapsule.songData.songId, currentDifficulty);
       intendedScore = songScore?.score ?? 0;
-      intendedCompletion = songScore?.accuracy ?? 0.0;
+      intendedCompletion = songScore == null ? 0.0 : ((songScore.tallies.sick + songScore.tallies.good) / songScore.tallies.totalNotes);
       diffIdsCurrent = daSongCapsule.songData.songDifficulties;
       rememberedSongId = daSongCapsule.songData.songId;
       changeDiff();
diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx
index 7a21a6e8f..fc2a8c7d7 100644
--- a/source/funkin/ui/mainmenu/MainMenuState.hx
+++ b/source/funkin/ui/mainmenu/MainMenuState.hx
@@ -351,8 +351,7 @@ class MainMenuState extends MusicBeatState
               maxCombo: 0,
               totalNotesHit: 0,
               totalNotes: 0,
-            },
-          accuracy: 0,
+            }
         });
     }
     #end