diff --git a/.gitignore b/.gitignore
index b2fe731ea..34a0c5590 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
 .DS_STORE
 .haxelib/
+.vs/
 APIStuff.hx
 dump/
 export/
diff --git a/assets b/assets
index 181e41088..ed3eb91f4 160000
--- a/assets
+++ b/assets
@@ -1 +1 @@
-Subproject commit 181e410888e7808ed373e14d539048a4ae17feea
+Subproject commit ed3eb91f4b04fa0473128698e0e079a28998401e
diff --git a/source/funkin/InitState.hx b/source/funkin/InitState.hx
index 0ad3c19b8..0a59fb70b 100644
--- a/source/funkin/InitState.hx
+++ b/source/funkin/InitState.hx
@@ -202,8 +202,9 @@ class InitState extends FlxState
     //
     // Plugins provide a useful interface for globally active Flixel objects,
     // that receive update events regardless of the current state.
-    // TODO: Move Module behavior to a Flixel plugin.
+    // TODO: Move scripted Module behavior to a Flixel plugin.
     funkin.util.plugins.EvacuateDebugPlugin.initialize();
+    funkin.util.plugins.ForceCrashPlugin.initialize();
     funkin.util.plugins.ReloadAssetsDebugPlugin.initialize();
     funkin.util.plugins.ScreenshotPlugin.initialize();
     funkin.util.plugins.VolumePlugin.initialize();
diff --git a/source/funkin/ui/debug/MemoryCounter.hx b/source/funkin/ui/debug/MemoryCounter.hx
index 312d853e7..b25b55645 100644
--- a/source/funkin/ui/debug/MemoryCounter.hx
+++ b/source/funkin/ui/debug/MemoryCounter.hx
@@ -1,5 +1,6 @@
 package funkin.ui.debug;
 
+import funkin.util.MemoryUtil;
 import openfl.text.TextFormat;
 import openfl.system.System;
 import openfl.text.TextField;
@@ -35,7 +36,7 @@ class MemoryCounter extends TextField
   @:noCompletion
   #if !flash override #end function __enterFrame(deltaTime:Float):Void
   {
-    var mem:Float = Math.round(System.totalMemory / BYTES_PER_MEG / ROUND_TO) * ROUND_TO;
+    var mem:Float = Math.round(MemoryUtil.getMemoryUsed() / BYTES_PER_MEG / ROUND_TO) * ROUND_TO;
 
     if (mem > memPeak) memPeak = mem;
 
diff --git a/source/funkin/ui/debug/anim/DebugBoundingState.hx b/source/funkin/ui/debug/anim/DebugBoundingState.hx
index 5561a9dcd..46a095a65 100644
--- a/source/funkin/ui/debug/anim/DebugBoundingState.hx
+++ b/source/funkin/ui/debug/anim/DebugBoundingState.hx
@@ -40,6 +40,7 @@ import openfl.utils.ByteArray;
 import funkin.input.Cursor;
 import funkin.play.character.CharacterData.CharacterDataParser;
 import funkin.util.SortUtil;
+import haxe.ui.core.Screen;
 
 using flixel.util.FlxSpriteUtil;
 
@@ -75,21 +76,19 @@ class DebugBoundingState extends FlxState
 
   var uiStuff:Component;
 
+  var haxeUIFocused(get, default):Bool = false;
+
+  function get_haxeUIFocused():Bool
+  {
+    // get the screen position, according to the HUD camera, temp default to FlxG.camera juuust in case?
+    var hudMousePos:FlxPoint = FlxG.mouse.getScreenPosition(hudCam ?? FlxG.camera);
+    return Screen.instance.hasSolidComponentUnderPoint(hudMousePos.x, hudMousePos.y);
+  }
+
   override function create()
   {
     Paths.setCurrentLevel('week1');
 
-    var str = Paths.xml('ui/animation-editor/offset-editor-view');
-    uiStuff = RuntimeComponentBuilder.fromAsset(str);
-
-    // uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
-    var dropdown:DropDown = cast uiStuff.findComponent("swapper");
-    dropdown.onChange = function(e:UIEvent) {
-      trace(e.type);
-      curView = cast e.data.curView;
-      trace(e.data);
-      // trace(e.data);
-    };
     // lv.
     // lv.onChange = function(e:UIEvent)
     // {
@@ -106,23 +105,40 @@ class DebugBoundingState extends FlxState
     hudCam = new FlxCamera();
     hudCam.bgColor.alpha = 0;
 
-    FlxG.cameras.add(hudCam, false);
-
     bg = FlxGridOverlay.create(10, 10);
     // bg = new FlxSprite().makeGraphic(FlxG.width, FlxG.height, FlxColor.GREEN);
 
     bg.scrollFactor.set();
     add(bg);
 
-    initSpritesheetView();
-    initOffsetView();
+    // we are setting this as the default draw camera only temporarily, to trick haxeui
+    FlxG.cameras.add(hudCam);
 
-    Cursor.show();
+    var str = Paths.xml('ui/animation-editor/offset-editor-view');
+    uiStuff = RuntimeComponentBuilder.fromAsset(str);
+
+    // uiStuff.findComponent("btnViewSpriteSheet").onClick = _ -> curView = SPRITESHEET;
+    var dropdown:DropDown = cast uiStuff.findComponent("swapper");
+    dropdown.onChange = function(e:UIEvent) {
+      trace(e.type);
+      curView = cast e.data.curView;
+      trace(e.data);
+      // trace(e.data);
+    };
 
     uiStuff.cameras = [hudCam];
 
     add(uiStuff);
 
+    // sets the default camera back to FlxG.camera, since we set it to hudCamera for haxeui stuf
+    FlxG.cameras.setDefaultDrawTarget(FlxG.camera, true);
+    FlxG.cameras.setDefaultDrawTarget(hudCam, false);
+
+    initSpritesheetView();
+    initOffsetView();
+
+    Cursor.show();
+
     super.create();
   }
 
@@ -359,7 +375,7 @@ class DebugBoundingState extends FlxState
         offsetView.visible = true;
         offsetView.active = true;
         offsetControls();
-        mouseOffsetMovement();
+        if (!haxeUIFocused) mouseOffsetMovement();
     }
 
     if (FlxG.keys.justPressed.H) hudCam.visible = !hudCam.visible;
@@ -367,7 +383,7 @@ class DebugBoundingState extends FlxState
     if (FlxG.keys.justPressed.F4) FlxG.switchState(() -> new MainMenuState());
 
     MouseUtil.mouseCamDrag();
-    MouseUtil.mouseWheelZoom();
+    if (!haxeUIFocused) MouseUtil.mouseWheelZoom();
 
     // bg.scale.x = FlxG.camera.zoom;
     // bg.scale.y = FlxG.camera.zoom;
diff --git a/source/funkin/ui/debug/charting/ChartEditorState.hx b/source/funkin/ui/debug/charting/ChartEditorState.hx
index a00c1681b..75352c21d 100644
--- a/source/funkin/ui/debug/charting/ChartEditorState.hx
+++ b/source/funkin/ui/debug/charting/ChartEditorState.hx
@@ -5258,14 +5258,6 @@ class ChartEditorState extends UIState // UIState derives from MusicBeatState
   {
     // F1 = Open Help
     if (FlxG.keys.justPressed.F1) this.openUserGuideDialog();
-
-    // DEBUG KEYBIND: Ctrl + Alt + Shift + L = Crash the game.
-    #if debug
-    if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.ALT && FlxG.keys.pressed.SHIFT && FlxG.keys.justPressed.L)
-    {
-      throw "DEBUG: Crashing the chart editor!";
-    }
-    #end
   }
 
   function handleQuickWatch():Void
diff --git a/source/funkin/util/MemoryUtil.hx b/source/funkin/util/MemoryUtil.hx
new file mode 100644
index 000000000..6b5f7deea
--- /dev/null
+++ b/source/funkin/util/MemoryUtil.hx
@@ -0,0 +1,113 @@
+package funkin.util;
+
+/**
+ * Utilities for working with the garbage collector.
+ *
+ * HXCPP is built on Immix.
+ * HTML5 builds use the browser's built-in mark-and-sweep and JS has no APIs to interact with it.
+ * @see https://www.cs.cornell.edu/courses/cs6120/2019fa/blog/immix/
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management
+ * @see https://betterprogramming.pub/deep-dive-into-garbage-collection-in-javascript-6881610239a
+ * @see https://github.com/HaxeFoundation/hxcpp/blob/master/docs/build_xml/Defines.md
+ * @see cpp.vm.Gc
+ */
+class MemoryUtil
+{
+  public static function buildGCInfo():String
+  {
+    #if cpp
+    var result = "HXCPP-Immix:";
+    result += '\n- Memory Used: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE)} bytes';
+    result += '\n- Memory Reserved: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_RESERVED)} bytes';
+    result += '\n- Memory Current Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_CURRENT)} bytes';
+    result += '\n- Memory Large Pool: ${cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_LARGE)} bytes';
+    result += '\n- HXCPP Debugger: ${#if HXCPP_DEBUGGER 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Exp Generational Mode: ${#if HXCPP_GC_GENERATIONAL 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_MOVING 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_DYNAMIC_SIZE 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Exp Moving GC: ${#if HXCPP_GC_BIG_BLOCKS 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Debug Link: ${#if HXCPP_DEBUG_LINK 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Stack Trace: ${#if HXCPP_STACK_TRACE 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Stack Trace Line Numbers: ${#if HXCPP_STACK_LINE 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Pointer Validation: ${#if HXCPP_CHECK_POINTER 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Profiler: ${#if HXCPP_PROFILER 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP Local Telemetry: ${#if HXCPP_TELEMETRY 'Enabled' #else 'Disabled' #end}';
+    result += '\n- HXCPP C++11: ${#if HXCPP_CPP11 'Enabled' #else 'Disabled' #end}';
+    result += '\n- Source Annotation: ${#if annotate_source 'Enabled' #else 'Disabled' #end}';
+    #elseif js
+    var result = "JS-MNS:";
+    result += '\n- Memory Used: ${getMemoryUsed()} bytes';
+    #else
+    var result = "Unknown GC";
+    #end
+
+    return result;
+  }
+
+  /**
+   * Calculate the total memory usage of the program, in bytes.
+   * @return Int
+   */
+  public static function getMemoryUsed():Int
+  {
+    #if cpp
+    // There is also Gc.MEM_INFO_RESERVED, MEM_INFO_CURRENT, and MEM_INFO_LARGE.
+    return cpp.vm.Gc.memInfo(cpp.vm.Gc.MEM_INFO_USAGE);
+    #else
+    return openfl.system.System.totalMemory;
+    #end
+  }
+
+  /**
+   * Enable garbage collection if it was previously disabled.
+   */
+  public static function enable():Void
+  {
+    #if cpp
+    cpp.vm.Gc.enable(true);
+    #else
+    throw "Not implemented!";
+    #end
+  }
+
+  /**
+   * Disable garbage collection entirely.
+   */
+  public static function disable():Void
+  {
+    #if cpp
+    cpp.vm.Gc.enable(false);
+    #else
+    throw "Not implemented!";
+    #end
+  }
+
+  /**
+   * Manually perform garbage collection once.
+   * Should only be called from the main thread.
+   * @param major `true` to perform major collection, whatever that means.
+   */
+  public static function collect(major:Bool = false):Void
+  {
+    #if cpp
+    cpp.vm.Gc.run(major);
+    #else
+    throw "Not implemented!";
+    #end
+  }
+
+  /**
+   * Perform major garbage collection repeatedly until less than 16kb of memory is freed in one operation.
+   * Should only be called from the main thread.
+   *
+   * NOTE: This is DIFFERENT from actual compaction,
+   */
+  public static function compact():Void
+  {
+    #if cpp
+    cpp.vm.Gc.compact();
+    #else
+    throw "Not implemented!";
+    #end
+  }
+}
diff --git a/source/funkin/util/logging/CrashHandler.hx b/source/funkin/util/logging/CrashHandler.hx
index ad5983e52..ef293486d 100644
--- a/source/funkin/util/logging/CrashHandler.hx
+++ b/source/funkin/util/logging/CrashHandler.hx
@@ -125,6 +125,24 @@ class CrashHandler
 
     fullContents += '=====================\n';
 
+    fullContents += '\n';
+
+    fullContents += MemoryUtil.buildGCInfo();
+
+    fullContents += '\n\n';
+
+    fullContents += '=====================\n';
+
+    fullContents += '\n';
+
+    fullContents += 'Flixel Current State: ${Type.getClassName(Type.getClass(FlxG.state))}\n';
+
+    fullContents += '\n';
+
+    fullContents += '=====================\n';
+
+    fullContents += '\n';
+
     fullContents += 'Haxelibs: \n';
 
     for (lib in Constants.LIBRARY_VERSIONS)
diff --git a/source/funkin/util/plugins/ForceCrashPlugin.hx b/source/funkin/util/plugins/ForceCrashPlugin.hx
new file mode 100644
index 000000000..e8094eb3c
--- /dev/null
+++ b/source/funkin/util/plugins/ForceCrashPlugin.hx
@@ -0,0 +1,37 @@
+package funkin.util.plugins;
+
+import flixel.FlxBasic;
+
+/**
+ * A plugin which forcibly crashes the application.
+ * TODO: Should we disable this in release builds?
+ */
+class ForceCrashPlugin extends FlxBasic
+{
+  public function new()
+  {
+    super();
+  }
+
+  public static function initialize():Void
+  {
+    FlxG.plugins.addPlugin(new ForceCrashPlugin());
+  }
+
+  public override function update(elapsed:Float):Void
+  {
+    super.update(elapsed);
+
+    // Ctrl + Shift + L = Crash the game for debugging purposes
+    if (FlxG.keys.pressed.CONTROL && FlxG.keys.pressed.SHIFT && FlxG.keys.pressed.L)
+    {
+      // TODO: Make this message 87% funnier.
+      throw "DEBUG: Crashing the game via debug keybind!";
+    }
+  }
+
+  public override function destroy():Void
+  {
+    super.destroy();
+  }
+}
diff --git a/source/funkin/util/plugins/ScreenshotPlugin.hx b/source/funkin/util/plugins/ScreenshotPlugin.hx
index 16d0c7244..d7e8109b8 100644
--- a/source/funkin/util/plugins/ScreenshotPlugin.hx
+++ b/source/funkin/util/plugins/ScreenshotPlugin.hx
@@ -13,6 +13,7 @@ import flixel.util.FlxSignal;
 import flixel.util.FlxTimer;
 import funkin.graphics.FunkinSprite;
 import funkin.input.Cursor;
+import funkin.audio.FunkinSound;
 import openfl.display.Bitmap;
 import openfl.display.Sprite;
 import openfl.display.BitmapData;
@@ -162,7 +163,7 @@ class ScreenshotPlugin extends FlxBasic
   final CAMERA_FLASH_DURATION = 0.25;
 
   /**
-   * Visual (and audio?) feedback when a screenshot is taken.
+   * Visual and audio feedback when a screenshot is taken.
    */
   function showCaptureFeedback():Void
   {
@@ -171,6 +172,9 @@ class ScreenshotPlugin extends FlxBasic
     flashSpr.addChild(flashBitmap);
     FlxG.stage.addChild(flashSpr);
     FlxTween.tween(flashSpr, {alpha: 0}, 0.15, {ease: FlxEase.quadOut, onComplete: _ -> FlxG.stage.removeChild(flashSpr)});
+
+    // Play a sound (auto-play is true).
+    FunkinSound.load(Paths.sound('screenshot'), 1.0, false, true, true);
   }
 
   static final PREVIEW_INITIAL_DELAY = 0.25; // How long before the preview starts fading in.