diff --git a/src/main/java/legend/core/RenderEngine.java b/src/main/java/legend/core/RenderEngine.java
index bae0680a4..b6c7f5323 100644
--- a/src/main/java/legend/core/RenderEngine.java
+++ b/src/main/java/legend/core/RenderEngine.java
@@ -45,7 +45,9 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.Deque;
 import java.util.EnumMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -287,6 +289,8 @@ public class RenderEngine {
 
   private int frameSkipIndex;
 
+  private final Deque<Runnable> tasks = new LinkedList<>();
+
   public RenderEngine() {
     this.mainBatch = new RenderBatch(this, () -> this.vdfUniform, this.vdfBuffer, this.lightBuffer);
     this.scissorStack = new ScissorStack(this, this.mainBatch);
@@ -437,6 +441,13 @@ public Texture getLastFrame() {
     return this.renderTextures[Math.floorMod(this.renderBufferIndex - 1, RENDER_BUFFER_COUNT)];
   }
 
+  /** Submit a task to be run at the start of the next frame */
+  public void addTask(final Runnable task) {
+    synchronized(this.tasks) {
+      this.tasks.push(task);
+    }
+  }
+
   public void init() {
     this.camera2d = new BasicCamera(0.0f, 0.0f);
     this.camera3d = new QuaternionCamera(0.0f, 0.0f, 0.0f);
@@ -553,6 +564,13 @@ public void init() {
     this.renderBufferQuad.persistent = true;
 
     this.window.events.onDraw(() -> {
+      synchronized(this.tasks) {
+        Runnable task;
+        while((task = this.tasks.poll()) != null) {
+          task.run();
+        }
+      }
+
       if(this.frameSkipIndex == 0) {
         this.pre();
       }
diff --git a/src/main/java/legend/game/combat/Battle.java b/src/main/java/legend/game/combat/Battle.java
index 97eb6e543..99b8ac6af 100644
--- a/src/main/java/legend/game/combat/Battle.java
+++ b/src/main/java/legend/game/combat/Battle.java
@@ -1899,10 +1899,13 @@ public void FUN_800c8748() {
 
   @Method(0x800c8774L)
   public void loadStageTmdAndAnim(final String modelName, final List<FileData> files) {
+    LOGGER.info("Battle stage %s loaded", modelName);
+
     this.setStageHasNoModel();
 
     if(files.get(0).size() > 0 && files.get(1).size() > 0 && files.get(2).size() > 0) {
       final BattleStage stage = battlePreloadedEntities_1f8003f4.stage_963c;
+      stage.name = modelName;
       this.loadStageTmd(stage, new CContainer(modelName, files.get(0), 10), new TmdAnimationFile(files.get(1)));
       stage.coord2_558.coord.transfer.set(0, 0, 0);
       stage.param_5a8.rotate.set(0.0f, MathHelper.TWO_PI / 4.0f, 0.0f);
@@ -1974,28 +1977,43 @@ public void renderSkybox() {
 
   @Method(0x800c8b20L)
   public void loadStage(final int stage) {
+    LOGGER.info("Loading battle stage %d", stage);
+
     if(battlePreloadedEntities_1f8003f4.skyboxObj != null) {
       battlePreloadedEntities_1f8003f4.skyboxObj.delete();
       battlePreloadedEntities_1f8003f4.skyboxObj = null;
     }
 
-    loadDrgnDir(0, 2497 + stage, files -> {
-      if(files.get(1).hasVirtualSize()) {
-        this.loadStageMcq(new McqHeader(files.get(1)));
+    // GH#1931
+    // Disable texture animations so we don't corrupt the texture of the loading stage due to the old stage model still being loaded...
+    if(stage_800bda0c != null) {
+      for(int i = 0; i < 10; i++) {
+        stage_800bda0c._618[i] = 0;
       }
+    }
 
-      if(files.get(2).size() != 0) {
-        this.loadStageTim(files.get(2));
-      }
-    });
+    // ... and defer loading to the next frame so that any texture animations currently in the pipeline finish
+    RENDERER.addTask(() -> {
+      loadDrgnDir(0, 2497 + stage, files -> {
+        if(files.get(1).hasVirtualSize()) {
+          this.loadStageMcq(new McqHeader(files.get(1)));
+        }
 
-    loadDrgnDir(0, (2497 + stage) + "/0", files -> this.loadStageTmdAndAnim("DRGN0/" + (2497 + stage) + "/0", files));
+        if(files.get(2).size() != 0) {
+          this.loadStageTim(files.get(2));
+        }
+      });
+
+      loadDrgnDir(0, (2497 + stage) + "/0", files -> this.loadStageTmdAndAnim("DRGN0/" + (2497 + stage) + "/0", files));
+    });
 
     this.currentStage_800c66a4 = stage;
   }
 
   @Method(0x800c8c84L)
   public void loadStageTim(final FileData data) {
+    LOGGER.info("Battle stage texture loaded");
+
     final Tim tim = new Tim(data);
 
     GPU.uploadData15(tim.getImageRect(), tim.getImageData());
@@ -2025,6 +2043,8 @@ public void rotateAndRenderBattleStage() {
 
   @Method(0x800c8d64L)
   public void loadStageMcq(final McqHeader mcq) {
+    LOGGER.info("Battle stage skybox loaded");
+
     final int x;
     if((battleFlags_800bc960 & 0x80) != 0) {
       x = 320;
diff --git a/src/main/java/legend/game/combat/environment/BattleStage.java b/src/main/java/legend/game/combat/environment/BattleStage.java
index 81021db5d..83788568f 100644
--- a/src/main/java/legend/game/combat/environment/BattleStage.java
+++ b/src/main/java/legend/game/combat/environment/BattleStage.java
@@ -8,6 +8,8 @@
 import legend.game.types.Keyframe0c;
 
 public class BattleStage {
+  public String name;
+
   public ModelPart10[] dobj2s_00;
 //  public final GsCOORDINATE2[] coord2s_a0 = new GsCOORDINATE2[10]; // Use coord2 on dobj2
 //  public final Transforms[] params_3c0 = new Transforms[10]; // Use dobj2.coord2.transforms