diff --git a/enhancements/platformdisplacement2.patch b/enhancements/platformdisplacement2.patch
new file mode 100644
index 0000000..6bf33a8
--- /dev/null
+++ b/enhancements/platformdisplacement2.patch
@@ -0,0 +1,288 @@
+diff --git a/src/game/behaviors/bowser.inc.c b/src/game/behaviors/bowser.inc.c
+index ebc6bf4..987f456 100644
+--- a/src/game/behaviors/bowser.inc.c
++++ b/src/game/behaviors/bowser.inc.c
+@@ -972,6 +972,8 @@ s32 bowser_check_fallen_off_stage(void) // bowser off stage?
+     return 0;
+ }
+ 
++struct PlatformDisplacementInfo sBowserDisplacementInfo;
++
+ void (*sBowserActions[])(void) = { bowser_act_default,  bowser_act_thrown_dropped,  bowser_act_jump_onto_stage,  bowser_act_dance,
+                                    bowser_act_dead,  bowser_act_text_wait,  bowser_act_intro_walk,  bowser_act_charge_mario,
+                                    bowser_act_spit_fire_into_sky,  bowser_act_spit_fire_onto_floor,  bowser_act_hit_edge, bowser_act_turn_from_edge,
+@@ -1033,7 +1035,7 @@ void bowser_free_update(void) {
+     struct Object *platform;
+     UNUSED f32 floorHeight;
+     if ((platform = o->platform) != NULL)
+-        apply_platform_displacement(0, platform);
++        apply_platform_displacement(&sBowserDisplacementInfo, &o->oPosX, &o->oFaceAngleYaw, platform);
+     o->oBowserUnk10E = 0;
+     cur_obj_update_floor_and_walls();
+     cur_obj_call_action_function(sBowserActions);
+diff --git a/src/game/behaviors/tilting_inverted_pyramid.inc.c b/src/game/behaviors/tilting_inverted_pyramid.inc.c
+index ebce64f..29136e9 100644
+--- a/src/game/behaviors/tilting_inverted_pyramid.inc.c
++++ b/src/game/behaviors/tilting_inverted_pyramid.inc.c
+@@ -131,7 +131,7 @@ void bhv_tilting_inverted_pyramid_loop(void) {
+         mx += posAfterRotation[0] - posBeforeRotation[0];
+         my += posAfterRotation[1] - posBeforeRotation[1];
+         mz += posAfterRotation[2] - posBeforeRotation[2];
+-        set_mario_pos(mx, my, mz);
++        //set_mario_pos(mx, my, mz);
+     }
+ 
+     o->header.gfx.throwMatrix = transform;
+diff --git a/src/game/platform_displacement.c b/src/game/platform_displacement.c
+index 29a741c..d1bb016 100644
+--- a/src/game/platform_displacement.c
++++ b/src/game/platform_displacement.c
+@@ -8,6 +8,7 @@
+ #include "object_list_processor.h"
+ #include "platform_displacement.h"
+ #include "types.h"
++#include "sm64.h"
+ 
+ u16 D_8032FEC0 = 0;
+ 
+@@ -84,96 +85,139 @@ void set_mario_pos(f32 x, f32 y, f32 z) {
+     gMarioStates[0].pos[2] = z;
+ }
+ 
+-/**
+- * Apply one frame of platform rotation to Mario or an object using the given
+- * platform. If isMario is 0, use gCurrentObject.
+- */
+-void apply_platform_displacement(u32 isMario, struct Object *platform) {
+-    f32 x;
+-    f32 y;
+-    f32 z;
+-    f32 platformPosX;
+-    f32 platformPosY;
+-    f32 platformPosZ;
+-    Vec3f currentObjectOffset;
+-    Vec3f relativeOffset;
+-    Vec3f newObjectOffset;
+-    Vec3s rotation;
+-    UNUSED s16 unused1;
+-    UNUSED s16 unused2;
+-    UNUSED s16 unused3;
+-    f32 displaceMatrix[4][4];
+-
+-    rotation[0] = platform->oAngleVelPitch;
+-    rotation[1] = platform->oAngleVelYaw;
+-    rotation[2] = platform->oAngleVelRoll;
+-
+-    if (isMario) {
+-        D_8032FEC0 = 0;
+-        get_mario_pos(&x, &y, &z);
+-    } else {
+-        x = gCurrentObject->oPosX;
+-        y = gCurrentObject->oPosY;
+-        z = gCurrentObject->oPosZ;
+-    }
+-
+-    x += platform->oVelX;
+-    z += platform->oVelZ;
++static struct PlatformDisplacementInfo sMarioDisplacementInfo;
++static Vec3f sMarioAmountDisplaced;
+ 
+-    if (rotation[0] != 0 || rotation[1] != 0 || rotation[2] != 0) {
+-        unused1 = rotation[0];
+-        unused2 = rotation[2];
+-        unused3 = platform->oFaceAngleYaw;
++extern s32 gGlobalTimer;
+ 
+-        if (isMario) {
+-            gMarioStates[0].faceAngle[1] += rotation[1];
+-        }
+-
+-        platformPosX = platform->oPosX;
+-        platformPosY = platform->oPosY;
+-        platformPosZ = platform->oPosZ;
+-
+-        currentObjectOffset[0] = x - platformPosX;
+-        currentObjectOffset[1] = y - platformPosY;
+-        currentObjectOffset[2] = z - platformPosZ;
+-
+-        rotation[0] = platform->oFaceAnglePitch - platform->oAngleVelPitch;
+-        rotation[1] = platform->oFaceAngleYaw - platform->oAngleVelYaw;
+-        rotation[2] = platform->oFaceAngleRoll - platform->oAngleVelRoll;
+-
+-        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
+-        linear_mtxf_transpose_mul_vec3f(displaceMatrix, relativeOffset, currentObjectOffset);
++/**
++ * Upscale or downscale a vector by another vector.
++ */
++static void scale_vec3f(Vec3f dst, Vec3f src, Vec3f scale, u32 doInverted) {
++	if (doInverted) {
++		dst[0] = src[0] / scale[0];
++		dst[1] = src[1] / scale[1];
++		dst[2] = src[2] / scale[2];
++	} else {
++		dst[0] = src[0] * scale[0];
++		dst[1] = src[1] * scale[1];
++		dst[2] = src[2] * scale[2];
++	}
++}
+ 
+-        rotation[0] = platform->oFaceAnglePitch;
+-        rotation[1] = platform->oFaceAngleYaw;
+-        rotation[2] = platform->oFaceAngleRoll;
++/**
++ * Apply one frame of platform displacement to Mario or an object using the given
++ * platform.
++ */
++void apply_platform_displacement(struct PlatformDisplacementInfo *displaceInfo, Vec3f pos, s16 *yaw, struct Object *platform) {
++	Vec3f platformPos;
++	Vec3f posDifference;
++	Vec3f yawVec;
++	Vec3f scaledPos;
++	// Determine how much Mario turned on his own since last frame
++	s16 yawDifference = *yaw - displaceInfo->prevYaw;
++
++	// Avoid a crash if the platform unloaded its collision while stood on
++	if (platform->header.gfx.throwMatrix == NULL) return;
++
++	vec3f_copy(platformPos, (*platform->header.gfx.throwMatrix)[3]);
++
++	// Determine how far Mario moved on his own since last frame
++	vec3f_copy(posDifference, pos);
++	vec3f_sub(posDifference, displaceInfo->prevPos);
++
++	if ((platform == displaceInfo->prevPlatform) && (gGlobalTimer == displaceInfo->prevTimer + 1)) {
++		// Transform from relative positions to world positions
++		scale_vec3f(scaledPos, displaceInfo->prevTransformedPos, platform->header.gfx.scale, FALSE);
++		linear_mtxf_mul_vec3f(*platform->header.gfx.throwMatrix, pos, scaledPos);
++
++		// Add on how much Mario moved in the previous frame
++		vec3f_add(pos, posDifference);
++
++		// Calculate new yaw
++		linear_mtxf_mul_vec3f(*platform->header.gfx.throwMatrix, yawVec, displaceInfo->prevTransformedYawVec);
++		*yaw = atan2s(yawVec[2], yawVec[0]) + yawDifference;
++	} else {
++		// First frame of standing on the platform, don't calculate a new position
++		vec3f_sub(pos, platformPos);
++	}
++
++	// Transform from world positions to relative positions for use next frame
++	linear_mtxf_transpose_mul_vec3f(*platform->header.gfx.throwMatrix, scaledPos, pos);
++	scale_vec3f(displaceInfo->prevTransformedPos, scaledPos, platform->header.gfx.scale, TRUE);
++	vec3f_add(pos, platformPos);
++
++	// If the object is Mario, set inertia
++	if (pos == gMarioState->pos) {
++		vec3f_copy(sMarioAmountDisplaced, pos);
++		vec3f_sub(sMarioAmountDisplaced, displaceInfo->prevPos);
++		vec3f_sub(sMarioAmountDisplaced, posDifference);
++
++		// Make sure inertia isn't set on the first frame otherwise the previous value isn't cleared
++		if ((platform != displaceInfo->prevPlatform) || (gGlobalTimer != displaceInfo->prevTimer + 1)) {
++			vec3f_set(sMarioAmountDisplaced, 0.f, 0.f, 0.f);
++		}
++	}
++
++	// Update info for next frame
++	// Update position
++	vec3f_copy(displaceInfo->prevPos, pos);
++
++	// Set yaw info
++	vec3f_set(yawVec, sins(*yaw), 0, coss(*yaw));
++	linear_mtxf_transpose_mul_vec3f(*platform->header.gfx.throwMatrix, displaceInfo->prevTransformedYawVec, yawVec);
++	displaceInfo->prevYaw = *yaw;
++
++	// Update platform and timer
++	displaceInfo->prevPlatform = platform;
++	displaceInfo->prevTimer = gGlobalTimer;
++}
+ 
+-        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
+-        linear_mtxf_mul_vec3f(displaceMatrix, newObjectOffset, relativeOffset);
++// Doesn't change in the code, set this to FALSE if you don't want inertia
++u8 gDoInertia = TRUE;
+ 
+-        x = platformPosX + newObjectOffset[0];
+-        y = platformPosY + newObjectOffset[1];
+-        z = platformPosZ + newObjectOffset[2];
+-    }
++static u8 sShouldApplyInertia = FALSE;
++static u8 sInertiaFirstFrame = FALSE;
+ 
+-    if (isMario) {
+-        set_mario_pos(x, y, z);
+-    } else {
+-        gCurrentObject->oPosX = x;
+-        gCurrentObject->oPosY = y;
+-        gCurrentObject->oPosZ = z;
+-    }
++/**
++ * Apply inertia based on Mario's last platform.
++ */
++static void apply_mario_inertia(void) {
++	// On the first frame of leaving the ground, boost Mario's y velocity
++	if (sInertiaFirstFrame) {
++		gMarioState->vel[1] += sMarioAmountDisplaced[1];
++	}
++
++	// Apply sideways inertia
++	gMarioState->pos[0] += sMarioAmountDisplaced[0];
++	gMarioState->pos[2] += sMarioAmountDisplaced[2];
++
++	// Drag
++	sMarioAmountDisplaced[0] *= 0.97f;
++	sMarioAmountDisplaced[2] *= 0.97f;
++
++	// Stop applying inertia once Mario has landed, or when ground pounding
++	if (!(gMarioState->action & ACT_FLAG_AIR) || (gMarioState->action == ACT_GROUND_POUND)) {
++		sShouldApplyInertia = FALSE;
++	}
+ }
+ 
+ /**
+- * If Mario's platform is not null, apply platform displacement.
++ * Apply platform displacement or inertia if required.
+  */
+ void apply_mario_platform_displacement(void) {
+     struct Object *platform;
+ 
+     platform = gMarioPlatform;
+-    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL && platform != NULL) {
+-        apply_platform_displacement(1, platform);
++    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL) {
++		if (platform != NULL) {
++			apply_platform_displacement(&sMarioDisplacementInfo, gMarioState->pos, &gMarioState->faceAngle[1], platform);
++			sShouldApplyInertia = TRUE;
++			sInertiaFirstFrame = TRUE;
++		} else if (sShouldApplyInertia && gDoInertia) {
++			apply_mario_inertia();
++			sInertiaFirstFrame = FALSE;
++		}
+     }
+ }
+ 
+diff --git a/src/game/platform_displacement.h b/src/game/platform_displacement.h
+index 556192b..3609e2d 100644
+--- a/src/game/platform_displacement.h
++++ b/src/game/platform_displacement.h
+@@ -5,10 +5,19 @@
+ 
+ #include "types.h"
+ 
++struct PlatformDisplacementInfo {
++	Vec3f prevPos;
++	Vec3f prevTransformedPos;
++	Vec3f prevTransformedYawVec;
++	s16 prevYaw;
++	struct Object *prevPlatform;
++	s32 prevTimer;
++};
++
+ void update_mario_platform(void);
+ void get_mario_pos(f32 *x, f32 *y, f32 *z);
+ void set_mario_pos(f32 x, f32 y, f32 z);
+-void apply_platform_displacement(u32 isMario, struct Object *platform);
++void apply_platform_displacement(struct PlatformDisplacementInfo *displaceInfo, Vec3f pos, s16 *yaw, struct Object *platform);
+ void apply_mario_platform_displacement(void);
+ #ifndef VERSION_JP
+ void clear_mario_platform(void);
diff --git a/src/game/behaviors/bowser.inc.c b/src/game/behaviors/bowser.inc.c
index 97cba2a..410e612 100644
--- a/src/game/behaviors/bowser.inc.c
+++ b/src/game/behaviors/bowser.inc.c
@@ -972,6 +972,8 @@ s32 bowser_check_fallen_off_stage(void) // bowser off stage?
     return 0;
 }
 
+struct PlatformDisplacementInfo sBowserDisplacementInfo;
+
 void (*sBowserActions[])(void) = { bowser_act_default,  bowser_act_thrown_dropped,  bowser_act_jump_onto_stage,  bowser_act_dance,
                                    bowser_act_dead,  bowser_act_text_wait,  bowser_act_intro_walk,  bowser_act_charge_mario,
                                    bowser_act_spit_fire_into_sky,  bowser_act_spit_fire_onto_floor,  bowser_act_hit_edge, bowser_act_turn_from_edge,
@@ -1032,8 +1034,9 @@ void bowser_free_update(void) {
     struct Surface *floor;
     struct Object *platform;
     UNUSED f32 floorHeight;
-    if ((platform = o->platform) != NULL)
-        apply_platform_displacement(FALSE, platform);
+    if ((platform = o->platform) != NULL) {
+        apply_platform_displacement(&sBowserDisplacementInfo, &o->oPosX, &o->oFaceAngleYaw, platform);
+    }
     o->oBowserUnk10E = 0;
     cur_obj_update_floor_and_walls();
     cur_obj_call_action_function(sBowserActions);
diff --git a/src/game/behaviors/bowser.inc.c.orig b/src/game/behaviors/bowser.inc.c.orig
new file mode 100644
index 0000000..97cba2a
--- /dev/null
+++ b/src/game/behaviors/bowser.inc.c.orig
@@ -0,0 +1,1586 @@
+// bowser.c.inc
+
+void bowser_tail_anchor_act_0(void) {
+    struct Object *bowser = o->parentObj;
+    cur_obj_become_tangible();
+    cur_obj_scale(1.0f);
+    if (bowser->oAction == 19)
+        bowser->oIntangibleTimer = -1;
+    else if (obj_check_if_collided_with_object(o, gMarioObject)) {
+        bowser->oIntangibleTimer = 0;
+        o->oAction = 2;
+    } else
+        bowser->oIntangibleTimer = -1;
+}
+
+void bowser_tail_anchor_act_1(void) {
+    if (o->oTimer > 30)
+        o->oAction = 0;
+}
+
+void bowser_tail_anchor_act_2(void) {
+    if (o->parentObj->oAction == 19) {
+        o->parentObj->oIntangibleTimer = -1;
+        o->oAction = 0;
+    }
+    cur_obj_become_intangible();
+}
+
+void (*sBowserTailAnchorActions[])(void) = { bowser_tail_anchor_act_0, bowser_tail_anchor_act_1,
+                                             bowser_tail_anchor_act_2 };
+s8 D_8032F4FC[] = { 7, 8, 9, 12, 13, 14, 15, 4, 3, 16, 17, 19, 3, 3, 3, 3 };
+s16 D_8032F50C[] = { 60, 0 };
+s16 D_8032F510[] = { 50, 0 };
+s8 D_8032F514[] = { 24, 42, 60, -1 };
+s16 sBowserDefeatedDialogText[3] = { DIALOG_119, DIALOG_120, DIALOG_121 };
+s16 D_8032F520[][3] = { { 1, 10, 40 },   { 0, 0, 74 },    { -1, -10, 114 },  { 1, -20, 134 },
+                        { -1, 20, 154 }, { 1, 40, 164 },  { -1, -40, 174 },  { 1, -80, 179 },
+                        { -1, 80, 184 }, { 1, 160, 186 }, { -1, -160, 186 }, { 1, 0, 0 }, };
+
+void bhv_bowser_tail_anchor_loop(void) {
+    cur_obj_call_action_function(sBowserTailAnchorActions);
+    o->oParentRelativePosX = 90.0f;
+    if (o->parentObj->oAction == 4)
+        o->parentObj->oIntangibleTimer = -1;
+    o->oInteractStatus = 0;
+}
+
+void bhv_bowser_flame_spawn_loop(void) {
+    struct Object *bowser = o->parentObj;
+    s32 sp30;
+    f32 sp2C;
+    f32 sp28;
+    f32 sp24 = coss(bowser->oMoveAngleYaw);
+    f32 sp20 = sins(bowser->oMoveAngleYaw);
+    s16 *sp1C = segmented_to_virtual(bowser_seg6_unkmoveshorts_060576FC);
+    if (bowser->oSoundStateID == 6) {
+        sp30 = bowser->header.gfx.animInfo.animFrame + 1.0f;
+        if (bowser->header.gfx.animInfo.curAnim->loopEnd == sp30)
+            sp30 = 0;
+        if (sp30 > 45 && sp30 < 85) {
+            cur_obj_play_sound_1(SOUND_AIR_BOWSER_SPIT_FIRE);
+            sp2C = sp1C[5 * sp30];
+            sp28 = sp1C[5 * sp30 + 2];
+            o->oPosX = bowser->oPosX + (sp28 * sp20 + sp2C * sp24);
+            o->oPosY = bowser->oPosY + sp1C[5 * sp30 + 1];
+            o->oPosZ = bowser->oPosZ + (sp28 * sp24 - sp2C * sp20);
+            o->oMoveAnglePitch = sp1C[5 * sp30 + 4] + 0xC00;
+            o->oMoveAngleYaw = sp1C[5 * sp30 + 3] + (s16) bowser->oMoveAngleYaw;
+            if (!(sp30 & 1))
+                spawn_object(o, MODEL_RED_FLAME, bhvFlameMovingForwardGrowing);
+        }
+    }
+}
+
+void bhv_bowser_body_anchor_loop(void) {
+    obj_copy_pos_and_angle(o, o->parentObj);
+    if (o->parentObj->oAction == 4) {
+#ifndef VERSION_JP
+        if (o->parentObj->oSubAction == 11)
+            o->oInteractType = 0;
+        else
+            o->oInteractType = 0x800000;
+#else
+        o->oInteractType = 0x800000;
+#endif
+    } else {
+        o->oInteractType = 8;
+        if (o->parentObj->oOpacity < 100)
+            cur_obj_become_intangible();
+        else
+            cur_obj_become_tangible();
+    }
+    if (o->parentObj->oHeldState != HELD_FREE)
+        cur_obj_become_intangible();
+    o->oInteractStatus = 0;
+}
+
+s32 bowser_spawn_shockwave(void) {
+    struct Object *wave;
+    if (o->oBehParams2ndByte == 2) {
+        wave = spawn_object(o, MODEL_BOWSER_WAVE, bhvBowserShockWave);
+        wave->oPosY = o->oFloorHeight;
+        return 1;
+    }
+    return 0;
+}
+
+void bowser_bounce(s32 *a) {
+    if (o->oMoveFlags & OBJ_MOVE_LANDED) {
+        a[0]++;
+        if (a[0] < 4) {
+            cur_obj_start_cam_event(o, CAM_EVENT_BOWSER_THROW_BOUNCE);
+            spawn_mist_particles_variable(0, 0, 60.0f);
+            cur_obj_play_sound_2(SOUND_OBJ_BOWSER_WALK);
+        }
+    }
+}
+
+#define BITDW (o->oBehParams2ndByte == 0)
+#define BITFS (o->oBehParams2ndByte == 1)
+#define BITS (o->oBehParams2ndByte == 2)
+
+s32 bowser_set_anim_look_up_and_walk(void) {
+    cur_obj_init_animation_with_sound(15);
+    if (cur_obj_check_anim_frame(21))
+        o->oForwardVel = 3.0f;
+    if (cur_obj_check_if_near_animation_end())
+        return 1;
+    else
+        return 0;
+}
+
+s32 bowser_set_anim_slow_gait(void) {
+    o->oForwardVel = 3.0f;
+    cur_obj_init_animation_with_sound(13);
+    if (cur_obj_check_if_near_animation_end())
+        return 1;
+    else
+        return 0;
+}
+
+s32 bowser_set_anim_look_down(void) {
+    cur_obj_init_animation_with_sound(14);
+    if (cur_obj_check_anim_frame(20))
+        o->oForwardVel = 0.0f;
+    if (cur_obj_check_if_near_animation_end())
+        return 1;
+    else
+        return 0;
+}
+
+void bowser_initialize_action(void) {
+    if (o->oBowserUnk88 == 0)
+        o->oAction = 5;
+    else if (o->oBowserUnk88 == 1)
+        o->oAction = 6;
+    else if (o->oBehParams2ndByte == 1)
+        o->oAction = 13;
+    else
+        o->oAction = 0;
+}
+
+void bowser_act_text_wait(void) // not much
+{
+    o->oForwardVel = 0.0f;
+    cur_obj_init_animation_with_sound(12);
+    bowser_initialize_action();
+}
+
+void bowser_act_intro_walk(void) {
+    if (o->oSubAction == 0) {
+        if (bowser_set_anim_look_up_and_walk())
+            o->oSubAction++;
+    } else if (o->oSubAction == 1) {
+        if (bowser_set_anim_slow_gait())
+            o->oSubAction++;
+    } else if (bowser_set_anim_look_down()) {
+        if (o->oBowserUnk88 == 1)
+            o->oBowserUnk88 = 0;
+        bowser_initialize_action();
+    }
+}
+
+static void bowser_debug_actions(void) // unused
+{
+    if (gDebugInfo[5][1] != 0) {
+        o->oAction = D_8032F4FC[gDebugInfo[5][2] & 0xf];
+        gDebugInfo[5][1] = 0;
+    }
+}
+
+void bowser_bitdw_act_controller(void) {
+    f32 rand = random_float();
+    if (o->oBowserUnk110 == 0) {
+        if (o->oBowserUnkF4 & 2) {
+            if (o->oDistanceToMario < 1500.0f)
+                o->oAction = 15; // nearby
+            else
+                o->oAction = 17; // far away
+        } else
+            o->oAction = 14;
+        o->oBowserUnk110++;
+    } else {
+        o->oBowserUnk110 = 0;
+#ifndef VERSION_JP
+        if (!gCurrDemoInput) {
+            if (rand < 0.1)
+                o->oAction = 3; // rare 1/10 chance
+            else
+                o->oAction = 14; // common
+        } else {
+            o->oAction = 14; // ensure demo starts with action 14.
+        }
+#else
+        if (rand < 0.1)
+            o->oAction = 3; // rare 1/10 chance
+        else
+            o->oAction = 14; // common
+#endif
+    }
+}
+
+void bowser_bitfs_act_controller(void) {
+    f32 rand = random_float();
+    if (o->oBowserUnk110 == 0) {
+        if (o->oBowserUnkF4 & 2) {
+            if (o->oDistanceToMario < 1300.0f) // nearby
+            {
+                if (rand < 0.5) // 50/50
+                    o->oAction = 16;
+                else
+                    o->oAction = 9;
+            } else // far away
+            {
+                o->oAction = 7;
+                if (500.0f < o->oBowserDistToCentre && o->oBowserDistToCentre < 1500.0f
+                    && rand < 0.5) // away from centre and good luck
+                    o->oAction = 13;
+            }
+        } else
+            o->oAction = 14;
+        o->oBowserUnk110++;
+    } else {
+        o->oBowserUnk110 = 0;
+        o->oAction = 14;
+    }
+}
+
+void bowser_general_bits_act_controller(void) {
+    f32 rand = random_float();
+    if (o->oBowserUnkF4 & 2) {
+        if (o->oDistanceToMario < 1000.0f) {
+            if (rand < 0.4)
+                o->oAction = 9;
+            else if (rand < 0.8)
+                o->oAction = 8;
+            else
+                o->oAction = 15;
+        } else if (rand < 0.5)
+            o->oAction = 13;
+        else
+            o->oAction = 7;
+    } else
+        o->oAction = 14;
+}
+
+void bowser_set_act_jump(void) {
+    o->oAction = 13;
+}
+
+void bowser_bits_act_controller(void) {
+    switch (o->oBowserUnk110) {
+        case 0:
+            if (o->oBowserUnk106 == 0)
+                bowser_general_bits_act_controller();
+            else
+                bowser_set_act_jump();
+            o->oBowserUnk110 = 1;
+            break;
+        case 1:
+            o->oBowserUnk110 = 0;
+            o->oAction = 14;
+            break;
+    }
+}
+
+#ifndef VERSION_JP
+void bowser_reset_fallen_off_stage(void) {
+    if (o->oVelY < 0 && o->oPosY < (o->oHomeY - 300.0f)) {
+        o->oPosX = o->oPosZ = 0;
+        o->oPosY = o->oHomeY + 2000.0f;
+        o->oVelY = 0;
+        o->oForwardVel = 0;
+    }
+}
+#endif
+
+void bowser_act_unused_slow_walk(void) // unused?
+{
+    if (cur_obj_init_animation_and_check_if_near_end(12))
+        o->oAction = 0;
+}
+
+void bowser_act_default(void) // only lasts one frame
+{
+    o->oBowserEyesShut = 0;
+    cur_obj_init_animation_with_sound(12);
+    // stop him still
+    o->oAngleVelYaw = 0;
+    o->oForwardVel = 0.0f;
+    o->oVelY = 0.0f;
+    if (BITDW)
+        bowser_bitdw_act_controller();
+    else if (BITFS)
+        bowser_bitfs_act_controller();
+    else
+        bowser_bits_act_controller();
+    // Action 14 commonly follows
+}
+
+void bowser_act_breath_fire(void) {
+    o->oForwardVel = 0.0f;
+    if (o->oTimer == 0)
+        cur_obj_play_sound_2(SOUND_OBJ_BOWSER_INHALING);
+    if (cur_obj_init_animation_and_check_if_near_end(6))
+        o->oAction = 0;
+}
+
+void bowser_act_walk_to_mario(void) // turn towards Mario
+{
+    UNUSED s32 facing; // is Bowser facing Mario?
+    s16 turnSpeed;
+    s16 angleFromMario = abs_angle_diff(o->oMoveAngleYaw, o->oAngleToMario);
+    if (BITFS)
+        turnSpeed = 0x400;
+    else if (o->oHealth > 2)
+        turnSpeed = 0x400;
+    else if (o->oHealth == 2)
+        turnSpeed = 0x300;
+    else
+        turnSpeed = 0x200;
+    facing = cur_obj_rotate_yaw_toward(o->oAngleToMario, turnSpeed);
+    if (o->oSubAction == 0) {
+        o->oBowserUnkF8 = 0;
+        if (bowser_set_anim_look_up_and_walk())
+            o->oSubAction++;
+    } else if (o->oSubAction == 1) {
+        if (bowser_set_anim_slow_gait()) {
+            o->oBowserUnkF8++;
+            if (o->oBowserUnkF4 & 0x20000) {
+                if (o->oBowserUnkF8 > 4)
+                    o->oBowserUnkF4 &= ~0x20000;
+            } else if (angleFromMario < 0x2000)
+                o->oSubAction++;
+        }
+    } else if (bowser_set_anim_look_down())
+        o->oAction = 0;
+}
+
+void bowser_act_teleport(void) {
+    switch (o->oSubAction) {
+        case 0:
+            cur_obj_become_intangible();
+            o->oBowserUnk1AC = 0;
+            o->oBowserUnkF8 = 30;
+            if (o->oTimer == 0)
+                cur_obj_play_sound_2(SOUND_OBJ2_BOWSER_TELEPORT);
+            if (o->oOpacity == 0) {
+                o->oSubAction++;
+                o->oMoveAngleYaw = o->oAngleToMario;
+            }
+            break;
+        case 1:
+            if (o->oBowserUnkF8--)
+                o->oForwardVel = 100.0f;
+            else {
+                o->oSubAction = 2;
+                o->oMoveAngleYaw = o->oAngleToMario;
+            }
+            if (abs_angle_diff(o->oMoveAngleYaw, o->oAngleToMario) > 0x4000)
+                if (o->oDistanceToMario > 500.0f) {
+                    o->oSubAction = 2;
+                    o->oMoveAngleYaw = o->oAngleToMario; // large change in angle?
+                    cur_obj_play_sound_2(SOUND_OBJ2_BOWSER_TELEPORT);
+                }
+            break;
+        case 2:
+            o->oForwardVel = 0.0f;
+            o->oBowserUnk1AC = 0xFF;
+            if (o->oOpacity == 0xFF)
+                o->oAction = 0;
+            cur_obj_become_tangible();
+            break;
+    }
+}
+
+void bowser_act_spit_fire_into_sky(void) // only in sky
+{
+    s32 frame;
+    cur_obj_init_animation_with_sound(11);
+    frame = o->header.gfx.animInfo.animFrame;
+    if (frame > 24 && frame < 36) {
+        cur_obj_play_sound_1(SOUND_AIR_BOWSER_SPIT_FIRE);
+        if (frame == 35)
+            spawn_object_relative(1, 0, 0x190, 0x64, o, MODEL_RED_FLAME, bhvBlueBowserFlame);
+        else
+            spawn_object_relative(0, 0, 0x190, 0x64, o, MODEL_RED_FLAME, bhvBlueBowserFlame);
+    }
+    if (cur_obj_check_if_near_animation_end())
+        o->oAction = 0;
+    o->oBowserUnkF4 |= 0x20000;
+}
+
+void bowser_act_hit_mine(void) {
+    if (o->oTimer == 0) {
+        o->oForwardVel = -400.0f;
+        o->oVelY = 100.0f;
+        o->oMoveAngleYaw = o->oBowserAngleToCentre + 0x8000;
+        o->oBowserEyesShut = 1;
+    }
+    if (o->oSubAction == 0) {
+        cur_obj_init_animation_with_sound(25);
+        o->oSubAction++;
+        o->oBowserUnkF8 = 0;
+    } else if (o->oSubAction == 1) {
+        cur_obj_init_animation_with_sound(25);
+        cur_obj_extend_animation_if_at_end();
+        bowser_bounce(&o->oBowserUnkF8);
+        if ((o->oBowserUnkF8 > 2)) {
+            cur_obj_init_animation_with_sound(26);
+            o->oVelY = 0.0f;
+            o->oForwardVel = 0.0f;
+            o->oSubAction++;
+        }
+    } else if (o->oSubAction == 2) {
+        if (cur_obj_check_if_near_animation_end()) {
+            if (o->oHealth == 1)
+                o->oAction = 3;
+            else
+                o->oAction = 0;
+            o->oBowserEyesShut = 0;
+        }
+    } else {
+    }
+}
+
+s32 bowser_set_anim_in_air(void) {
+    cur_obj_init_animation_with_sound(9);
+    if (cur_obj_check_anim_frame(11))
+        return 1;
+    else
+        return 0;
+}
+
+s32 bowser_land(void) {
+    if (o->oMoveFlags & OBJ_MOVE_LANDED) {
+        o->oForwardVel = 0;
+        o->oVelY = 0;
+        spawn_mist_particles_variable(0, 0, 60.0f);
+        cur_obj_init_animation_with_sound(8);
+        o->header.gfx.animInfo.animFrame = 0;
+        cur_obj_start_cam_event(o, CAM_EVENT_BOWSER_JUMP);
+        if (BITDW) {
+            if (o->oDistanceToMario < 850.0f)
+                gMarioObject->oInteractStatus |= INT_STATUS_MARIO_UNK1;
+            else
+                gMarioObject->oInteractStatus |= INT_STATUS_HOOT_GRABBED_BY_MARIO; // hmm...
+        }
+        return 1;
+    } else
+        return 0;
+}
+
+void bowser_short_second_hop(void) {
+    if (BITS && o->oBowserUnkF4 & 0x10000)
+        if (o->oBowserDistToCentre > 1000.0f)
+            o->oForwardVel = 60.0f;
+}
+
+void bowser_act_jump(void) {
+    UNUSED s32 unused;
+    if (o->oSubAction == 0) {
+        if (bowser_set_anim_in_air()) {
+            if (BITS && o->oBowserUnkF4 & 0x10000)
+                o->oVelY = 70.0f;
+            else
+                o->oVelY = 80.0f;
+            o->oBowserUnkF8 = 0;
+            bowser_short_second_hop();
+            o->oSubAction++;
+        }
+    } else if (o->oSubAction == 1) {
+#ifndef VERSION_JP
+        if (o->oBehParams2ndByte == 2 && o->oBowserUnkF4 & 0x10000)
+            bowser_reset_fallen_off_stage();
+#endif
+        if (bowser_land()) {
+            o->oBowserUnkF4 &= ~0x10000;
+            o->oForwardVel = 0.0f;
+            o->oSubAction++;
+            bowser_spawn_shockwave();
+            if (BITFS)
+                o->oAction = 19;
+        } else {
+        }
+    } else if (cur_obj_check_if_near_animation_end())
+        o->oAction = 0;
+}
+
+void bowser_act_jump_towards_mario(void) {
+    f32 sp1C = D_8032F50C[0];
+    f32 sp18 = D_8032F510[0];
+    if (o->oSubAction == 0) {
+        if (bowser_set_anim_in_air()) {
+            o->oVelY = sp1C;
+            o->oForwardVel = sp18;
+            o->oBowserUnkF8 = 0;
+            o->oSubAction++;
+        }
+    } else if (o->oSubAction == 1) {
+        if (bowser_land())
+            o->oSubAction++;
+    } else if (cur_obj_check_if_near_animation_end())
+        o->oAction = 0;
+}
+
+void bowser_act_hit_edge(void) {
+    o->oForwardVel = 0.0f;
+    if (o->oTimer == 0)
+        o->oBowserUnkF8 = 0;
+    switch (o->oSubAction) {
+        case 0:
+            cur_obj_init_animation_with_sound(23);
+            if (cur_obj_check_if_near_animation_end())
+                o->oBowserUnkF8++;
+            if (o->oBowserUnkF8 > 0)
+                o->oSubAction++;
+            break;
+        case 1:
+            cur_obj_init_animation_with_sound(24);
+            if (cur_obj_check_if_near_animation_end())
+                o->oAction = 11;
+            break;
+    }
+}
+
+void bowser_act_spit_fire_onto_floor(void) {
+    if (gHudDisplay.wedges < 4)
+        o->oBowserUnk108 = 3;
+    else
+        o->oBowserUnk108 = random_float() * 3.0f + 1.0f;
+    cur_obj_init_animation_with_sound(22);
+    if (cur_obj_check_anim_frame(5))
+        obj_spit_fire(0, 200, 180, 7.0f, MODEL_RED_FLAME, 30.0f, 10.0f, 0x1000);
+    if (cur_obj_check_if_near_animation_end())
+        o->oSubAction++;
+    if (o->oSubAction >= o->oBowserUnk108)
+        o->oAction = 0;
+}
+
+s32 bowser_turn_on_timer(s32 a0, s16 a1) {
+    if (o->oSubAction == 0) {
+        if (cur_obj_init_animation_and_check_if_near_end(15))
+            o->oSubAction++;
+    } else if (o->oSubAction == 1) {
+        if (cur_obj_init_animation_and_check_if_near_end(14))
+            o->oSubAction++;
+    } else
+        cur_obj_init_animation_with_sound(12);
+    o->oForwardVel = 0.0f;
+    o->oMoveAngleYaw += a1;
+    if (o->oTimer >= a0)
+        return 1;
+    else
+        return 0;
+}
+
+void bowser_act_turn_from_edge(void) {
+    if (bowser_turn_on_timer(63, 0x200))
+        o->oAction = 0;
+}
+
+void bowser_act_charge_mario(void) {
+    s32 sp34;
+    if (o->oTimer == 0)
+        o->oForwardVel = 0.0f;
+    switch (o->oSubAction) {
+        case 0:
+            o->oBowserUnkF8 = 0;
+            if (cur_obj_init_animation_and_check_if_near_end(18))
+                o->oSubAction = 1;
+            break;
+        case 1:
+            o->oForwardVel = 50.0f;
+            if (cur_obj_init_animation_and_check_if_near_end(0x13) != 0) {
+                o->oBowserUnkF8++;
+                if (o->oBowserUnkF8 >= 6)
+                    o->oSubAction = 3;
+                if (o->oBowserUnkF8 >= 2)
+                    if (abs_angle_diff(o->oAngleToMario, o->oMoveAngleYaw) > 0x2000)
+                        o->oSubAction = 3;
+            }
+            cur_obj_rotate_yaw_toward(o->oAngleToMario, 0x200);
+            break;
+        case 3:
+            o->oBowserUnkF8 = 0;
+            cur_obj_init_animation_with_sound(21);
+            spawn_object_relative_with_scale(0, 100, -50, 0, 3.0f, o, MODEL_SMOKE, bhvWhitePuffSmoke2);
+            spawn_object_relative_with_scale(0, -100, -50, 0, 3.0f, o, MODEL_SMOKE,
+                                             bhvWhitePuffSmoke2);
+            if (approach_f32_signed(&o->oForwardVel, 0, -1.0f))
+                o->oSubAction = 2;
+            cur_obj_extend_animation_if_at_end();
+            break;
+        case 2:
+            o->oForwardVel = 0.0f;
+            cur_obj_init_animation_with_sound(20);
+            if (cur_obj_check_if_near_animation_end()) {
+                if (BITS)
+                    sp34 = 10;
+                else
+                    sp34 = 30;
+                if (o->oBowserUnkF8 > sp34)
+                    o->oAction = 0;
+                o->oBowserUnkF8++;
+            }
+            cur_obj_extend_animation_if_at_end();
+            break;
+    }
+    if (o->oMoveFlags & OBJ_MOVE_HIT_EDGE)
+        o->oAction = 10;
+}
+
+s32 bowser_check_hit_mine(void) {
+    struct Object *mine;
+    f32 sp18;
+    mine = cur_obj_find_nearest_object_with_behavior(bhvBowserBomb, &sp18);
+    if (mine != NULL && sp18 < 800.0f) {
+        mine->oInteractStatus |= INT_STATUS_HIT_MINE;
+        return 1;
+    }
+    return 0;
+}
+
+void bowser_act_thrown_dropped(void)
+{
+    UNUSED s32 unused;
+    if (o->oTimer < 2)
+        o->oBowserUnkF8 = 0;
+    if (o->oSubAction == 0) {
+        cur_obj_init_animation_with_sound(2);
+        bowser_bounce(&o->oBowserUnkF8);
+        if (o->oMoveFlags & OBJ_MOVE_ON_GROUND) {
+            o->oForwardVel = 0.0f;
+            o->oSubAction++;
+        }
+    } else if (cur_obj_init_animation_and_check_if_near_end(0))
+        o->oAction = 0;
+    if (bowser_check_hit_mine()) {
+        o->oHealth--;
+        if (o->oHealth <= 0)
+            o->oAction = 4;
+        else
+            o->oAction = 12;
+    }
+}
+
+void bowser_set_goal_invisible(void) {
+    o->oBowserUnk1AC = 0;
+    if (o->oOpacity == 0) {
+        o->oForwardVel = 0.0f;
+        o->oVelY = 0.0f;
+        o->oPosY = o->oHomeY - 1000.0f;
+    }
+}
+
+void bowser_act_jump_onto_stage(void) {
+    s32 sp2C;
+    UNUSED s32 unused;
+    struct Surface *sp24 = o->oFloor;
+    if (sp24 != NULL && sp24->flags & 1)
+        sp2C = 1;
+    else
+        sp2C = 0;
+    o->oBowserUnkF4 |= 0x10000;
+    switch (o->oSubAction) {
+        case 0:
+            if (o->oTimer == 0) {
+                o->oFaceAnglePitch = 0;
+                o->oFaceAngleRoll = 0;
+            } //? missing else
+            o->oFaceAnglePitch += 0x800;
+            o->oFaceAngleRoll += 0x800;
+            if (!(o->oFaceAnglePitch & 0xFFFF))
+                o->oSubAction++;
+            bowser_set_goal_invisible();
+            break;
+        case 1:
+            cur_obj_init_animation_with_sound(9);
+            if (cur_obj_check_anim_frame(11)) {
+                o->oMoveAngleYaw = o->oBowserAngleToCentre;
+                o->oVelY = 150.0f;
+                o->oBowserUnk1AC = 0xFF;
+                o->oBowserUnkF8 = 0;
+                o->oSubAction++;
+            } else
+                bowser_set_goal_invisible();
+            break;
+        case 2:
+            if (o->oPosY > o->oHomeY) {
+                o->oDragStrength = 0.0f;
+                if (o->oBowserDistToCentre < 2500.0f) {
+                    if (absf(o->oFloorHeight - o->oHomeY) < 100.0f)
+                        approach_f32_signed(&o->oForwardVel, 0, -5.0f);
+                    else
+                        cur_obj_forward_vel_approach_upward(150.0f, 2.0f);
+                } else
+                    cur_obj_forward_vel_approach_upward(150.0f, 2.0f);
+            }
+            if (bowser_land()) {
+                o->oDragStrength = 10.0f;
+                o->oSubAction++;
+                if (sp2C == 0)
+                    bowser_spawn_shockwave();
+                else if (BITS)
+                    o->oAction = 13;
+                if (BITFS)
+                    o->oAction = 19;
+            }
+#ifndef VERSION_JP
+            bowser_reset_fallen_off_stage();
+#else
+            if (o->oVelY < 0.0f && o->oPosY < o->oHomeY - 300.0f) {
+                o->oPosZ = 0.0f, o->oPosX = o->oPosZ;
+                o->oPosY = o->oHomeY + 2000.0f;
+                o->oVelY = 0.0f;
+            }
+#endif
+            break;
+        case 3:
+            if (cur_obj_check_if_near_animation_end()) {
+                o->oAction = 0;
+                o->oBowserUnkF4 &= ~0x10000;
+                cur_obj_extend_animation_if_at_end();
+            }
+            break;
+    }
+    print_debug_bottom_up("sp %d", o->oForwardVel);
+}
+
+void bowser_act_dance(void) {
+    if (is_item_in_array(o->oTimer, D_8032F514))
+        cur_obj_play_sound_2(SOUND_OBJ_BOWSER_WALK);
+    if (cur_obj_init_animation_and_check_if_near_end(10))
+        o->oAction = 0;
+}
+
+void bowser_spawn_grand_star_key(void) {
+    if (BITS)
+        gSecondCameraFocus = spawn_object(o, MODEL_STAR, bhvGrandStar);
+    else {
+        gSecondCameraFocus = spawn_object(o, MODEL_BOWSER_KEY, bhvBowserKey);
+        cur_obj_play_sound_2(SOUND_GENERAL2_BOWSER_KEY);
+    }
+    gSecondCameraFocus->oAngleVelYaw = o->oAngleVelYaw;
+}
+
+void bowser_fly_back_dead(void) {
+    cur_obj_init_animation_with_sound(16);
+    if (BITS)
+        o->oForwardVel = -400.0f;
+    else
+        o->oForwardVel = -200.0f;
+    o->oVelY = 100.0f;
+    o->oMoveAngleYaw = o->oBowserAngleToCentre + 0x8000;
+    o->oBowserUnkF8 = 0;
+    o->oSubAction++;
+}
+
+void bowser_dead_bounce(void) {
+    o->oBowserEyesShut = 1;
+    bowser_bounce(&o->oBowserUnkF8);
+    if (o->oMoveFlags & OBJ_MOVE_LANDED)
+        cur_obj_play_sound_2(SOUND_OBJ_BOWSER_WALK);
+    if (o->oMoveFlags & OBJ_MOVE_ON_GROUND) {
+        o->oForwardVel = 0.0f;
+        o->oSubAction++;
+    }
+}
+
+s32 bowser_dead_wait_for_mario(void) {
+    s32 ret = 0;
+    cur_obj_become_intangible();
+    if (cur_obj_init_animation_and_check_if_near_end(17) && o->oDistanceToMario < 700.0f
+        && abs_angle_diff(gMarioObject->oMoveAngleYaw, o->oAngleToMario) > 0x6000)
+        ret = 1;
+    cur_obj_extend_animation_if_at_end();
+    o->oBowserUnkF8 = 0;
+    return ret;
+}
+
+s32 bowser_dead_twirl_into_trophy(void) {
+    s32 ret = 0;
+    if (o->header.gfx.scale[0] < 0.8)
+        o->oAngleVelYaw += 0x80;
+    if (o->header.gfx.scale[0] > 0.2) {
+        o->header.gfx.scale[0] = o->header.gfx.scale[0] - 0.02;
+        o->header.gfx.scale[2] = o->header.gfx.scale[2] - 0.02;
+    } else {
+        o->header.gfx.scale[1] = o->header.gfx.scale[1] - 0.01;
+        o->oVelY = 20.0f;
+        o->oGravity = 0.0f;
+    }
+    if (o->header.gfx.scale[1] < 0.5)
+        ret = 1;
+    o->oMoveAngleYaw += o->oAngleVelYaw;
+    if (o->oOpacity >= 3)
+        o->oOpacity -= 2;
+    return ret;
+}
+
+void bowser_dead_hide(void) {
+    cur_obj_scale(0);
+    o->oForwardVel = 0;
+    o->oVelY = 0;
+    o->oGravity = 0;
+}
+
+s32 bowser_dead_not_bits_end(void) {
+    s32 ret = 0;
+    if (o->oBowserUnkF8 < 2) {
+        if (o->oBowserUnkF8 == 0) {
+            seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40);
+            o->oBowserUnkF8++;
+        }
+        if (cur_obj_update_dialog(2, 18, sBowserDefeatedDialogText[o->oBehParams2ndByte], 0)) {
+            o->oBowserUnkF8++;
+            cur_obj_play_sound_2(SOUND_GENERAL2_BOWSER_EXPLODE);
+            seq_player_unlower_volume(SEQ_PLAYER_LEVEL, 60);
+            seq_player_fade_out(SEQ_PLAYER_LEVEL, 1);
+        }
+    } else if (bowser_dead_twirl_into_trophy()) {
+        bowser_dead_hide();
+        spawn_triangle_break_particles(20, 116, 1.0f, 0);
+        bowser_spawn_grand_star_key();
+        set_mario_npc_dialog(0);
+        ret = 1;
+    }
+    return ret;
+}
+
+s32 bowser_dead_bits_end(void) {
+    UNUSED s32 unused;
+    s32 ret = 0;
+    s32 dialogID;
+    if (o->oBowserUnkF8 < 2) {
+        if (gHudDisplay.stars < 120)
+            dialogID = DIALOG_121;
+        else
+            dialogID = DIALOG_163;
+        if (o->oBowserUnkF8 == 0) {
+            seq_player_lower_volume(SEQ_PLAYER_LEVEL, 60, 40);
+            o->oBowserUnkF8++;
+        }
+        if (cur_obj_update_dialog(2, 18, dialogID, 0)) {
+            cur_obj_set_model(MODEL_BOWSER2);
+            seq_player_unlower_volume(SEQ_PLAYER_LEVEL, 60);
+            seq_player_fade_out(SEQ_PLAYER_LEVEL, 1);
+            bowser_spawn_grand_star_key();
+            o->oBowserUnkF8++;
+        }
+    } else if (o->oOpacity > 4)
+        o->oOpacity -= 4;
+    else {
+        bowser_dead_hide();
+        ret = 1;
+    }
+    return ret;
+}
+
+void bowser_act_dead(void) {
+    switch (o->oSubAction) {
+        case 0:
+            bowser_fly_back_dead();
+            break;
+        case 1:
+            bowser_dead_bounce();
+            break;
+        case 2:
+            if (bowser_dead_wait_for_mario()) {
+                o->oBowserUnkF8 = 0;
+                if (BITS)
+                    o->oSubAction = 10;
+                else {
+                    o->activeFlags |= ACTIVE_FLAG_DITHERED_ALPHA;
+                    o->oSubAction++;
+                }
+            }
+            break;
+        case 3:
+            if (bowser_dead_not_bits_end())
+                o->oSubAction++;
+            break;
+        case 4:
+            break;
+        case 10:
+            if (bowser_dead_bits_end())
+                o->oSubAction++;
+            break;
+        case 11:
+            break;
+    }
+}
+
+void bowser_tilt_platform(struct Object *platform, s16 a1) {
+    s16 angle;
+    angle = o->oBowserAngleToCentre + 0x8000;
+    platform->oAngleVelPitch = coss(angle) * a1;
+    platform->oAngleVelRoll = -sins(angle) * a1;
+}
+
+void bowser_act_ride_tilting_platform(void) {
+    struct Object *platform = cur_obj_nearest_object_with_behavior(bhvTiltingBowserLavaPlatform);
+    UNUSED s16 sp2A = o->oBowserAngleToCentre + 0x8000;
+    s16 sp28;
+    UNUSED s32 unused;
+    s32 i;
+    s32 sp1C;
+    if (platform == NULL)
+        o->oAction = 0;
+    else {
+        i = 0;
+        sp1C = 1;
+        while (D_8032F520[i][2] != 0) {
+            if (o->oTimer < D_8032F520[i][2]) {
+                sp28 = D_8032F520[i][1];
+                if (D_8032F520[i][0] > 0)
+                    sp28 = (D_8032F520[i][2] - o->oTimer - 1) * sp28;
+                else
+                    sp28 = (o->oTimer - D_8032F520[i - 1][2]) * sp28;
+                bowser_tilt_platform(platform, sp28);
+                if (sp28 != 0)
+                    play_sound(SOUND_ENV_UNKNOWN4, platform->header.gfx.cameraToObject);
+                sp1C = 0;
+                break;
+            }
+            i++;
+        }
+        if (sp1C) {
+            o->oAction = 0;
+            platform->oAngleVelPitch = 0;
+            platform->oAngleVelRoll = 0;
+            platform->oFaceAnglePitch = 0;
+            platform->oFaceAngleRoll = 0;
+        }
+    }
+    cur_obj_extend_animation_if_at_end();
+}
+
+s32 bowser_check_fallen_off_stage(void) // bowser off stage?
+{
+    if (o->oAction != 2 && o->oAction != 19) {
+        if (o->oPosY < o->oHomeY - 1000.0f)
+            return 1;
+        if (o->oMoveFlags & OBJ_MOVE_LANDED) {
+            if (o->oFloorType == SURFACE_BURNING)
+                return 1;
+            if (o->oFloorType == SURFACE_DEATH_PLANE)
+                return 1;
+        }
+    }
+    return 0;
+}
+
+void (*sBowserActions[])(void) = { bowser_act_default,  bowser_act_thrown_dropped,  bowser_act_jump_onto_stage,  bowser_act_dance,
+                                   bowser_act_dead,  bowser_act_text_wait,  bowser_act_intro_walk,  bowser_act_charge_mario,
+                                   bowser_act_spit_fire_into_sky,  bowser_act_spit_fire_onto_floor,  bowser_act_hit_edge, bowser_act_turn_from_edge,
+                                   bowser_act_hit_mine, bowser_act_jump, bowser_act_walk_to_mario, bowser_act_breath_fire,
+                                   bowser_act_teleport, bowser_act_jump_towards_mario, bowser_act_unused_slow_walk, bowser_act_ride_tilting_platform };
+struct SoundState D_8032F5B8[] = { { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 1, 0, -1, SOUND_OBJ_BOWSER_WALK },
+                                   { 1, 0, -1, SOUND_OBJ2_BOWSER_ROAR },
+                                   { 1, 0, -1, SOUND_OBJ2_BOWSER_ROAR },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 1, 20, 40, SOUND_OBJ_BOWSER_WALK },
+                                   { 1, 20, -1, SOUND_OBJ_BOWSER_WALK },
+                                   { 1, 20, 40, SOUND_OBJ_BOWSER_WALK },
+                                   { 1, 0, -1, SOUND_OBJ_BOWSER_TAIL_PICKUP },
+                                   { 1, 0, -1, SOUND_OBJ_BOWSER_DEFEATED },
+                                   { 1, 8, -1, SOUND_OBJ_BOWSER_WALK },
+                                   { 1, 8, 17, SOUND_OBJ_BOWSER_WALK },
+                                   { 1, 8, -10, SOUND_OBJ_BOWSER_WALK },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 1, 5, -1, SOUND_OBJ_FLAME_BLOWN },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 0, 0, 0, NO_SOUND },
+                                   { 1, 0, -1, SOUND_OBJ_BOWSER_TAIL_PICKUP },
+                                   { 1, 0, -1, SOUND_OBJ2_BOWSER_ROAR } };
+s8 D_8032F690[4] = { 0, 0, 1, 0 };
+s8 D_8032F694[4] = { 1, 1, 3, 0 };
+extern u8 bowser_3_seg7_collision_07004B94[];
+extern u8 bowser_3_seg7_collision_07004C18[];
+extern u8 bowser_3_seg7_collision_07004C9C[];
+extern u8 bowser_3_seg7_collision_07004D20[];
+extern u8 bowser_3_seg7_collision_07004DA4[];
+extern u8 bowser_3_seg7_collision_07004E28[];
+extern u8 bowser_3_seg7_collision_07004EAC[];
+extern u8 bowser_3_seg7_collision_07004F30[];
+extern u8 bowser_3_seg7_collision_07004FB4[];
+extern u8 bowser_3_seg7_collision_07005038[];
+struct Struct8032F698 D_8032F698[] = { { NULL, 0, 0, 0, 0 },
+                                       { bowser_3_seg7_collision_07004B94, -800, -1000, -20992, 0 },
+                                       { bowser_3_seg7_collision_07004C18, -1158, 390, -18432, 0 },
+                                       { bowser_3_seg7_collision_07004C9C, -1158, 390, -7680, 0 },
+                                       { bowser_3_seg7_collision_07004D20, 0, 1240, -6144, 0 },
+                                       { bowser_3_seg7_collision_07004DA4, 0, 1240, 6144, 0 },
+                                       { bowser_3_seg7_collision_07004E28, 1158, 390, 7680, 0 },
+                                       { bowser_3_seg7_collision_07004EAC, 1158, 390, 18432, 0 },
+                                       { bowser_3_seg7_collision_07004F30, 800, -1000, 20992, 0 },
+                                       { bowser_3_seg7_collision_07004FB4, 800, -1000, -31744, 0 },
+                                       { bowser_3_seg7_collision_07005038, -800, -1000, 31744, 0 } };
+
+void bowser_free_update(void) {
+    struct Surface *floor;
+    struct Object *platform;
+    UNUSED f32 floorHeight;
+    if ((platform = o->platform) != NULL)
+        apply_platform_displacement(FALSE, platform);
+    o->oBowserUnk10E = 0;
+    cur_obj_update_floor_and_walls();
+    cur_obj_call_action_function(sBowserActions);
+    cur_obj_move_standard(-78);
+    if (bowser_check_fallen_off_stage())
+        o->oAction = 2; // bowser go home?
+    floorHeight = find_floor(o->oPosX, o->oPosY, o->oPosZ, &floor);
+    if ((floor != NULL) && (floor->object != 0))
+        o->platform = floor->object;
+    else
+        o->platform = NULL;
+    exec_anim_sound_state(D_8032F5B8);
+}
+
+void bowser_held_update(void) {
+    o->oBowserUnkF4 &= ~0x20000;
+    cur_obj_become_intangible();
+    switch (o->oBowserUnk10E) {
+        case 0:
+            cur_obj_play_sound_2(SOUND_OBJ_BOWSER_TAIL_PICKUP);
+            cur_obj_unrender_and_reset_state(3, 1);
+            o->oBowserUnk10E++;
+            break;
+        case 1:
+            if (cur_obj_check_if_near_animation_end()) {
+                cur_obj_init_animation_with_sound(2);
+                o->oBowserUnk10E++;
+            }
+            break;
+        case 2:
+            break;
+    }
+    o->oMoveFlags = 0;
+    o->oBowserHeldAnglePitch = gMarioObject->oMoveAnglePitch;
+    o->oBowserHeldAngleVelYaw = gMarioObject->oAngleVelYaw;
+    o->oMoveAngleYaw = gMarioObject->oMoveAngleYaw;
+}
+
+void bowser_thrown_dropped_update(void) {
+    f32 sp1C;
+    o->oBowserUnk10E = 0;
+    cur_obj_get_thrown_or_placed(1.0f, 1.0f, 1);
+    sp1C = o->oBowserHeldAngleVelYaw / 3000.0 * 70.0f;
+    if (sp1C < 0.0f)
+        sp1C = -sp1C;
+    if (sp1C > 90.0f)
+        sp1C *= 2.5; // > 90 => get bigger?
+    o->oForwardVel = coss(o->oBowserHeldAnglePitch) * sp1C;
+    o->oVelY = -sins(o->oBowserHeldAnglePitch) * sp1C;
+    cur_obj_become_intangible();
+    o->prevObj->oAction = 1; // not sure what prevObj is
+    o->prevObj->oTimer = 0;
+    o->prevObj->oSubAction = 0;
+    o->oTimer = 0;
+    o->oSubAction = 0;
+}
+
+void bhv_bowser_loop(void) {
+    s16 angleToMario;  // AngleToMario    from Bowser's perspective
+    s16 angleToCentre; // AngleToCentre from Bowser's perspective
+
+    o->oBowserDistToCentre = sqrtf(o->oPosX * o->oPosX + o->oPosZ * o->oPosZ);
+    o->oBowserAngleToCentre = atan2s(0.0f - o->oPosZ, 0.0f - o->oPosX);
+    angleToMario = abs_angle_diff(o->oMoveAngleYaw, o->oAngleToMario);
+    angleToCentre = abs_angle_diff(o->oMoveAngleYaw, o->oBowserAngleToCentre);
+    o->oBowserUnkF4 &= ~0xFF;
+    if (angleToMario < 0x2000)
+        o->oBowserUnkF4 |= 2;
+    if (angleToCentre < 0x3800)
+        o->oBowserUnkF4 |= 4;
+    if (o->oBowserDistToCentre < 1000.0f)
+        o->oBowserUnkF4 |= 0x10;
+    if (o->oDistanceToMario < 850.0f)
+        o->oBowserUnkF4 |= 8;
+    switch (o->oHeldState) {
+        case HELD_FREE:
+            bowser_free_update();
+            break;
+        case HELD_HELD:
+            bowser_held_update();
+            break;
+        case HELD_THROWN:
+            bowser_thrown_dropped_update();
+            break;
+        case HELD_DROPPED:
+            bowser_thrown_dropped_update();
+            break;
+    }
+    cur_obj_align_gfx_with_floor();
+    if (o->oAction != 4)
+        if (o->oBowserUnk1AC != o->oOpacity) {
+            if (o->oBowserUnk1AC > o->oOpacity) {
+                o->oOpacity += 20;
+                if (o->oOpacity >= 0x100)
+                    o->oOpacity = 0xFF;
+            } else {
+                o->oOpacity -= 20;
+                if (o->oOpacity < 0)
+                    o->oOpacity = 0;
+            }
+        }
+}
+
+void bhv_bowser_init(void) {
+    s32 level; // 0 is dw, 1 is fs, 2 is sky
+    o->oBowserUnk110 = 1;
+    o->oOpacity = 0xFF;
+    o->oBowserUnk1AC = 0xFF;
+    if (gCurrLevelNum == LEVEL_BOWSER_2)
+        level = 1;
+    else if (gCurrLevelNum == LEVEL_BOWSER_3)
+        level = 2;
+    else
+        level = 0;
+    o->oBehParams2ndByte = level;
+    o->oBowserUnk1B2 = D_8032F690[level];
+    o->oHealth = D_8032F694[level];
+    cur_obj_start_cam_event(o, CAM_EVENT_BOWSER_INIT);
+    o->oAction = 5;
+    o->oBowserUnk1AE = 0;
+    o->oBowserEyesShut = 0;
+}
+
+#undef BITDW
+#undef BITFS
+#undef BITS
+
+Gfx *geo_update_body_rot_from_parent(s32 run, UNUSED struct GraphNode *node, Mat4 mtx) {
+    Mat4 sp20;
+    struct Object *sp1C;
+
+    if (run == TRUE) {
+        sp1C = (struct Object *) gCurGraphNodeObject;
+        if (sp1C->prevObj != NULL) {
+            create_transformation_from_matrices(sp20, mtx, *gCurGraphNodeCamera->matrixPtr);
+            obj_update_pos_from_parent_transformation(sp20, sp1C->prevObj);
+            obj_set_gfx_pos_from_pos(sp1C->prevObj);
+        }
+    }
+    return NULL;
+}
+
+void bowser_open_eye_switch(struct Object *a0, struct GraphNodeSwitchCase *switchCase) {
+    s32 sp1C;
+    s16 sp1A;
+    sp1A = abs_angle_diff(a0->oMoveAngleYaw, a0->oAngleToMario);
+    sp1C = switchCase->selectedCase;
+    switch (sp1C) {
+        case 0:
+            if (sp1A > 0x2000) {
+                if (a0->oAngleVelYaw > 0)
+                    switchCase->selectedCase = 5;
+                if (a0->oAngleVelYaw < 0)
+                    switchCase->selectedCase = 3;
+            }
+            if (a0->oBowserUnk1AE > 50)
+                switchCase->selectedCase = 1;
+            break;
+        case 1:
+            if (a0->oBowserUnk1AE > 2)
+                switchCase->selectedCase = 2;
+            break;
+        case 2:
+            if (a0->oBowserUnk1AE > 2)
+                switchCase->selectedCase = 9;
+            break;
+        case 9:
+            if (a0->oBowserUnk1AE > 2)
+                switchCase->selectedCase = 0;
+            break;
+        case 5:
+            if (a0->oBowserUnk1AE > 2) {
+                switchCase->selectedCase = 6;
+                if (a0->oAngleVelYaw <= 0)
+                    switchCase->selectedCase = 0;
+            }
+            break;
+        case 6:
+            if (a0->oAngleVelYaw <= 0)
+                switchCase->selectedCase = 5;
+            break;
+        case 3:
+            if (a0->oBowserUnk1AE > 2) {
+                switchCase->selectedCase = 4;
+                if (a0->oAngleVelYaw >= 0)
+                    switchCase->selectedCase = 0;
+            }
+            break;
+        case 4:
+            if (a0->oAngleVelYaw >= 0)
+                switchCase->selectedCase = 3;
+            break;
+        default:
+            switchCase->selectedCase = 0;
+    }
+    if (switchCase->selectedCase != sp1C)
+        a0->oBowserUnk1AE = -1;
+}
+
+/** Geo switch for controlling the state of bowser's eye direction and open/closed
+ * state. Checks whether oBowserEyesShut is TRUE and closes eyes if so and processes
+ * direction otherwise.
+ */
+Gfx *geo_switch_bowser_eyes(s32 run, struct GraphNode *node, UNUSED Mat4 *mtx) {
+    UNUSED s16 sp36;
+    UNUSED s32 unused;
+    struct Object *obj = (struct Object *) gCurGraphNodeObject;
+    struct GraphNodeSwitchCase *switchCase = (struct GraphNodeSwitchCase *) node;
+    if (run == TRUE) {
+        if (gCurGraphNodeHeldObject != NULL)
+            obj = gCurGraphNodeHeldObject->objNode;
+        switch (sp36 = obj->oBowserEyesShut) {
+            case 0: // eyes open, handle eye looking direction
+                bowser_open_eye_switch(obj, switchCase);
+                break;
+            case 1: // eyes closed, blinking
+                switchCase->selectedCase = 2;
+                break;
+        }
+        obj->oBowserUnk1AE++;
+    }
+    return NULL;
+}
+
+Gfx *geo_bits_bowser_coloring(s32 run, struct GraphNode *node, UNUSED s32 a2) {
+    Gfx *sp2C = NULL;
+    Gfx *sp28;
+    struct Object *sp24;
+    struct GraphNodeGenerated *sp20;
+
+    if (run == 1) {
+        sp24 = (struct Object *) gCurGraphNodeObject;
+        sp20 = (struct GraphNodeGenerated *) node;
+        if (gCurGraphNodeHeldObject != 0)
+            sp24 = gCurGraphNodeHeldObject->objNode;
+        if (sp24->oOpacity == 0xFF)
+            sp20->fnNode.node.flags = (sp20->fnNode.node.flags & 0xFF) | GRAPH_NODE_TYPE_FUNCTIONAL;
+        else
+            sp20->fnNode.node.flags = (sp20->fnNode.node.flags & 0xFF) | (GRAPH_NODE_TYPE_FUNCTIONAL | GRAPH_NODE_TYPE_400);
+        sp28 = sp2C = alloc_display_list(2 * sizeof(Gfx));
+
+        if (sp24->oBowserUnk1B2 != 0) {
+            gSPClearGeometryMode(sp28++, G_LIGHTING);
+        }
+        gSPEndDisplayList(sp28);
+    }
+    return sp2C;
+}
+
+void falling_bowser_plat_act_0(void) {
+    o->oPlatformUnkF8 = cur_obj_nearest_object_with_behavior(bhvBowser);
+    obj_set_collision_data(o, D_8032F698[o->oBehParams2ndByte].unk0);
+    if (o->oPlatformUnkF8 != 0)
+        o->oAction = 1;
+}
+
+void falling_bowser_plat_act_1(void) {
+    UNUSED s32 unused;
+    struct Object *sp0 = o->oPlatformUnkF8;
+    if (sp0->platform == o)
+        if (sp0->oAction == 13 && sp0->oBowserUnkF4 & 0x10000)
+            o->oAction = 2;
+    if (sp0->oHealth == 1 && (sp0->oAction == 3 || sp0->oHeldState != HELD_FREE))
+        o->oSubAction = 1;
+    if (o->oSubAction == 0)
+        o->oPlatformUnkFC = 0;
+    else {
+        if ((gDebugInfo[4][6] + 20) * (o->oBehParams2ndByte - 1) < o->oPlatformUnkFC)
+            o->oAction = 2;
+        o->oPlatformUnkFC++;
+    }
+}
+
+void falling_bowser_plat_act_2(void) {
+    Vec3f sp24;
+    s16 sp22;
+    f32 sp1C;
+    UNUSED struct Object *sp18 = o->oPlatformUnkF8;
+    if (o->oTimer == 0 || o->oTimer == 22)
+        cur_obj_play_sound_2(SOUND_GENERAL_BOWSER_PLATFORM_2);
+    if (o->oTimer < 22) {
+        set_environmental_camera_shake(SHAKE_ENV_FALLING_BITS_PLAT);
+        o->oVelY = 8.0f;
+        o->oGravity = 0.0f;
+    } else
+        o->oGravity = -4.0f;
+    if ((o->oTimer & 1) == 0 && o->oTimer < 14) {
+        sp22 = D_8032F698[o->oBehParams2ndByte].unk3 + (gDebugInfo[4][1] << 8);
+        sp1C = -(o->oTimer / 2) * 290 + 1740;
+        vec3f_copy_2(sp24, &o->oPosX);
+        o->oPosX = D_8032F698[o->oBehParams2ndByte].unk1 + sins(sp22 + 5296) * sp1C;
+        o->oPosZ = D_8032F698[o->oBehParams2ndByte].unk2 + coss(sp22 + 5296) * sp1C;
+        o->oPosY = 307.0f;
+        spawn_mist_particles_variable(4, 0, 100.0f);
+        o->oPosX = D_8032F698[o->oBehParams2ndByte].unk1 + sins(sp22 - 5296) * sp1C;
+        o->oPosZ = D_8032F698[o->oBehParams2ndByte].unk2 + coss(sp22 - 5296) * sp1C;
+        spawn_mist_particles_variable(4, 0, 100);
+        vec3f_copy_2(&o->oPosX, sp24);
+    }
+    cur_obj_move_using_fvel_and_gravity();
+    if (o->oTimer > 300)
+        obj_mark_for_deletion(o);
+}
+
+void (*sFallingBowserPlatformActions[])(void) = { falling_bowser_plat_act_0,
+                                                  falling_bowser_plat_act_1,
+                                                  falling_bowser_plat_act_2 };
+
+struct ObjectHitbox sGrowingBowserFlameHitbox = {
+    /* interactType: */ INTERACT_FLAME,
+    /* downOffset: */ 20,
+    /* damageOrCoinValue: */ 1,
+    /* health: */ 0,
+    /* numLootCoins: */ 0,
+    /* radius: */ 10,
+    /* height: */ 40,
+    /* hurtboxRadius: */ 0,
+    /* hurtboxHeight: */ 0,
+};
+
+struct ObjectHitbox sBowserFlameHitbox = {
+    /* interactType: */ INTERACT_FLAME,
+    /* downOffset: */ 0,
+    /* damageOrCoinValue: */ 1,
+    /* health: */ 0,
+    /* numLootCoins: */ 0,
+    /* radius: */ 10,
+    /* height: */ 40,
+    /* hurtboxRadius: */ 0,
+    /* hurtboxHeight: */ 0,
+};
+
+f32 D_8032F748[] = { -8.0f, -6.0f, -3.0f };
+
+void bhv_falling_bowser_platform_loop(void) {
+    cur_obj_call_action_function(sFallingBowserPlatformActions);
+}
+
+void bowser_flame_despawn(void) {
+    obj_mark_for_deletion(o);
+    spawn_object_with_scale(o, MODEL_NONE, bhvBlackSmokeUpward, 1.0f);
+    if (random_float() < 0.1)
+        spawn_object(o, MODEL_YELLOW_COIN, bhvTemporaryYellowCoin);
+}
+
+s32 bowser_flame_should_despawn(s32 maxTime) {
+    if (maxTime < o->oTimer)
+        return 1;
+    if (o->oFloorType == SURFACE_BURNING)
+        return 1;
+    if (o->oFloorType == SURFACE_DEATH_PLANE)
+        return 1;
+    return 0;
+}
+
+void bhv_flame_bowser_init(void) {
+    o->oAnimState = (s32)(random_float() * 10.0f);
+    o->oMoveAngleYaw = random_u16();
+    if (random_float() < 0.2)
+        o->oVelY = 80.0f;
+    else
+        o->oVelY = 20.0f;
+    o->oForwardVel = 10.0f;
+    o->oGravity = -1.0f;
+    o->oFlameScale = random_float() + 1.0f;
+}
+
+void bhv_flame_large_burning_out_init(void) {
+    o->oAnimState = (s32)(random_float() * 10.0f);
+    o->oMoveAngleYaw = random_u16();
+    o->oVelY = 10.0f;
+    o->oForwardVel = 0.0f;
+    o->oFlameScale = 7.0f;
+}
+
+void bowser_flame_move(void) {
+    s32 sp4;
+    sp4 = ((o->oFlameSpeedTimerOffset + gGlobalTimer) & 0x3F) << 10;
+    o->oPosX += sins(o->oMoveAngleYaw) * sins(sp4) * 4.0f;
+    o->oPosZ += coss(o->oMoveAngleYaw) * sins(sp4) * 4.0f;
+}
+
+void bhv_flame_bowser_loop(void) {
+    cur_obj_update_floor_and_walls();
+    cur_obj_move_standard(78);
+    if (o->oVelY < -4.0f)
+        o->oVelY = -4.0f;
+    if (o->oAction == 0) {
+        cur_obj_become_intangible();
+        bowser_flame_move();
+        if (o->oMoveFlags & OBJ_MOVE_LANDED) {
+            o->oAction++;
+            if (cur_obj_has_behavior(bhvFlameLargeBurningOut))
+                o->oFlameScale = 8.0f;
+            else
+                o->oFlameScale = random_float() * 2 + 6.0f;
+            o->oForwardVel = 0;
+            o->oVelY = 0;
+            o->oGravity = 0;
+        }
+    } else {
+        cur_obj_become_tangible();
+        if (o->oTimer > o->oFlameScale * 10 + 5.0f) {
+            o->oFlameScale -= 0.15;
+            if (o->oFlameScale <= 0)
+                bowser_flame_despawn();
+        }
+    }
+    cur_obj_scale(o->oFlameScale);
+    o->oGraphYOffset = o->header.gfx.scale[1] * 14.0f;
+    obj_set_hitbox(o, &sBowserFlameHitbox);
+}
+
+void bhv_flame_moving_forward_growing_init(void) {
+    o->oForwardVel = 30.0f;
+    obj_translate_xz_random(o, 80.0f);
+    o->oAnimState = (s32)(random_float() * 10.0f);
+    o->oFlameScale = 3.0f;
+}
+
+void bhv_flame_moving_forward_growing_loop(void) {
+    UNUSED s32 unused;
+    UNUSED struct Object *sp18;
+    obj_set_hitbox(o, &sGrowingBowserFlameHitbox);
+    o->oFlameScale = o->oFlameScale + 0.5;
+    cur_obj_scale(o->oFlameScale);
+    if (o->oMoveAnglePitch > 0x800)
+        o->oMoveAnglePitch -= 0x200;
+    cur_obj_set_pos_via_transform();
+    cur_obj_update_floor_height();
+    if (o->oFlameScale > 30.0f)
+        obj_mark_for_deletion(o);
+    if (o->oPosY < o->oFloorHeight) {
+        o->oPosY = o->oFloorHeight;
+        sp18 = spawn_object(o, MODEL_RED_FLAME, bhvFlameBowser);
+        obj_mark_for_deletion(o);
+    }
+}
+
+void bhv_flame_floating_landing_init(void) {
+    o->oAnimState = (s32)(random_float() * 10.0f);
+    o->oMoveAngleYaw = random_u16();
+    if (o->oBehParams2ndByte != 0)
+        o->oForwardVel = random_float() * 5.0f;
+    else
+        o->oForwardVel = random_float() * 70.0f;
+    o->oVelY = random_float() * 20.0f;
+    o->oGravity = -1.0f;
+    o->oFlameSpeedTimerOffset = random_float() * 64.0f;
+}
+
+void bhv_flame_floating_landing_loop(void) {
+    UNUSED s32 unused;
+    cur_obj_update_floor_and_walls();
+    cur_obj_move_standard(0x4e);
+    bowser_flame_move();
+    if (bowser_flame_should_despawn(900))
+        obj_mark_for_deletion(o);
+    if (o->oVelY < D_8032F748[o->oBehParams2ndByte])
+        o->oVelY = D_8032F748[o->oBehParams2ndByte];
+    if (o->oMoveFlags & OBJ_MOVE_LANDED) {
+        if (o->oBehParams2ndByte == 0)
+            spawn_object(o, MODEL_RED_FLAME, bhvFlameLargeBurningOut);
+        else
+            spawn_object(o, MODEL_NONE, bhvBlueFlamesGroup); //? wonder if they meant MODEL_BLUE_FLAME?
+        obj_mark_for_deletion(o);
+    }
+    o->oGraphYOffset = o->header.gfx.scale[1] * 14.0f;
+}
+
+void bhv_blue_bowser_flame_init(void) {
+    obj_translate_xz_random(o, 80.0f);
+    o->oAnimState = (s32)(random_float() * 10.0f);
+    o->oVelY = 7.0f;
+    o->oForwardVel = 35.0f;
+    o->oFlameScale = 3.0f;
+    o->oFlameUnkFC = random_float() * 0.5;
+    o->oGravity = 1.0f;
+    o->oFlameSpeedTimerOffset = (s32)(random_float() * 64.0f);
+}
+
+void bhv_blue_bowser_flame_loop(void) {
+    s32 i;
+    obj_set_hitbox(o, &sGrowingBowserFlameHitbox);
+    if (o->oFlameScale < 16.0f)
+        o->oFlameScale = o->oFlameScale + 0.5;
+    cur_obj_scale(o->oFlameScale);
+    cur_obj_update_floor_and_walls();
+    cur_obj_move_standard(0x4e);
+    if (o->oTimer > 0x14) {
+        if (o->oBehParams2ndByte == 0)
+            for (i = 0; i < 3; i++)
+                spawn_object_relative_with_scale(0, 0, 0, 0, 5.0f, o, MODEL_RED_FLAME,
+                                                 bhvFlameFloatingLanding);
+        else {
+            spawn_object_relative_with_scale(1, 0, 0, 0, 8.0f, o, MODEL_BLUE_FLAME,
+                                             bhvFlameFloatingLanding);
+            spawn_object_relative_with_scale(2, 0, 0, 0, 8.0f, o, MODEL_BLUE_FLAME,
+                                             bhvFlameFloatingLanding);
+        }
+        obj_mark_for_deletion(o);
+    }
+}
+
+void bhv_flame_bouncing_init(void) {
+    o->oAnimState = (s32)(random_float() * 10.0f);
+    o->oVelY = 30.0f;
+    o->oForwardVel = 20.0f;
+    o->oFlameScale = o->header.gfx.scale[0];
+    o->oFlameSpeedTimerOffset = (s32)(random_float() * 64.0f);
+}
+
+void bhv_flame_bouncing_loop(void) {
+    struct Object *bowser;
+    if (o->oTimer == 0)
+        o->oFlameBowser = cur_obj_nearest_object_with_behavior(bhvBowser);
+    bowser = o->oFlameBowser;
+    o->oForwardVel = 15.0f;
+    o->oBounciness = -1.0f;
+    cur_obj_scale(o->oFlameScale);
+    obj_set_hitbox(o, &sGrowingBowserFlameHitbox);
+    cur_obj_update_floor_and_walls();
+    cur_obj_move_standard(78);
+    if (bowser_flame_should_despawn(300))
+        obj_mark_for_deletion(o);
+    if (bowser != NULL)
+        if (bowser->oHeldState == 0)
+            if (lateral_dist_between_objects(o, bowser) < 300.0f)
+                obj_mark_for_deletion(o);
+}
+
+void bhv_blue_flames_group_loop(void) {
+    struct Object *flame;
+    s32 i;
+    if (o->oTimer == 0) {
+        o->oMoveAngleYaw = obj_angle_to_object(o, gMarioObject);
+        o->oBlueFlameNextScale = 5.0f;
+    }
+    if (o->oTimer < 16) {
+        if ((o->oTimer & 1) == 0) {
+            for (i = 0; i < 3; i++) {
+                flame = spawn_object(o, MODEL_BLUE_FLAME, bhvFlameBouncing);
+                flame->oMoveAngleYaw += i * 0x5555;
+                flame->header.gfx.scale[0] = o->oBlueFlameNextScale;
+            }
+            o->oBlueFlameNextScale -= 0.5;
+        }
+    } else
+        obj_mark_for_deletion(o);
+}
diff --git a/src/game/behaviors/bowser.inc.c.rej b/src/game/behaviors/bowser.inc.c.rej
new file mode 100644
index 0000000..f47808d
--- /dev/null
+++ b/src/game/behaviors/bowser.inc.c.rej
@@ -0,0 +1,11 @@
+--- src/game/behaviors/bowser.inc.c
++++ src/game/behaviors/bowser.inc.c
+@@ -1035,7 +1037,7 @@ void bowser_free_update(void) {
+     struct Object *platform;
+     UNUSED f32 floorHeight;
+     if ((platform = o->platform) != NULL)
+-        apply_platform_displacement(0, platform);
++        apply_platform_displacement(&sBowserDisplacementInfo, &o->oPosX, &o->oFaceAngleYaw, platform);
+     o->oBowserUnk10E = 0;
+     cur_obj_update_floor_and_walls();
+     cur_obj_call_action_function(sBowserActions);

diff --git a/src/game/behaviors/tilting_inverted_pyramid.inc.c b/src/game/behaviors/tilting_inverted_pyramid.inc.c
index ebce64f..29136e9 100644
--- a/src/game/behaviors/tilting_inverted_pyramid.inc.c
+++ b/src/game/behaviors/tilting_inverted_pyramid.inc.c
@@ -131,7 +131,7 @@ void bhv_tilting_inverted_pyramid_loop(void) {
         mx += posAfterRotation[0] - posBeforeRotation[0];
         my += posAfterRotation[1] - posBeforeRotation[1];
         mz += posAfterRotation[2] - posBeforeRotation[2];
-        set_mario_pos(mx, my, mz);
+        //set_mario_pos(mx, my, mz);
     }
 
     o->header.gfx.throwMatrix = transform;
diff --git a/src/game/platform_displacement.c b/src/game/platform_displacement.c
index 9153bc4..1e8cd86 100644
--- a/src/game/platform_displacement.c
+++ b/src/game/platform_displacement.c
@@ -8,6 +8,7 @@
 #include "object_list_processor.h"
 #include "platform_displacement.h"
 #include "types.h"
+#include "sm64.h"
 
 u16 D_8032FEC0 = 0;
 
@@ -84,98 +85,144 @@ void set_mario_pos(f32 x, f32 y, f32 z) {
     gMarioStates[0].pos[2] = z;
 }
 
-/**
- * Apply one frame of platform rotation to Mario or an object using the given
- * platform. If isMario is false, use gCurrentObject.
- */
-void apply_platform_displacement(u32 isMario, struct Object *platform) {
-    f32 x;
-    f32 y;
-    f32 z;
-    f32 platformPosX;
-    f32 platformPosY;
-    f32 platformPosZ;
-    Vec3f currentObjectOffset;
-    Vec3f relativeOffset;
-    Vec3f newObjectOffset;
-    Vec3s rotation;
-    UNUSED s16 unused1;
-    UNUSED s16 unused2;
-    UNUSED s16 unused3;
-    f32 displaceMatrix[4][4];
-
-    rotation[0] = platform->oAngleVelPitch;
-    rotation[1] = platform->oAngleVelYaw;
-    rotation[2] = platform->oAngleVelRoll;
-
-    if (isMario) {
-        D_8032FEC0 = 0;
-        get_mario_pos(&x, &y, &z);
-    } else {
-        x = gCurrentObject->oPosX;
-        y = gCurrentObject->oPosY;
-        z = gCurrentObject->oPosZ;
-    }
-
-    x += platform->oVelX;
-    z += platform->oVelZ;
-
-    if (rotation[0] != 0 || rotation[1] != 0 || rotation[2] != 0) {
-        unused1 = rotation[0];
-        unused2 = rotation[2];
-        unused3 = platform->oFaceAngleYaw;
+static struct PlatformDisplacementInfo sMarioDisplacementInfo;
+static Vec3f sMarioAmountDisplaced;
 
-        if (isMario) {
-            gMarioStates[0].faceAngle[1] += rotation[1];
-        }
+extern s32 gGlobalTimer;
 
-        platformPosX = platform->oPosX;
-        platformPosY = platform->oPosY;
-        platformPosZ = platform->oPosZ;
-
-        currentObjectOffset[0] = x - platformPosX;
-        currentObjectOffset[1] = y - platformPosY;
-        currentObjectOffset[2] = z - platformPosZ;
-
-        rotation[0] = platform->oFaceAnglePitch - platform->oAngleVelPitch;
-        rotation[1] = platform->oFaceAngleYaw - platform->oAngleVelYaw;
-        rotation[2] = platform->oFaceAngleRoll - platform->oAngleVelRoll;
-
-        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
-        linear_mtxf_transpose_mul_vec3f(displaceMatrix, relativeOffset, currentObjectOffset);
+/**
+ * Upscale or downscale a vector by another vector.
+ */
+static void scale_vec3f(Vec3f dst, Vec3f src, Vec3f scale, u32 doInverted) {
+	if (doInverted) {
+		dst[0] = src[0] / scale[0];
+		dst[1] = src[1] / scale[1];
+		dst[2] = src[2] / scale[2];
+	} else {
+		dst[0] = src[0] * scale[0];
+		dst[1] = src[1] * scale[1];
+		dst[2] = src[2] * scale[2];
+	}
+}
 
-        rotation[0] = platform->oFaceAnglePitch;
-        rotation[1] = platform->oFaceAngleYaw;
-        rotation[2] = platform->oFaceAngleRoll;
+/**
+ * Apply one frame of platform displacement to Mario or an object using the given
+ * platform.
+ */
+void apply_platform_displacement(struct PlatformDisplacementInfo *displaceInfo, Vec3f pos, s16 *yaw, struct Object *platform) {
+	Vec3f platformPos;
+	Vec3f posDifference;
+	Vec3f yawVec;
+	Vec3f scaledPos;
+	// Determine how much Mario turned on his own since last frame
+	s16 yawDifference = *yaw - displaceInfo->prevYaw;
+
+	// Avoid a crash if the platform unloaded its collision while stood on
+	if (platform->header.gfx.throwMatrix == NULL) return;
+
+	vec3f_copy(platformPos, (*platform->header.gfx.throwMatrix)[3]);
+
+	// Determine how far Mario moved on his own since last frame
+	vec3f_copy(posDifference, pos);
+	vec3f_sub(posDifference, displaceInfo->prevPos);
+
+	if ((platform == displaceInfo->prevPlatform) && (gGlobalTimer == displaceInfo->prevTimer + 1)) {
+		// Transform from relative positions to world positions
+		scale_vec3f(scaledPos, displaceInfo->prevTransformedPos, platform->header.gfx.scale, FALSE);
+		linear_mtxf_mul_vec3f(*platform->header.gfx.throwMatrix, pos, scaledPos);
+
+		// Add on how much Mario moved in the previous frame
+		vec3f_add(pos, posDifference);
+
+		// Calculate new yaw
+		linear_mtxf_mul_vec3f(*platform->header.gfx.throwMatrix, yawVec, displaceInfo->prevTransformedYawVec);
+		*yaw = atan2s(yawVec[2], yawVec[0]) + yawDifference;
+	} else {
+		// First frame of standing on the platform, don't calculate a new position
+		vec3f_sub(pos, platformPos);
+	}
+
+	// Transform from world positions to relative positions for use next frame
+	linear_mtxf_transpose_mul_vec3f(*platform->header.gfx.throwMatrix, scaledPos, pos);
+	scale_vec3f(displaceInfo->prevTransformedPos, scaledPos, platform->header.gfx.scale, TRUE);
+	vec3f_add(pos, platformPos);
+
+	// If the object is Mario, set inertia
+	if (pos == gMarioState->pos) {
+		vec3f_copy(sMarioAmountDisplaced, pos);
+		vec3f_sub(sMarioAmountDisplaced, displaceInfo->prevPos);
+		vec3f_sub(sMarioAmountDisplaced, posDifference);
+
+		// Make sure inertia isn't set on the first frame otherwise the previous value isn't cleared
+		if ((platform != displaceInfo->prevPlatform) || (gGlobalTimer != displaceInfo->prevTimer + 1)) {
+			vec3f_set(sMarioAmountDisplaced, 0.f, 0.f, 0.f);
+		}
+	}
+
+	// Update info for next frame
+	// Update position
+	vec3f_copy(displaceInfo->prevPos, pos);
+
+	// Set yaw info
+	vec3f_set(yawVec, sins(*yaw), 0, coss(*yaw));
+	linear_mtxf_transpose_mul_vec3f(*platform->header.gfx.throwMatrix, displaceInfo->prevTransformedYawVec, yawVec);
+	displaceInfo->prevYaw = *yaw;
+
+	// Update platform and timer
+	displaceInfo->prevPlatform = platform;
+	displaceInfo->prevTimer = gGlobalTimer;
+}
 
-        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
-        linear_mtxf_mul_vec3f(displaceMatrix, newObjectOffset, relativeOffset);
+// Doesn't change in the code, set this to FALSE if you don't want inertia
+u8 gDoInertia = TRUE;
 
-        x = platformPosX + newObjectOffset[0];
-        y = platformPosY + newObjectOffset[1];
-        z = platformPosZ + newObjectOffset[2];
-    }
+static u8 sShouldApplyInertia = FALSE;
+static u8 sInertiaFirstFrame = FALSE;
 
-    if (isMario) {
-        set_mario_pos(x, y, z);
-    } else {
-        gCurrentObject->oPosX = x;
-        gCurrentObject->oPosY = y;
-        gCurrentObject->oPosZ = z;
-    }
+/**
+ * Apply inertia based on Mario's last platform.
+ */
+static void apply_mario_inertia(void) {
+	// On the first frame of leaving the ground, boost Mario's y velocity
+	if (sInertiaFirstFrame) {
+		gMarioState->vel[1] += sMarioAmountDisplaced[1];
+	}
+
+	// Apply sideways inertia
+	gMarioState->pos[0] += sMarioAmountDisplaced[0];
+	gMarioState->pos[2] += sMarioAmountDisplaced[2];
+
+	// Drag
+	sMarioAmountDisplaced[0] *= 0.97f;
+	sMarioAmountDisplaced[2] *= 0.97f;
+
+	// Stop applying inertia once Mario has landed, or when ground pounding
+	if (!(gMarioState->action & ACT_FLAG_AIR) || (gMarioState->action == ACT_GROUND_POUND)) {
+		sShouldApplyInertia = FALSE;
+	}
 }
 
 /**
- * If Mario's platform is not null, apply platform displacement.
+ * Apply platform displacement or inertia if required.
  */
 void apply_mario_platform_displacement(void) {
-    struct Object *platform = gMarioPlatform;
-
-    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL && platform != NULL) {
-        apply_platform_displacement(TRUE, platform);
+    struct Object *platform;
+
+    platform = gMarioPlatform;
+    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL) {
+		if (platform != NULL) {
+			apply_platform_displacement(&sMarioDisplacementInfo, gMarioState->pos, &gMarioState->faceAngle[1], platform);
+			sShouldApplyInertia = TRUE;
+			sInertiaFirstFrame = TRUE;
+		} else if (sShouldApplyInertia && gDoInertia) {
+			apply_mario_inertia();
+			sInertiaFirstFrame = FALSE;
+		}
     }
 }
 
+
+
 #ifndef VERSION_JP
 /**
  * Set Mario's platform to NULL.
diff --git a/src/game/platform_displacement.c.orig b/src/game/platform_displacement.c.orig
new file mode 100644
index 0000000..9153bc4
--- /dev/null
+++ b/src/game/platform_displacement.c.orig
@@ -0,0 +1,186 @@
+#include <PR/ultratypes.h>
+
+#include "engine/math_util.h"
+#include "engine/surface_collision.h"
+#include "level_update.h"
+#include "object_fields.h"
+#include "object_helpers.h"
+#include "object_list_processor.h"
+#include "platform_displacement.h"
+#include "types.h"
+
+u16 D_8032FEC0 = 0;
+
+u32 unused_8032FEC4[4] = { 0 };
+
+struct Object *gMarioPlatform = NULL;
+
+/**
+ * Determine if Mario is standing on a platform object, meaning that he is
+ * within 4 units of the floor. Set his referenced platform object accordingly.
+ */
+void update_mario_platform(void) {
+    struct Surface *floor;
+    UNUSED u32 unused;
+    f32 marioX;
+    f32 marioY;
+    f32 marioZ;
+    f32 floorHeight;
+    u32 awayFromFloor;
+
+    if (gMarioObject == NULL) {
+        return;
+    }
+
+    //! If Mario moves onto a rotating platform in a PU, the find_floor call
+    //  will detect the platform and he will end up receiving a large amount
+    //  of displacement since he is considered to be far from the platform's
+    //  axis of rotation.
+
+    marioX = gMarioObject->oPosX;
+    marioY = gMarioObject->oPosY;
+    marioZ = gMarioObject->oPosZ;
+    floorHeight = find_floor(marioX, marioY, marioZ, &floor);
+
+    if (absf(marioY - floorHeight) < 4.0f) {
+        awayFromFloor = 0;
+    } else {
+        awayFromFloor = 1;
+    }
+
+    switch (awayFromFloor) {
+        case 1:
+            gMarioPlatform = NULL;
+            gMarioObject->platform = NULL;
+            break;
+
+        case 0:
+            if (floor != NULL && floor->object != NULL) {
+                gMarioPlatform = floor->object;
+                gMarioObject->platform = floor->object;
+            } else {
+                gMarioPlatform = NULL;
+                gMarioObject->platform = NULL;
+            }
+            break;
+    }
+}
+
+/**
+ * Get Mario's position and store it in x, y, and z.
+ */
+void get_mario_pos(f32 *x, f32 *y, f32 *z) {
+    *x = gMarioStates[0].pos[0];
+    *y = gMarioStates[0].pos[1];
+    *z = gMarioStates[0].pos[2];
+}
+
+/**
+ * Set Mario's position.
+ */
+void set_mario_pos(f32 x, f32 y, f32 z) {
+    gMarioStates[0].pos[0] = x;
+    gMarioStates[0].pos[1] = y;
+    gMarioStates[0].pos[2] = z;
+}
+
+/**
+ * Apply one frame of platform rotation to Mario or an object using the given
+ * platform. If isMario is false, use gCurrentObject.
+ */
+void apply_platform_displacement(u32 isMario, struct Object *platform) {
+    f32 x;
+    f32 y;
+    f32 z;
+    f32 platformPosX;
+    f32 platformPosY;
+    f32 platformPosZ;
+    Vec3f currentObjectOffset;
+    Vec3f relativeOffset;
+    Vec3f newObjectOffset;
+    Vec3s rotation;
+    UNUSED s16 unused1;
+    UNUSED s16 unused2;
+    UNUSED s16 unused3;
+    f32 displaceMatrix[4][4];
+
+    rotation[0] = platform->oAngleVelPitch;
+    rotation[1] = platform->oAngleVelYaw;
+    rotation[2] = platform->oAngleVelRoll;
+
+    if (isMario) {
+        D_8032FEC0 = 0;
+        get_mario_pos(&x, &y, &z);
+    } else {
+        x = gCurrentObject->oPosX;
+        y = gCurrentObject->oPosY;
+        z = gCurrentObject->oPosZ;
+    }
+
+    x += platform->oVelX;
+    z += platform->oVelZ;
+
+    if (rotation[0] != 0 || rotation[1] != 0 || rotation[2] != 0) {
+        unused1 = rotation[0];
+        unused2 = rotation[2];
+        unused3 = platform->oFaceAngleYaw;
+
+        if (isMario) {
+            gMarioStates[0].faceAngle[1] += rotation[1];
+        }
+
+        platformPosX = platform->oPosX;
+        platformPosY = platform->oPosY;
+        platformPosZ = platform->oPosZ;
+
+        currentObjectOffset[0] = x - platformPosX;
+        currentObjectOffset[1] = y - platformPosY;
+        currentObjectOffset[2] = z - platformPosZ;
+
+        rotation[0] = platform->oFaceAnglePitch - platform->oAngleVelPitch;
+        rotation[1] = platform->oFaceAngleYaw - platform->oAngleVelYaw;
+        rotation[2] = platform->oFaceAngleRoll - platform->oAngleVelRoll;
+
+        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
+        linear_mtxf_transpose_mul_vec3f(displaceMatrix, relativeOffset, currentObjectOffset);
+
+        rotation[0] = platform->oFaceAnglePitch;
+        rotation[1] = platform->oFaceAngleYaw;
+        rotation[2] = platform->oFaceAngleRoll;
+
+        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
+        linear_mtxf_mul_vec3f(displaceMatrix, newObjectOffset, relativeOffset);
+
+        x = platformPosX + newObjectOffset[0];
+        y = platformPosY + newObjectOffset[1];
+        z = platformPosZ + newObjectOffset[2];
+    }
+
+    if (isMario) {
+        set_mario_pos(x, y, z);
+    } else {
+        gCurrentObject->oPosX = x;
+        gCurrentObject->oPosY = y;
+        gCurrentObject->oPosZ = z;
+    }
+}
+
+/**
+ * If Mario's platform is not null, apply platform displacement.
+ */
+void apply_mario_platform_displacement(void) {
+    struct Object *platform = gMarioPlatform;
+
+    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL && platform != NULL) {
+        apply_platform_displacement(TRUE, platform);
+    }
+}
+
+#ifndef VERSION_JP
+/**
+ * Set Mario's platform to NULL.
+ */
+void clear_mario_platform(void) {
+    gMarioPlatform = NULL;
+}
+#endif
diff --git a/src/game/platform_displacement.c.rej b/src/game/platform_displacement.c.rej
new file mode 100644
index 0000000..ff20eca
--- /dev/null
+++ b/src/game/platform_displacement.c.rej
@@ -0,0 +1,218 @@
+--- src/game/platform_displacement.c
++++ src/game/platform_displacement.c
+@@ -85,96 +86,139 @@ void set_mario_pos(f32 x, f32 y, f32 z) {
+     gMarioStates[0].pos[2] = z;
+ }
+ 
+-/**
+- * Apply one frame of platform rotation to Mario or an object using the given
+- * platform. If isMario is 0, use gCurrentObject.
+- */
+-void apply_platform_displacement(u32 isMario, struct Object *platform) {
+-    f32 x;
+-    f32 y;
+-    f32 z;
+-    f32 platformPosX;
+-    f32 platformPosY;
+-    f32 platformPosZ;
+-    Vec3f currentObjectOffset;
+-    Vec3f relativeOffset;
+-    Vec3f newObjectOffset;
+-    Vec3s rotation;
+-    UNUSED s16 unused1;
+-    UNUSED s16 unused2;
+-    UNUSED s16 unused3;
+-    f32 displaceMatrix[4][4];
+-
+-    rotation[0] = platform->oAngleVelPitch;
+-    rotation[1] = platform->oAngleVelYaw;
+-    rotation[2] = platform->oAngleVelRoll;
+-
+-    if (isMario) {
+-        D_8032FEC0 = 0;
+-        get_mario_pos(&x, &y, &z);
+-    } else {
+-        x = gCurrentObject->oPosX;
+-        y = gCurrentObject->oPosY;
+-        z = gCurrentObject->oPosZ;
+-    }
+-
+-    x += platform->oVelX;
+-    z += platform->oVelZ;
++static struct PlatformDisplacementInfo sMarioDisplacementInfo;
++static Vec3f sMarioAmountDisplaced;
+ 
+-    if (rotation[0] != 0 || rotation[1] != 0 || rotation[2] != 0) {
+-        unused1 = rotation[0];
+-        unused2 = rotation[2];
+-        unused3 = platform->oFaceAngleYaw;
++extern s32 gGlobalTimer;
+ 
+-        if (isMario) {
+-            gMarioStates[0].faceAngle[1] += rotation[1];
+-        }
+-
+-        platformPosX = platform->oPosX;
+-        platformPosY = platform->oPosY;
+-        platformPosZ = platform->oPosZ;
+-
+-        currentObjectOffset[0] = x - platformPosX;
+-        currentObjectOffset[1] = y - platformPosY;
+-        currentObjectOffset[2] = z - platformPosZ;
+-
+-        rotation[0] = platform->oFaceAnglePitch - platform->oAngleVelPitch;
+-        rotation[1] = platform->oFaceAngleYaw - platform->oAngleVelYaw;
+-        rotation[2] = platform->oFaceAngleRoll - platform->oAngleVelRoll;
+-
+-        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
+-        linear_mtxf_transpose_mul_vec3f(displaceMatrix, relativeOffset, currentObjectOffset);
++/**
++ * Upscale or downscale a vector by another vector.
++ */
++static void scale_vec3f(Vec3f dst, Vec3f src, Vec3f scale, u32 doInverted) {
++	if (doInverted) {
++		dst[0] = src[0] / scale[0];
++		dst[1] = src[1] / scale[1];
++		dst[2] = src[2] / scale[2];
++	} else {
++		dst[0] = src[0] * scale[0];
++		dst[1] = src[1] * scale[1];
++		dst[2] = src[2] * scale[2];
++	}
++}
+ 
+-        rotation[0] = platform->oFaceAnglePitch;
+-        rotation[1] = platform->oFaceAngleYaw;
+-        rotation[2] = platform->oFaceAngleRoll;
++/**
++ * Apply one frame of platform displacement to Mario or an object using the given
++ * platform.
++ */
++void apply_platform_displacement(struct PlatformDisplacementInfo *displaceInfo, Vec3f pos, s16 *yaw, struct Object *platform) {
++	Vec3f platformPos;
++	Vec3f posDifference;
++	Vec3f yawVec;
++	Vec3f scaledPos;
++	// Determine how much Mario turned on his own since last frame
++	s16 yawDifference = *yaw - displaceInfo->prevYaw;
++
++	// Avoid a crash if the platform unloaded its collision while stood on
++	if (platform->header.gfx.throwMatrix == NULL) return;
++
++	vec3f_copy(platformPos, (*platform->header.gfx.throwMatrix)[3]);
++
++	// Determine how far Mario moved on his own since last frame
++	vec3f_copy(posDifference, pos);
++	vec3f_sub(posDifference, displaceInfo->prevPos);
++
++	if ((platform == displaceInfo->prevPlatform) && (gGlobalTimer == displaceInfo->prevTimer + 1)) {
++		// Transform from relative positions to world positions
++		scale_vec3f(scaledPos, displaceInfo->prevTransformedPos, platform->header.gfx.scale, FALSE);
++		linear_mtxf_mul_vec3f(*platform->header.gfx.throwMatrix, pos, scaledPos);
++
++		// Add on how much Mario moved in the previous frame
++		vec3f_add(pos, posDifference);
++
++		// Calculate new yaw
++		linear_mtxf_mul_vec3f(*platform->header.gfx.throwMatrix, yawVec, displaceInfo->prevTransformedYawVec);
++		*yaw = atan2s(yawVec[2], yawVec[0]) + yawDifference;
++	} else {
++		// First frame of standing on the platform, don't calculate a new position
++		vec3f_sub(pos, platformPos);
++	}
++
++	// Transform from world positions to relative positions for use next frame
++	linear_mtxf_transpose_mul_vec3f(*platform->header.gfx.throwMatrix, scaledPos, pos);
++	scale_vec3f(displaceInfo->prevTransformedPos, scaledPos, platform->header.gfx.scale, TRUE);
++	vec3f_add(pos, platformPos);
++
++	// If the object is Mario, set inertia
++	if (pos == gMarioState->pos) {
++		vec3f_copy(sMarioAmountDisplaced, pos);
++		vec3f_sub(sMarioAmountDisplaced, displaceInfo->prevPos);
++		vec3f_sub(sMarioAmountDisplaced, posDifference);
++
++		// Make sure inertia isn't set on the first frame otherwise the previous value isn't cleared
++		if ((platform != displaceInfo->prevPlatform) || (gGlobalTimer != displaceInfo->prevTimer + 1)) {
++			vec3f_set(sMarioAmountDisplaced, 0.f, 0.f, 0.f);
++		}
++	}
++
++	// Update info for next frame
++	// Update position
++	vec3f_copy(displaceInfo->prevPos, pos);
++
++	// Set yaw info
++	vec3f_set(yawVec, sins(*yaw), 0, coss(*yaw));
++	linear_mtxf_transpose_mul_vec3f(*platform->header.gfx.throwMatrix, displaceInfo->prevTransformedYawVec, yawVec);
++	displaceInfo->prevYaw = *yaw;
++
++	// Update platform and timer
++	displaceInfo->prevPlatform = platform;
++	displaceInfo->prevTimer = gGlobalTimer;
++}
+ 
+-        mtxf_rotate_zxy_and_translate(displaceMatrix, currentObjectOffset, rotation);
+-        linear_mtxf_mul_vec3f(displaceMatrix, newObjectOffset, relativeOffset);
++// Doesn't change in the code, set this to FALSE if you don't want inertia
++u8 gDoInertia = TRUE;
+ 
+-        x = platformPosX + newObjectOffset[0];
+-        y = platformPosY + newObjectOffset[1];
+-        z = platformPosZ + newObjectOffset[2];
+-    }
++static u8 sShouldApplyInertia = FALSE;
++static u8 sInertiaFirstFrame = FALSE;
+ 
+-    if (isMario) {
+-        set_mario_pos(x, y, z);
+-    } else {
+-        gCurrentObject->oPosX = x;
+-        gCurrentObject->oPosY = y;
+-        gCurrentObject->oPosZ = z;
+-    }
++/**
++ * Apply inertia based on Mario's last platform.
++ */
++static void apply_mario_inertia(void) {
++	// On the first frame of leaving the ground, boost Mario's y velocity
++	if (sInertiaFirstFrame) {
++		gMarioState->vel[1] += sMarioAmountDisplaced[1];
++	}
++
++	// Apply sideways inertia
++	gMarioState->pos[0] += sMarioAmountDisplaced[0];
++	gMarioState->pos[2] += sMarioAmountDisplaced[2];
++
++	// Drag
++	sMarioAmountDisplaced[0] *= 0.97f;
++	sMarioAmountDisplaced[2] *= 0.97f;
++
++	// Stop applying inertia once Mario has landed, or when ground pounding
++	if (!(gMarioState->action & ACT_FLAG_AIR) || (gMarioState->action == ACT_GROUND_POUND)) {
++		sShouldApplyInertia = FALSE;
++	}
+ }
+ 
+ /**
+- * If Mario's platform is not null, apply platform displacement.
++ * Apply platform displacement or inertia if required.
+  */
+ void apply_mario_platform_displacement(void) {
+     struct Object *platform;
+ 
+     platform = gMarioPlatform;
+-    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL && platform != NULL) {
+-        apply_platform_displacement(1, platform);
++    if (!(gTimeStopState & TIME_STOP_ACTIVE) && gMarioObject != NULL) {
++		if (platform != NULL) {
++			apply_platform_displacement(&sMarioDisplacementInfo, gMarioState->pos, &gMarioState->faceAngle[1], platform);
++			sShouldApplyInertia = TRUE;
++			sInertiaFirstFrame = TRUE;
++		} else if (sShouldApplyInertia && gDoInertia) {
++			apply_mario_inertia();
++			sInertiaFirstFrame = FALSE;
++		}
+     }
+ }
+ 
diff --git a/src/game/platform_displacement.h b/src/game/platform_displacement.h
index 556192b..3609e2d 100644
--- a/src/game/platform_displacement.h
+++ b/src/game/platform_displacement.h
@@ -5,10 +5,19 @@
 
 #include "types.h"
 
+struct PlatformDisplacementInfo {
+	Vec3f prevPos;
+	Vec3f prevTransformedPos;
+	Vec3f prevTransformedYawVec;
+	s16 prevYaw;
+	struct Object *prevPlatform;
+	s32 prevTimer;
+};
+
 void update_mario_platform(void);
 void get_mario_pos(f32 *x, f32 *y, f32 *z);
 void set_mario_pos(f32 x, f32 y, f32 z);
-void apply_platform_displacement(u32 isMario, struct Object *platform);
+void apply_platform_displacement(struct PlatformDisplacementInfo *displaceInfo, Vec3f pos, s16 *yaw, struct Object *platform);
 void apply_mario_platform_displacement(void);
 #ifndef VERSION_JP
 void clear_mario_platform(void);
