diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index a203a8d77..f71de00f4 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -225,7 +225,7 @@ class InitState extends FlxState
     // -DRESULTS
     FlxG.switchState(() -> new funkin.play.ResultState(
       {
-        storyMode: false,
+        storyMode: true,
         title: "Cum Song Erect by Kawai Sprite",
         songId: "cum",
         characterId: "pico-playable",
diff --git a/source/funkin/data/freeplay/player/PlayerRegistry.hx b/source/funkin/data/freeplay/player/PlayerRegistry.hx
index c0a15ed1c..76b1c25c1 100644
--- a/source/funkin/data/freeplay/player/PlayerRegistry.hx
+++ b/source/funkin/data/freeplay/player/PlayerRegistry.hx
@@ -71,7 +71,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
 
   public function hasNewCharacter():Bool
   {
-    var characters = Save.instance.charactersSeen.clone();
+    var charactersSeen = Save.instance.charactersSeen.clone();
 
     for (charId in listEntryIds())
     {
@@ -79,7 +79,7 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
       if (player == null) continue;
 
       if (!player.isUnlocked()) continue;
-      if (characters.contains(charId)) continue;
+      if (charactersSeen.contains(charId)) continue;
 
       // This character is unlocked but we haven't seen them in Freeplay yet.
       return true;
@@ -89,6 +89,26 @@ class PlayerRegistry extends BaseRegistry<PlayableCharacter, PlayerData>
     return false;
   }
 
+  public function listNewCharacters():Array<String>
+  {
+    var charactersSeen = Save.instance.charactersSeen.clone();
+    var result = [];
+
+    for (charId in listEntryIds())
+    {
+      var player = fetchEntry(charId);
+      if (player == null) continue;
+
+      if (!player.isUnlocked()) continue;
+      if (charactersSeen.contains(charId)) continue;
+
+      // This character is unlocked but we haven't seen them in Freeplay yet.
+      result.push(charId);
+    }
+
+    return result;
+  }
+
   /**
    * Get the playable character associated with a given stage character.
    * @param characterId The stage character ID.
diff --git a/source/funkin/play/ResultState.hx b/source/funkin/play/ResultState.hx
index ec6069ad4..2878248e2 100644
--- a/source/funkin/play/ResultState.hx
+++ b/source/funkin/play/ResultState.hx
@@ -731,42 +731,59 @@ class ResultState extends MusicBeatSubState
             }
           });
       }
+
+      // Determining the target state(s) to go to.
+      // Default to main menu because that's better than `null`.
+      var targetState:flixel.FlxState = new funkin.ui.mainmenu.MainMenuState();
+      var shouldTween = false;
+
       if (params.storyMode)
       {
-        openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker)));
+        if (PlayerRegistry.instance.hasNewCharacter())
+        {
+          targetState = new StoryMenuState(null);
+
+          var newCharacters = PlayerRegistry.instance.listNewCharacters();
+
+          for (charId in newCharacters)
+          {
+            shouldTween = true;
+            // This works recursively, ehe!
+            targetState = new funkin.ui.charSelect.CharacterUnlockState(charId, targetState);
+          }
+        }
+        else
+        {
+          targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> new StoryMenuState(sticker));
+        }
       }
       else
       {
-        var rigged:Bool = true;
-        if (rank > Scoring.calculateRank(params?.prevScoreData)) // if (rigged)
+        if (rank > Scoring.calculateRank(params?.prevScoreData))
         {
           trace('THE RANK IS Higher.....');
 
-          FlxTween.tween(rankBg, {alpha: 1}, 0.5,
+          shouldTween = true;
+          targetState = FreeplayState.build(
             {
-              ease: FlxEase.expoOut,
-              onComplete: function(_) {
-                FlxG.switchState(FreeplayState.build(
+              {
+                character: playerCharacterId ?? "bf",
+                fromResults:
                   {
-                    {
-                      character: playerCharacterId ?? "bf",
-                      fromResults:
-                        {
-                          oldRank: Scoring.calculateRank(params?.prevScoreData),
-                          newRank: rank,
-                          songId: params.songId,
-                          difficultyId: params.difficultyId,
-                          playRankAnim: true
-                        }
-                    }
-                  }));
+                    oldRank: Scoring.calculateRank(params?.prevScoreData),
+                    newRank: rank,
+                    songId: params.songId,
+                    difficultyId: params.difficultyId,
+                    playRankAnim: true
+                  }
               }
             });
         }
         else
         {
           trace('rank is lower...... and/or equal');
-          openSubState(new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
+
+          targetState = new funkin.ui.transition.StickerSubState(null, (sticker) -> FreeplayState.build(
             {
               {
                 fromResults:
@@ -778,9 +795,24 @@ class ResultState extends MusicBeatSubState
                     difficultyId: params.difficultyId
                   }
               }
-            }, sticker)));
+            }, sticker));
         }
       }
+
+      if (shouldTween)
+      {
+        FlxTween.tween(rankBg, {alpha: 1}, 0.5,
+          {
+            ease: FlxEase.expoOut,
+            onComplete: function(_) {
+              FlxG.switchState(targetState);
+            }
+          });
+      }
+      else
+      {
+        FlxG.switchState(targetState);
+      }
     }
 
     super.update(elapsed);
diff --git a/source/funkin/play/components/HealthIcon.hx b/source/funkin/play/components/HealthIcon.hx
index 0e24d73fb..c11850b2a 100644
--- a/source/funkin/play/components/HealthIcon.hx
+++ b/source/funkin/play/components/HealthIcon.hx
@@ -49,7 +49,7 @@ class HealthIcon extends FunkinSprite
    * this value allows you to set a relative scale for the icon.
    * @default 1x scale = 150px width and height.
    */
-  public var size:FlxPoint = new FlxPoint(1, 1);
+  public var size:FlxPoint;
 
   /**
    * Apply the "bop" animation once every X steps.
@@ -120,11 +120,15 @@ class HealthIcon extends FunkinSprite
   {
     super(0, 0);
     this.playerId = playerId;
+    this.size = new FlxCallbackPoint(onSetSize);
     this.scrollFactor.set();
-
+    size.set(1.0, 1.0);
     this.characterId = char;
+  }
 
-    initTargetSize();
+  function onSetSize(value:FlxPoint):Void
+  {
+    snapToTargetSize();
   }
 
   function set_characterId(value:Null<String>):Null<String>
@@ -243,6 +247,22 @@ class HealthIcon extends FunkinSprite
     this.updateHitbox();
   }
 
+  /*
+   * Immediately snap the health icon to its target size without lerping.
+   */
+  public function snapToTargetSize():Void
+  {
+    if (this.width > this.height)
+    {
+      setGraphicSize(Std.int(HEALTH_ICON_SIZE * this.size.x), 0);
+    }
+    else
+    {
+      setGraphicSize(0, Std.int(HEALTH_ICON_SIZE * this.size.y));
+    }
+    updateHitbox();
+  }
+
   /**
    * Update the position (and status) of the health icon.
    */
@@ -301,12 +321,6 @@ class HealthIcon extends FunkinSprite
     }
   }
 
-  inline function initTargetSize():Void
-  {
-    setGraphicSize(HEALTH_ICON_SIZE);
-    updateHitbox();
-  }
-
   function updateHealthIcon(health:Float):Void
   {
     // We want to efficiently handle animation playback
diff --git a/source/funkin/ui/charSelect/CharacterUnlockState.hx b/source/funkin/ui/charSelect/CharacterUnlockState.hx
new file mode 100644
index 000000000..b32a35145
--- /dev/null
+++ b/source/funkin/ui/charSelect/CharacterUnlockState.hx
@@ -0,0 +1,128 @@
+package funkin.ui.charSelect;
+
+import flixel.FlxSprite;
+import flixel.FlxState;
+import flixel.group.FlxSpriteGroup;
+import flixel.text.FlxText;
+import flixel.tweens.FlxEase;
+import flixel.tweens.FlxTween;
+import flixel.util.FlxColor;
+import funkin.play.character.CharacterData;
+import funkin.play.character.CharacterData.CharacterDataParser;
+import funkin.play.components.HealthIcon;
+import funkin.ui.freeplay.charselect.PlayableCharacter;
+import funkin.data.freeplay.player.PlayerData;
+import funkin.data.freeplay.player.PlayerRegistry;
+import funkin.ui.mainmenu.MainMenuState;
+
+using flixel.util.FlxSpriteUtil;
+
+/**
+ * When you want the player to unlock a character, call `CharacterUnlockState.unlock(characterName)`.
+ * It handles both the act of unlocking the character and displaying the dialog.
+ */
+class CharacterUnlockState extends MusicBeatState
+{
+  public var targetCharacterId:String = "";
+  public var targetCharacterData:Null<PlayableCharacter>;
+
+  var nextState:FlxState;
+
+  static final DIALOG_BG_COLOR:FlxColor = 0xFF000000; // Iconic
+  static final DIALOG_COLOR:FlxColor = 0xFF4344F6; // Iconic
+  static final DIALOG_FONT_COLOR:FlxColor = 0xFFFFFFFF; // Iconic
+
+  var busy:Bool = false;
+
+  public function new(targetPlayableCharacter:String, ?nextState:FlxState)
+  {
+    super();
+
+    this.targetCharacterId = targetPlayableCharacter;
+    this.targetCharacterData = PlayerRegistry.instance.fetchEntry(targetCharacterId);
+    this.nextState = nextState == null ? new MainMenuState() : nextState;
+  }
+
+  override function create():Void
+  {
+    super.create();
+
+    handleMusic();
+
+    bgColor = DIALOG_BG_COLOR;
+
+    var dialogContainer:FlxSpriteGroup = new FlxSpriteGroup();
+    add(dialogContainer);
+
+    // Build the graphic for the text...
+    var charName:String = targetCharacterData != null ? targetCharacterData.getName() : targetCharacterId.toTitleCase();
+    // var dialogText:FlxText = new FlxText(0, 0, 0, 'You can now play as     $charName.\n\nCheck it out in Freeplay!');
+    var dialogText:FlxText = new FlxText(0, 0, 0, 'You can now play as     $charName.');
+    dialogText.setFormat("VCR OSD Mono", 32, DIALOG_FONT_COLOR, LEFT);
+
+    // THEN we can size the dialog to match...
+    var dialogBG:FlxSprite = new FlxSprite(0, 0);
+    dialogBG.makeGraphic(Std.int(dialogText.width + 32), Std.int(dialogText.height + 32), FlxColor.TRANSPARENT);
+    dialogBG.drawRoundRect(0, 0, dialogBG.width, dialogBG.height, 16, 16, DIALOG_COLOR);
+    dialogContainer.add(dialogBG);
+
+    dialogBG.screenCenter(XY);
+
+    // THEN we can position the text inside that.
+    dialogText.x = dialogBG.x + 16;
+    dialogText.y = dialogBG.y + 16;
+    dialogContainer.add(dialogText);
+
+    // HealthIcon handles getting the right frames for us,
+    // but it has a bunch of overhead in it that makes it gross to work with outside the health bar.
+    var healthIconCharacterId = targetCharacterData.getOwnedCharacterIds()[0];
+    var baseCharacter = CharacterDataParser.fetchCharacter(healthIconCharacterId);
+    var healthIcon:HealthIcon = new HealthIcon(healthIconCharacterId);
+    @:privateAccess
+    healthIcon.configure(baseCharacter._data.healthIcon);
+    healthIcon.autoUpdate = false;
+    healthIcon.bopEvery = 0; // You can increase this number later once the animation is done.
+    healthIcon.size.set(0.5, 0.5);
+    healthIcon.x = dialogBG.x + 390;
+    healthIcon.y = dialogBG.y + 6;
+    healthIcon.flipX = true;
+    healthIcon.snapToTargetSize();
+    dialogContainer.add(healthIcon);
+
+    dialogContainer.scale.set(0, 0);
+    FlxTween.num(0.0, 1.0, 0.75,
+      {
+        ease: FlxEase.elasticOut,
+      }, function(curScale) {
+        dialogContainer.scale.set(curScale, curScale);
+        healthIcon.size.set(0.5 * curScale, 0.5 * curScale);
+      });
+
+    // performUnlock();
+  }
+
+  function handleMusic():Void
+  {
+    FlxG.sound.music?.stop();
+    FlxG.sound.play(Paths.sound('confirmMenu'));
+  }
+
+  override function update(elapsed:Float):Void
+  {
+    super.update(elapsed);
+
+    if (controls.ACCEPT || controls.BACK && !busy)
+    {
+      busy = true;
+      startClose();
+    }
+  }
+
+  function startClose():Void
+  {
+    // Fade to black, then switch state.
+    FlxG.camera.fade(FlxColor.BLACK, 0.75, false, () -> {
+      FlxG.switchState(nextState);
+    });
+  }
+}
diff --git a/source/funkin/ui/mainmenu/MainMenuState.hx b/source/funkin/ui/mainmenu/MainMenuState.hx
index d2a9f046a..19dc0d687 100644
--- a/source/funkin/ui/mainmenu/MainMenuState.hx
+++ b/source/funkin/ui/mainmenu/MainMenuState.hx
@@ -344,8 +344,8 @@ class MainMenuState extends MusicBeatState
       }
     }
 
-    #if FEATURE_DEBUG_FUNCTIONS
     // Open the debug menu, defaults to ` / ~
+    // This includes stuff like the Chart Editor, so it should be present on all builds.
     if (controls.DEBUG_MENU)
     {
       persistentUpdate = false;
@@ -353,6 +353,18 @@ class MainMenuState extends MusicBeatState
       FlxG.state.openSubState(new DebugMenuSubState());
     }
 
+    #if FEATURE_DEBUG_FUNCTIONS
+    // Ctrl+Alt+Shift+P = Character Unlock screen
+    // Ctrl+Alt+Shift+W = Meet requirements for Pico Unlock
+    // Ctrl+Alt+Shift+L = Revoke requirements for Pico Unlock
+    // Ctrl+Alt+Shift+R = Score/Rank conflict test
+    // Ctrl+Alt+Shift+E = Dump save data
+
+    if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.P)
+    {
+      FlxG.switchState(() -> new funkin.ui.charSelect.CharacterUnlockState('pico'));
+    }
+
     if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.W)
     {
       FunkinSound.playOnce(Paths.sound('confirmMenu'));