diff --git a/example_mods/introMod/_append/data/introText.txt b/example_mods/introMod/_append/data/introText.txt
deleted file mode 100644
index 45e0c08ab..000000000
--- a/example_mods/introMod/_append/data/introText.txt
+++ /dev/null
@@ -1 +0,0 @@
-swagshit--moneymoney
\ No newline at end of file
diff --git a/example_mods/introMod/assets/preload/data/introText.txt b/example_mods/introMod/assets/preload/data/introText.txt
deleted file mode 100644
index f0dd8224a..000000000
--- a/example_mods/introMod/assets/preload/data/introText.txt
+++ /dev/null
@@ -1 +0,0 @@
-awesomes tream--really awesome
\ No newline at end of file
diff --git a/example_mods/introMod/assets/preload/images/gfDanceTitle.png b/example_mods/introMod/assets/preload/images/gfDanceTitle.png
deleted file mode 100644
index 989f2a68a..000000000
Binary files a/example_mods/introMod/assets/preload/images/gfDanceTitle.png and /dev/null differ
diff --git a/example_mods/testing123/_polymod_icon.png b/example_mods/testing123/_polymod_icon.png
deleted file mode 100644
index 2828bd554..000000000
Binary files a/example_mods/testing123/_polymod_icon.png and /dev/null differ
diff --git a/example_mods/testing123/_polymod_meta.json b/example_mods/testing123/_polymod_meta.json
deleted file mode 100644
index e74efc4f3..000000000
--- a/example_mods/testing123/_polymod_meta.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "title": "Testing123",
-  "description": "Newgrounds? More like OLDGROUNDS lol.",
-  "author": "MasterEric",
-  "api_version": "0.1.0",
-  "mod_version": "1.0.0",
-  "license": "Apache-2.0"
-}
diff --git a/example_mods/testing123/images/newgrounds_logo.png b/example_mods/testing123/images/newgrounds_logo.png
deleted file mode 100644
index 2828bd554..000000000
Binary files a/example_mods/testing123/images/newgrounds_logo.png and /dev/null differ
diff --git a/source/InitState.hx b/source/InitState.hx
index 2183e0d87..7be00f074 100644
--- a/source/InitState.hx
+++ b/source/InitState.hx
@@ -40,7 +40,7 @@ class InitState extends FlxTransitionableState
 	{
 		trace('This is a debug build, loading InitState...');
 		#if android
-		FlxG.android.preventDefaultKeys = [FlxAndroidKey.BACK];
+		FlxG.android.preventDefaultKeys = [flixel.input.android.FlxAndroidKey.BACK];
 		#end
 		#if newgrounds
 		NGio.init();
diff --git a/source/PlayState.hx b/source/PlayState.hx
index b7d900238..98380e0dd 100644
--- a/source/PlayState.hx
+++ b/source/PlayState.hx
@@ -391,53 +391,12 @@ class PlayState extends MusicBeatState
 		{
 			case 'spookeez' | 'monster' | 'south':
 				curStageId = "spookyMansion";
-
-				// TODO: Move lightning strike behavior to a scripted class extending Stage.
 				loadStage(curStageId);
 
 			case 'pico' | 'blammed' | 'philly':
-				curStageId = 'philly';
+				curStageId = 'phillyTrain';
+				loadStage(curStageId);
 
-				var bg:FlxSprite = new FlxSprite(-100).loadGraphic(Paths.image('philly/sky'));
-				bg.scrollFactor.set(0.1, 0.1);
-				add(bg);
-
-				var city:FlxSprite = new FlxSprite(-10).loadGraphic(Paths.image('philly/city'));
-				city.scrollFactor.set(0.3, 0.3);
-				city.setGraphicSize(Std.int(city.width * 0.85));
-				city.updateHitbox();
-				add(city);
-
-				lightFadeShader = new BuildingShaders();
-				phillyCityLights = new FlxTypedGroup<FlxSprite>();
-
-				add(phillyCityLights);
-
-				for (i in 0...5)
-				{
-					var light:FlxSprite = new FlxSprite(city.x).loadGraphic(Paths.image('philly/win' + i));
-					light.scrollFactor.set(0.3, 0.3);
-					light.visible = false;
-					light.setGraphicSize(Std.int(light.width * 0.85));
-					light.updateHitbox();
-					light.antialiasing = true;
-					light.shader = lightFadeShader.shader;
-					phillyCityLights.add(light);
-				}
-
-				var streetBehind:FlxSprite = new FlxSprite(-40, 50).loadGraphic(Paths.image('philly/behindTrain'));
-				add(streetBehind);
-
-				phillyTrain = new FlxSprite(2000, 360).loadGraphic(Paths.image('philly/train'));
-				add(phillyTrain);
-
-				trainSound = new FlxSound().loadEmbedded(Paths.sound('train_passes'));
-				FlxG.sound.list.add(trainSound);
-
-				// var cityLights:FlxSprite = new FlxSprite().loadGraphic(AssetPaths.win0.png);
-
-				var street:FlxSprite = new FlxSprite(-40, streetBehind.y).loadGraphic(Paths.image('philly/street'));
-				add(street);
 			case "milf" | 'satin-panties' | 'high':
 				curStageId = 'limo';
 				defaultCamZoom *= 0.90;
@@ -680,7 +639,7 @@ class PlayState extends MusicBeatState
 			case "darnell":
 				loadStageOld('phillyStreets');
 			default:
-				loadStageOld('stage');
+				loadStage('mainStage');
 		}
 	}
 
@@ -830,8 +789,8 @@ class PlayState extends MusicBeatState
 		{
 			// We're using Eric's stage handler.
 			// Characters get added to the stage, not the main scene.
-			curStage.addCharacter(boyfriend, BF);
 			curStage.addCharacter(gf, GF);
+			curStage.addCharacter(boyfriend, BF);
 			curStage.addCharacter(dad, DAD);
 
 			// Redo z-indexes.
@@ -942,6 +901,35 @@ class PlayState extends MusicBeatState
 		});*/
 	}
 
+	/**
+	 * Removes any references to the current stage, then clears the stage cache,
+	 * then reloads all the stages.
+	 * 
+	 * This is useful for when you want to edit a stage without reloading the whole game.
+	 * Reloading works on both the JSON and the HXC, if applicable.
+	 */
+	function debug_refreshStages()
+	{
+		// Remove the current stage. If the stage gets deleted while it's still in use,
+		// it'll probably crash the game or something.
+		if (this.curStage != null)
+		{
+			remove(curStage);
+			curStage.kill();
+			curStage = null;
+		}
+
+		// Forcibly reload scripts so that scripted stages can be edited.
+		polymod.hscript.PolymodScriptClass.clearScriptClasses();
+		polymod.hscript.PolymodScriptClass.registerAllScriptClasses();
+
+		// Reload the stages in cache. This might cause a lag spike but who cares this is a debug utility.
+		StageDataParser.loadStageCache();
+
+		// Reload the level. This should use new data from the assets folder.
+		LoadingState.loadAndSwitchState(new PlayState());
+	}
+
 	public var curStage:Stage;
 
 	/**
@@ -1983,9 +1971,6 @@ class PlayState extends MusicBeatState
 					}
 				}
 
-				lightFadeShader.update((Conductor.crochet / 1000) * FlxG.elapsed * 1.5);
-			// phillyCityLights.members[curLight].alpha -= (Conductor.crochet / 1000) * FlxG.elapsed;
-
 			case 'tank':
 				moveTank();
 		}
@@ -2006,9 +1991,7 @@ class PlayState extends MusicBeatState
 			persistentDraw = true;
 			paused = true;
 
-			// 1 / 1000 chance for Gitaroo Man easter egg
-			// can this please move to dying it's kinda fucked up that pausing has a 1/1000 chance ur forced to restart
-			if (FlxG.random.bool(0.1))
+			if (FlxG.random.bool(1 / 1000))
 			{
 				// gitaroo man easter egg
 				FlxG.switchState(new GitarooPause());
@@ -2042,6 +2025,10 @@ class PlayState extends MusicBeatState
 		if (FlxG.keys.justPressed.EIGHT)
 			FlxG.switchState(new ui.animDebugShit.DebugBoundingState());
 
+		// get it like refreshing a browser
+		if (FlxG.keys.justPressed.F5)
+			debug_refreshStages();
+
 		if (FlxG.keys.justPressed.NINE)
 			iconP1.swapOldIcon();
 
@@ -2288,6 +2275,12 @@ class PlayState extends MusicBeatState
 
 		if (!inCutscene)
 			keyShit();
+
+		if (curStage != null)
+		{
+			// We're using Eric's stage handler.
+			curStage.onUpdate(elapsed);
+		}
 	}
 
 	function applyClipRect(daNote:Note):Void
@@ -2725,10 +2718,17 @@ class PlayState extends MusicBeatState
 	{
 		openfl.utils.Assets.cache.clear(Paths.inst(SONG.song));
 		openfl.utils.Assets.cache.clear(Paths.voices(SONG.song));
-		curStage.cleanup();
-		curStage = null;
+		if (curStage != null)
+		{
+			remove(curStage);
+			curStage.kill();
+			curStage = null;
+		}
 	}
 
+	/**
+	 * This function is called before switching to a new FlxState.
+	 */
 	override function switchTo(nextState:FlxState):Bool
 	{
 		performCleanup();
diff --git a/source/baseCharPos.txt b/source/baseCharPos.txt
new file mode 100644
index 000000000..fbae4834d
--- /dev/null
+++ b/source/baseCharPos.txt
@@ -0,0 +1,3 @@
+BF: 770, 450
+GF: 400, 130
+DD: 100, 100
\ No newline at end of file
diff --git a/source/modding/IHook.hx b/source/modding/IHook.hx
index a1a75d08d..9b84b2039 100644
--- a/source/modding/IHook.hx
+++ b/source/modding/IHook.hx
@@ -1,6 +1,8 @@
 package modding;
 
+#if polymod
 import polymod.hscript.HScriptable;
+#end
 
 /**
  * Add this interface to a class to make it a scriptable object.
@@ -10,4 +12,4 @@ import polymod.hscript.HScriptable;
 	// ALL of these values are added to ALL scripts in the child classes.
 	context: [FlxG, FlxSprite, Math, Paths, Std]
 })
-interface IHook extends HScriptable {}
+interface IHook #if polymod extends HScriptable #end {}
diff --git a/source/play/stage/Stage.hx b/source/play/stage/Stage.hx
index 3a2d2b578..125fa7c7d 100644
--- a/source/play/stage/Stage.hx
+++ b/source/play/stage/Stage.hx
@@ -1,5 +1,6 @@
 package play.stage;
 
+import flixel.math.FlxPoint;
 import flixel.FlxSprite;
 import flixel.group.FlxSpriteGroup;
 import flixel.util.FlxSort;
@@ -50,6 +51,7 @@ class Stage extends FlxSpriteGroup implements IHook
 		trace('Building stage for display: ${this.stageId}');
 
 		this.camZoom = _data.cameraZoom;
+		// this.scrollFactor = new FlxPoint(1, 1);
 
 		for (dataProp in _data.props)
 		{
@@ -116,6 +118,7 @@ class Stage extends FlxSpriteGroup implements IHook
 	public function refresh()
 	{
 		sort(SortUtil.byZIndex, FlxSort.ASCENDING);
+		trace('Stage sorted by z-index');
 	}
 
 	/**
@@ -125,6 +128,27 @@ class Stage extends FlxSpriteGroup implements IHook
 	public function onUpdate(elapsed:Float):Void
 	{
 		// Override me in your scripted stage to perform custom behavior!
+		// trace('Stage.onUpdate(${elapsed})');
+	}
+
+	/**
+	 * Adjusts the position and other properties of the soon-to-be child of this sprite group.
+	 * Private helper to avoid duplicate code in `add()` and `insert()`.
+	 *
+	 * @param	Sprite	The sprite or sprite group that is about to be added or inserted into the group.
+	 */
+	override function preAdd(Sprite:FlxSprite):Void
+	{
+		var sprite:FlxSprite = cast Sprite;
+		sprite.x += x;
+		sprite.y += y;
+		sprite.alpha *= alpha;
+		// Don't override scroll factors.
+		// sprite.scrollFactor.copyFrom(scrollFactor);
+		sprite.cameras = _cameras; // _cameras instead of cameras because get_cameras() will not return null
+
+		if (clipRect != null)
+			clipRectTransform(sprite, clipRect);
 	}
 
 	/**
@@ -143,6 +167,7 @@ class Stage extends FlxSpriteGroup implements IHook
 	public function onBeatHit(curBeat:Int):Void
 	{
 		// Override me in your scripted stage to perform custom behavior!
+		// trace('Stage.onBeatHit(${curBeat})');
 	}
 
 	/**
@@ -211,10 +236,56 @@ class Stage extends FlxSpriteGroup implements IHook
 		return this.namedProps.get(name);
 	}
 
-	public function cleanup()
+	/**
+	 * Retrieve a list of all the asset paths required to load the stage.
+	 * Override this in a scripted class to ensure that all necessary assets are loaded!
+	 * 
+	 * @return An array of file names.
+	 */
+	public function fetchAssetPaths():Array<String>
 	{
-		this.clear();
+		var result:Array<String> = [];
+		for (dataProp in _data.props)
+		{
+			result.push(Paths.image(dataProp.assetPath));
+		}
+		return result;
+	}
+
+	/**
+	 * Perform cleanup for when you are leaving the level.
+	 */
+	public override function kill()
+	{
+		super.kill();
+
+		for (prop in this.namedProps)
+		{
+			prop.destroy();
+		}
 		namedProps.clear();
+
+		for (char in this.characters)
+		{
+			char.destroy();
+		}
 		characters.clear();
+
+		for (sprite in this.group)
+		{
+			sprite.destroy();
+		}
+		group.clear();
+	}
+
+	/**
+	 * Perform cleanup for when you are destroying the stage
+	 * and removing all its data from cache.
+	 * 
+	 * Call this ONLY when you are performing a hard cache clear.
+	 */
+	public override function destroy()
+	{
+		super.destroy();
 	}
 }
diff --git a/source/play/stage/StageData.hx b/source/play/stage/StageData.hx
index c64acee19..5912ad824 100644
--- a/source/play/stage/StageData.hx
+++ b/source/play/stage/StageData.hx
@@ -30,10 +30,11 @@ class StageDataParser
 	 */
 	public static function loadStageCache():Void
 	{
-		stageCache.clear();
-
+		// Clear any stages that are cached if there were any.
+		clearStageCache();
 		trace("Loading stage cache...");
 
+		#if polymod
 		//
 		// SCRIPTED STAGES
 		//
@@ -45,6 +46,11 @@ class StageDataParser
 			if (stage != null)
 			{
 				trace('    Loaded scripted stage: ${stage.stageName}');
+				// Disable the rendering logic for stage until it's loaded.
+				// Note that kill() =/= destroy()
+				stage.kill();
+
+				// Then store it.
 				stageCache.set(stage.stageId, stage);
 			}
 			else
@@ -52,15 +58,21 @@ class StageDataParser
 				trace('    Failed to instantiate scripted stage class: ${stageCls}');
 			}
 		}
+		#end
 
 		//
 		// UNSCRIPTED STAGES
 		//
 		var stageIdList:Array<String> = DataAssets.listDataFilesInPath('stages/');
-		var unscriptedStageIds:Array<String> = stageIdList.filter(function(stageId:String):Bool
-		{
-			return !stageCache.exists(stageId);
-		});
+		var unscriptedStageIds:Array<String> =
+			#if polymod
+			stageIdList.filter(function(stageId:String):Bool
+			{
+				return !stageCache.exists(stageId);
+			});
+			#else
+			stageIdList;
+			#end
 		trace('  Instantiating ${unscriptedStageIds.length} non-scripted stages...');
 		for (stageId in unscriptedStageIds)
 		{
@@ -80,7 +92,9 @@ class StageDataParser
 		if (stageCache.exists(stageId))
 		{
 			trace('Successfully fetch stage: ${stageId}');
-			return stageCache.get(stageId);
+			var stage:Stage = stageCache.get(stageId);
+			stage.revive();
+			return stage;
 		}
 		else
 		{
@@ -89,6 +103,18 @@ class StageDataParser
 		}
 	}
 
+	static function clearStageCache():Void
+	{
+		if (stageCache != null)
+		{
+			for (stage in stageCache)
+			{
+				stage.destroy();
+			}
+			stageCache.clear();
+		}
+	}
+
 	/**
 	 * Load a stage's JSON file, parse its data, and return it.
 	 *