diff --git a/mk64.ld b/mk64.ld
index 70f4820..f36f11c 100644
--- a/mk64.ld
+++ b/mk64.ld
@@ -75,6 +75,7 @@ SECTIONS
       BUILD_DIR/src/audio/seqplayer.o(.text);
       BUILD_DIR/src/audio/external.o(.text);
       BUILD_DIR/src/audio/port_eu.o(.text);
+      BUILD_DIR/src/flycam.o(.text);
       #if DEBUG
          BUILD_DIR/src/os/osCartRomInit.o(.text);
          BUILD_DIR/src/debug/crash_screen_enhancement.o(.text);
@@ -229,6 +230,7 @@ SECTIONS
       BUILD_DIR/src/audio/load.o(.data);
       BUILD_DIR/src/audio/playback.o(.data);
       BUILD_DIR/src/audio/effects.o(.data);
+      BUILD_DIR/src/flycam.o(.data);
       #if DEBUG
          BUILD_DIR/src/debug/crash_screen_enhancement.o(.data);
          BUILD_DIR/src/debug/debug.o(.data);
@@ -277,6 +279,7 @@ SECTIONS
       BUILD_DIR/src/audio/seqplayer.o(.rodata);
       BUILD_DIR/src/audio/external.o(.rodata);
       BUILD_DIR/src/audio/port_eu.o(.rodata);
+      BUILD_DIR/src/flycam.o(.rodata);
       #if DEBUG
          BUILD_DIR/src/debug/crash_screen_enhancement.o(.rodata);
          BUILD_DIR/src/debug/debug.o(.rodata);
@@ -311,6 +314,7 @@ SECTIONS
       BUILD_DIR/src/code_80091750.jp.o(.bss);
       BUILD_DIR/src/code_800AF9B0.o(.bss);
       BUILD_DIR/src/menus.o(.bss);
+      BUILD_DIR/src/flycam.o(.bss);
       #if DEBUG
          BUILD_DIR/src/os/osCartRomInit.o(.bss);
          BUILD_DIR/src/debug/crash_screen_enhancement.o(.bss);
diff --git a/src/camera.c b/src/camera.c
index 02275b1..406739d 100644
--- a/src/camera.c
+++ b/src/camera.c
@@ -969,7 +969,9 @@ void func_8001EE98(Player *player, Camera *camera, s8 index) {
                 func_8001E8E8(camera, player, index);
                 break;
             }
-            func_8001E45C(camera, player, index);
+
+            flycam(camera, player, index);
+            
             break;
         case 8:
             func_8001E0C4(camera, player, index);
diff --git a/src/render_player.c b/src/render_player.c
index 6b14e27..d10796b 100644
--- a/src/render_player.c
+++ b/src/render_player.c
@@ -65,6 +65,8 @@ void func_8001F9E4(Player *player, Camera *camera, s8 arg2) {
     }
 }
 
+extern u32 isFlycam;
+
 u16 check_player_camera_collision(Player *player, Camera *camera, f32 arg2, f32 arg3) {
     UNUSED f32 pad[6];
     f32 sp64;
@@ -79,6 +81,10 @@ u16 check_player_camera_collision(Player *player, Camera *camera, f32 arg2, f32
     s16 var_v0;
     u16 ret;
 
+    if (isFlycam) {
+        return 1;
+    }
+    
     ret = 0;
     switch (gActiveScreenMode) {                              /* irregular */
     case SCREEN_MODE_1P:
diff --git a/src/flycam.c b/src/flycam.c
new file mode 100644
index 0000000..5569838
--- /dev/null
+++ b/src/flycam.c
@@ -0,0 +1,328 @@
+#include <ultra64.h>
+#include <PR/os.h>
+#include <macros.h>
++#include <defines.h>
+#include <camera.h>
+#include "main.h"
+#include <libc/math.h>
+#include <common_structs.h>
+#include "racing/collision.h"
+#include <variables.h>
+#include "player_controller.h"
+#include "code_80057C60.h"
+
+// Yaw/pitch rotation sensitivity
+#define SENSITIVITY_X 0.0003f
+#define SENSITIVITY_Y 0.0003f
+
+u32 isFlycam = false;
+u32 fRankIndex = 0;
+u32 fTargetPlayer = false;
+u32 fMode; // flycam mode should probably be an enum
+u32 fModeInit = false;
+
+typedef struct {
+    Vec3f pos;
+    Vec3f lookAt;
+    Vec3s rot; 
+} FlycamSaveState;
+
+FlycamSaveState fState;
+
+void flycam_calculate_forward_vector(Camera* camera, Vec3f forwardVector);
+void flycam_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance);
+void flycam_update(Camera* camera, struct Controller *controller);
+void flycam_controller_manager(Camera *camera, struct Controller *controller, Player *player);
+void flycam_target_player(Camera *camera, u32 playerIndex);
+void flycam_move_camera_up(Camera* camera, struct Controller *controller, f32 distance);
+void flycam_save_state(Camera *camera);
+void flycam_load_state(Camera *camera);
+
+
+/**
+ * Controls
+ * 
+ * Forward: A
+ * Backward: B
+ * 
+ * Go faster: Z
+ * 
+ * Up:       C-up
+ * Down:     C-down
+ * 
+ * Targets players based on rank position
+ * 
+ * Target player: R-trig
+ * Target next player: C-right
+ * Target previous player: C-left
+ * 
+ * Switch camera modes:   D-pad left
+ * 
+ * Camera mode 1: Enter flycam at the player's position
+ * Camera mode 2: Enter flycam at previous flycam spot
+ * 
+*/
+
+void flycam(Camera *camera, Player *player, s8 index) {
+    struct Controller *controller = &gControllers[0];
+    Vec3f forwardVector;
+    f32 dirX;
+    f32 dirY;
+    f32 dirZ;
+    f32 length;
+    
+    if (controller->buttonPressed & L_TRIG) {
+        isFlycam = !isFlycam;
+
+        // Don't use `bool = !bool` here as the game code can swap these on you.
+        // Which will confuse the code. This forces it to always be correct
+        if (isFlycam) {
+            player->type |= PLAYER_KART_AI;
+        } else {
+            player->type &= PLAYER_KART_AI;
+        }
+
+        gIsHUDVisible = !isFlycam;
+
+        if (isFlycam) {
+
+            if (fMode && fModeInit) {
+                flycam_load_state(camera);
+            } else {
+                // !fMode or fMode not initialized
+                flycam_target_player(camera, get_player_index_for_player(player));
+            }
+
+            return;
+        } else {
+            if(fMode) {
+                flycam_save_state(camera);
+            }
+        }
+    }
+
+    // Driving mode
+    if (!isFlycam) {
+        // Use normal camera code
+        func_8001E45C(camera, &gPlayers[fRankIndex], index);
+        return;
+    }
+
+
+    //if (player == gPlayerOne) { return; }
+
+    //player->type &= ~PLAYER_HUMAN;
+    //player->type |= PLAYER_HUMAN;
+
+    if ((player->type & PLAYER_START_SEQUENCE)) { return; }
+    
+
+    flycam_controller_manager(camera, controller, player);
+
+
+
+
+}
+
+void flycam_save_state(Camera *camera) {
+    fState.pos[0] = camera->pos[0];
+    fState.pos[1] = camera->pos[1];
+    fState.pos[2] = camera->pos[2];
+
+    fState.lookAt[0] = camera->lookAt[0];
+    fState.lookAt[1] = camera->lookAt[1];
+    fState.lookAt[2] = camera->lookAt[2];
+
+    fState.rot[0] = camera->rot[0];
+    fState.rot[1] = camera->rot[1];
+    fState.rot[2] = camera->rot[2];
+    fModeInit = true;
+}
+
+void flycam_load_state(Camera *camera) {
+    camera->pos[0] = fState.pos[0];
+    camera->pos[1] = fState.pos[1];
+    camera->pos[2] = fState.pos[2];
+
+    camera->lookAt[0] = fState.lookAt[0];
+    camera->lookAt[1] = fState.lookAt[1];
+    camera->lookAt[2] = fState.lookAt[2];
+
+    camera->rot[0] = fState.rot[0];
+    camera->rot[1] = fState.rot[1];
+    camera->rot[2] = fState.rot[2];
+}
+
+void flycam_controller_manager(Camera *camera, struct Controller *controller, Player *player) {
+
+    if (controller->buttonPressed & U_JPAD) {
+        fMode = !fMode;
+    }
+
+    // Target a player
+    if (controller->buttonPressed & R_TRIG) {
+        fTargetPlayer = !fTargetPlayer;
+    }
+
+    // Target next player
+    if (controller->buttonPressed & L_CBUTTONS) {
+        if (fRankIndex > 0) {
+            fRankIndex--;
+            camera->playerId = fRankIndex;
+            D_800DC5EC->player = &gPlayers[fRankIndex];
+        }
+    }
+
+    // Target previous player
+    if (controller->buttonPressed & R_CBUTTONS) {
+        if (fRankIndex < 7) {
+            fRankIndex++;
+            camera->playerId = fRankIndex;
+            D_800DC5EC->player = &gPlayers[fRankIndex];
+        }
+    }
+
+    // Target camera at chosen player
+    if (fTargetPlayer) {
+        flycam_target_player(camera, gGPCurrentRacePlayerIdByRank[fRankIndex]);
+        // Don't run the other camera code.
+        return;
+    }
+
+    // Rotation
+    if (!fTargetPlayer) {
+        if (controller->stickDirection != 0) {
+            flycam_update(camera, controller);
+        }
+    }
+
+    // Forward
+    if (controller->button & A_BUTTON)  {
+        flycam_move_camera_forward(camera, controller, 3.0f);
+    }
+
+    // Backward     B button but not A button.
+    if (controller->button & B_BUTTON && !(controller->button & A_BUTTON))  {
+        flycam_move_camera_forward(camera, controller, -3.0f);
+    }
+
+    // Up
+    if (controller->button & U_CBUTTONS) {
+        flycam_move_camera_up(camera, controller, 2.0f);
+    }
+    // Up
+    if (controller->button & D_CBUTTONS) {
+        flycam_move_camera_up(camera, controller, -2.0f);
+    }
+}
+
+// Calculates the forward direction vector based on camera orientation
+void flycam_calculate_forward_vector(Camera* camera, Vec3f forwardVector) {
+    f32 pitch = (camera->rot[2] / 65535.0f) * 360.0f; // Convert pitch from 0-65535 to degrees
+    f32 yaw = (camera->rot[1] / 65535.0f) * 360.0f;   // Convert yaw from 0-65535 to degrees
+
+    // Convert degrees to radians
+    pitch = pitch * M_PI / 180.0f;
+    yaw = yaw * M_PI / 180.0f;
+
+    forwardVector[0] = -sinf(yaw) * cosf(pitch);
+    forwardVector[1] = -sinf(pitch);
+    forwardVector[2] = cosf(yaw) * cosf(pitch);
+}
+
+// Function to move the camera forward
+void flycam_move_camera_forward(Camera* camera, struct Controller *controller, f32 distance) {
+    Vec3f forwardVector;
+    Vec3f rightVector;
+    f32 length;
+    flycam_calculate_forward_vector(camera, forwardVector);
+
+    if (controller->button & Z_TRIG) {
+        distance *= 3;
+    }
+
+    // Normalize the forward vector
+    length = sqrtf(forwardVector[0] * forwardVector[0] + forwardVector[1] * forwardVector[1] + forwardVector[2] * forwardVector[2]);
+    forwardVector[0] /= length;
+    forwardVector[1] /= length;
+    forwardVector[2] /= length;
+
+    // Calculate the right vector by taking the cross product of forward and up
+    rightVector[0] = forwardVector[1] * camera->up[2] - forwardVector[2] * camera->up[1];
+    rightVector[1] = forwardVector[2] * camera->up[0] - forwardVector[0] * camera->up[2];
+    rightVector[2] = forwardVector[0] * camera->up[1] - forwardVector[1] * camera->up[0];
+
+    // Move the camera's position along the forward vector while considering its up vector
+    camera->pos[0] += forwardVector[0] * distance;
+    camera->pos[1] += forwardVector[1] * distance;
+    camera->pos[2] += forwardVector[2] * distance;
+
+    // Optionally, you can also adjust the lookAt point to maintain the same relative position
+    camera->lookAt[0] += forwardVector[0] * distance;
+    camera->lookAt[1] += forwardVector[1] * distance;
+    camera->lookAt[2] += forwardVector[2] * distance;
+}
+
+// Function to move the camera forward
+void flycam_move_camera_up(Camera* camera, struct Controller *controller, f32 distance) {
+    // Check if the Z button is pressed (for faster movement)
+    if (controller->button & Z_TRIG) {
+        distance *= 3;
+    }
+
+    // Move the camera's position along its up vector (Y-axis)
+    camera->pos[1] += distance;
+
+    // Optionally, adjust the lookAt point to maintain the same relative position
+    camera->lookAt[1] += distance;
+}
+
+// Update camera rotation and lookAt point based on input
+void flycam_update(Camera* camera, struct Controller *controller) {
+    // Calculate yaw (horizontal movement)
+    f32 yawChange = controller->rawStickX * SENSITIVITY_X;
+    f32 pitchChange = controller->rawStickY * SENSITIVITY_Y;
+    Vec3f forwardVector;
+
+    check_bounding_collision(&camera->collision, 50, camera->pos[0], camera->pos[1], camera->pos[2]);
+
+    camera->rot[1] += (short)(yawChange * 65535.0f / (2 * M_PI)); // Convert radians to 0-65535 range
+
+    camera->rot[2] += (short)(-pitchChange * 65535.0f / (2 * M_PI)); // Convert radians to 0-65535 range
+
+    if (camera->rot[2] > 15999) {
+        camera->rot[2] = 15999;
+    } else if (camera->rot[2] < -15999) {
+        camera->rot[2] = -15999;
+    }
+
+    // Update the lookAt point based on the new orientation
+    flycam_calculate_forward_vector(camera, forwardVector);
+    camera->lookAt[0] = camera->pos[0] + forwardVector[0];
+    camera->lookAt[1] = camera->pos[1] + forwardVector[1];
+    camera->lookAt[2] = camera->pos[2] + forwardVector[2];
+}
+
+void flycam_target_player(Camera *camera, u32 playerIndex) {
+    Vec3f forwardVector;// = 2.0f;
+    Player *player = &gPlayers[playerIndex];
+
+    // Calculate the direction from the player to the camera
+    f32 dirX = player->pos[0] - camera->pos[0];
+    f32 dirY = player->pos[1] - camera->pos[1];
+    f32 dirZ = player->pos[2] - camera->pos[2];
+
+    // Normalize the direction vector (if needed)
+    f32 length = sqrtf(dirX * dirX + dirY * dirY + dirZ * dirZ);
+    if (length > 0) {
+        dirX /= length;
+        dirY /= length;
+        dirZ /= length;
+    }
+
+    // Update the camera's look-at direction
+    camera->lookAt[0] = camera->pos[0] + dirX;
+    camera->lookAt[1] = camera->pos[1] + dirY;
+    camera->lookAt[2] = camera->pos[2] + dirZ;
+}
diff --git a/src/main.c b/src/main.c
index 7c90951..135e98c 100644
--- a/src/main.c
+++ b/src/main.c
@@ -578,6 +578,7 @@ void race_logic_loop(void) {
             gTickSpeed = 2;
             staff_ghosts_loop();
             if (gIsGamePaused == 0) {
+                func_8001EE98(gPlayerOneCopy, camera1, 0);
                 for (i = 0; i < gTickSpeed; i++) {
                     if (D_8015011E) {
                         gCourseTimer += 0.01666666; // 1 / 60
@@ -585,7 +586,6 @@ void race_logic_loop(void) {
                     func_802909F0();
                     evaluate_collision_for_players_and_actors();
                     func_800382DC();
-                    func_8001EE98(gPlayerOneCopy, camera1, 0);
                     func_80028F70();
                     func_8028F474();
                     func_80059AC8();
diff --git a/src/racing/render_courses.c b/src/racing/render_courses.c
index c2a84aa..338e2bd 100644
--- a/src/racing/render_courses.c
+++ b/src/racing/render_courses.c
@@ -180,6 +180,11 @@ void render_course_segments(uintptr_t addr, struct UnkStruct_800DC5EC *arg1) {
     arg1->pathCounter = temp_v1;
     temp_v1 = ((temp_v1 - 1) * 4) + var_a3;
     
+    if (isFlycam) {
+        func_8029569C();
+       return;
+    }
+
     gSPDisplayList(gDisplayListHead++, gfx[temp_v1]);
 }
 
