Repository: vabold/Kinoko (language: cpp)

Issue:
Initializing karts causes segfaults



Discussion / hints:

Fix patch:
diff --git a/STATUS.md b/STATUS.md
index a556d16f..c8a33e2c 100644
--- a/STATUS.md
+++ b/STATUS.md
@@ -10,7 +10,7 @@ Test Case                                                 | Frames      |     |
 [`tf-ng-rta-1-49-039`](https://youtu.be/mqQa_1Cq1bw)      | 690 / 6947  | ❌ | Moving road
 [`mc-rta-0-46-553`](https://youtu.be/1F2xfHYrkXM)         | 634 / 3202  | ❌ | Wallclip
 [`mc-ng-rta-1-20-357`](https://youtu.be/kG8PvG8K1ZA)      | 1223 / 4228 | ❌ | KMP
-[`cm-rta-0-25-145`](https://youtu.be/F_RUQVghmuA)         | 4 / 1919    | ❌ | Unknown
+[`cm-rta-0-25-145`](https://youtu.be/F_RUQVghmuA)         | 582 / 1919  | ❌ | HWG
 [`cm-ng-rta-1-55-264`](https://youtu.be/XxKG3IYWduE)      | 638 / 7320  | ❌ | KMP
 [`dks-rta-1-44-568`](https://youtu.be/b9hacHlifcw)        | 620 / 6679  | ❌ | Cannon
 [`wgm-rta-0-31-678`](https://youtu.be/VVFXP639DRY)        | 563 / 2310  | ❌ | KMP
@@ -23,12 +23,12 @@ Test Case                                                 | Frames      |     |
 [`gv-rta-0-15-425`](https://youtu.be/bB0oUzdCHTA)         | 488 / 1336  | ❌ | KMP?
 [`gv-ng-rta-1-32-914`](https://youtu.be/J55Fo2ZMz9M)      | 571 / 5981  | ❌ | KMP?
 [`gv-nosc-rta-1-50-927`](https://youtu.be/R7oK3U7iZrk)    | 482 / 7060  | ❌ | KMP?
-[`ddr-rta-1-46-400`](https://youtu.be/nVcVbd4n3yM)        | 4 / 6789    | ❌ | KCL
+[`ddr-rta-1-46-400`](https://youtu.be/nVcVbd4n3yM)        | 494 / 6789  | ❌ | Sand drift?
 [`mh-rta-1-42-872`](https://youtu.be/CellUlOYgnc)         | 6578 / 6578 | ✔️ | 
 [`bc-rta-2-08-697`](https://youtu.be/1DEReKemoeI)         | 809 / 8126  | ❌ | KMP
 [`bc-ng-rta-2-20-001`](https://youtu.be/028nClzy7B4)      | 809 / 8803  | ❌ | KMP
 [`rr-rta-1-24-751`](https://youtu.be/dgNMHyFda14)         | 349 / 5491  | ❌ | Outward-drifting bike
-[`rr-ng-rta-2-24-281`](https://youtu.be/O-BtWWsq82o)      | `SEGFAULT`  | ❌ | Karts
+[`rr-ng-rta-2-24-281`](https://youtu.be/O-BtWWsq82o)      | 467 / 9060  | ❌ | Outward-drifting kart
 [`rpb-rta-0-59-978`](https://youtu.be/Z-lVl-7B-So)        | 1079 / 4007 | ❌ | Moving water
 [`rpb-ng-rta-1-12-656`](https://youtu.be/LujU0kJx-hU)     | 2492 / 4767 | ❌ | Moving water
 [`ryf-rta-0-58-648`](https://youtu.be/3IKzbmawUbk)        | 584 / 3927  | ❌ | Water
@@ -40,10 +40,10 @@ Test Case                                                 | Frames      |     |
 [`rsgb-rta-1-14-513`](https://youtu.be/lgfw-zswqIM)       | 356 / 4878  | ❌ | Outward-drifting bike
 [`rsgb-ng-rta-1-21-363`](https://youtu.be/SjXUPXT8n8g)    | 5288 / 5288 | ✔️ | 
 [`rds-rta-2-03-525`](https://youtu.be/a9Mnd2W7JXI)        | 1943 / 7816 | ❌ | Taildive slipdrift
-[`rws-rta-1-31-987`](https://youtu.be/2rDSx5pgQ9A)        | 4 / 5925    | ❌ | Unknown
+[`rws-rta-1-31-987`](https://youtu.be/2rDSx5pgQ9A)        | 826 / 5925  | ❌ | HWG
 [`rws-ng-rta-1-48-193`](https://youtu.be/4PU4zpCU_q4)     | 770 / 6897  | ❌ | Unknown
-[`rdh-rta-1-30-425`](https://youtu.be/v5Qj0DnqVo0)        | 3 / 5832    | ❌ | KCL
-[`rdh-ng-rta-1-34-237`](https://youtu.be/4Lp-ehOOiGo)     | 3 / 6060    | ❌ | KCL
+[`rdh-rta-1-30-425`](https://youtu.be/v5Qj0DnqVo0)        | 664 / 5832  | ❌ | Wall clip
+[`rdh-ng-rta-1-34-237`](https://youtu.be/4Lp-ehOOiGo)     | 1476 / 6060 | ❌ | Trick landing
 [`rbc3-rta-1-55-715`](https://youtu.be/vSbSADDEzEs)       | 7347 / 7347 | ✔️ | 
 [`rbc3-ng-rta-2-16-183`](https://youtu.be/xZwlaonIBws)    | 2006 / 8574 | ❌ | KMP
 [`rdkjp-rta-0-40-105`](https://youtu.be/bkinW1UZK6M)      | 581 / 2815  | ❌ | Offroad rotation
diff --git a/source/egg/math/Matrix.cc b/source/egg/math/Matrix.cc
index b5aa71c1..053feca1 100644
--- a/source/egg/math/Matrix.cc
+++ b/source/egg/math/Matrix.cc
@@ -218,11 +218,12 @@ Vector3f Matrix34f::multVector33(const Vector3f &vec) const {
 /// @details Unlike a typical matrix inversion, if the determinant is 0, then this function returns
 /// the identity matrix.
 Matrix34f Matrix34f::inverseTo33() const {
-    f32 determinant = (((mtx[2][1] * mtx[0][2] * mtx[1][0] + mtx[2][2] * mtx[0][0] * mtx[1][1] +
-                                mtx[2][0] * mtx[0][1] * mtx[1][2]) -
-                               mtx[0][2] * mtx[2][0] * mtx[1][1]) -
-                              mtx[2][2] * mtx[1][0] * mtx[0][1]) -
-            mtx[1][2] * mtx[0][0] * mtx[2][1];
+    f32 determinant = ((((mtx[2][1] * (mtx[0][2] * mtx[1][0])) +
+                                ((mtx[2][2] * (mtx[0][0] * mtx[1][1])) +
+                                        (mtx[2][0] * (mtx[0][1] * mtx[1][2])))) -
+                               (mtx[0][2] * (mtx[2][0] * mtx[1][1]))) -
+                              (mtx[2][2] * (mtx[1][0] * mtx[0][1]))) -
+            (mtx[1][2] * (mtx[0][0] * mtx[2][1]));
 
     if (determinant == 0.0f) {
         return Matrix34f::ident;
diff --git a/source/game/kart/KartBody.cc b/source/game/kart/KartBody.cc
index 16f27ee9..285d99b5 100644
--- a/source/game/kart/KartBody.cc
+++ b/source/game/kart/KartBody.cc
@@ -10,9 +10,11 @@ KartBody::KartBody(KartPhysics *physics) : m_physics(physics) {
 }
 
 /// @addr{0x8056C604}
-/// @todo: Implement for karts
+/// @brief Computes a matrix to represent wheel rotation. For Karts, this is wheel-agnostic.
 EGG::Matrix34f KartBody::wheelMatrix(u16) {
-    return EGG::Matrix34f::ident;
+    EGG::Matrix34f mat;
+    mat.makeQT(fullRot(), pos());
+    return mat;
 }
 
 /// @addr{0x8056C4B4}
diff --git a/source/game/kart/KartDynamics.cc b/source/game/kart/KartDynamics.cc
index 01b92338..300874cb 100644
--- a/source/game/kart/KartDynamics.cc
+++ b/source/game/kart/KartDynamics.cc
@@ -15,6 +15,19 @@ KartDynamics::KartDynamics() {
 /// @addr{0x8059F6B8}
 KartDynamics::~KartDynamics() = default;
 
+/// @addr{0x805B5B68}
+/// @brief Stabilizes the kart by rotating towards the y-axis unit vector.
+void KartDynamics::stabilize() {
+    EGG::Vector3f top = m_mainRot.rotateVector(EGG::Vector3f::ey);
+    if (EGG::Mathf::abs(top.dot(m_top)) >= 0.9999f) {
+        return;
+    }
+
+    EGG::Quatf q;
+    q.makeVectorRotation(top, m_top);
+    m_mainRot = m_mainRot.slerpTo(q.multSwap(m_mainRot), m_stabilizationFactor);
+}
+
 /// @addr{0x805B4B54}
 void KartDynamics::init() {
     m_speedNorm = 0.0f;
@@ -95,8 +108,8 @@ void KartDynamics::calc(f32 dt, f32 maxSpeed, bool /*air*/) {
     m_velocity *= m_speedNorm;
     m_pos += m_velocity;
 
-    EGG::Vector3f t1 = m_invInertiaTensor.multVector(m_totalTorque) * dt;
-    m_angVel0 += (t1 + m_invInertiaTensor.multVector(t1 + m_totalTorque) * dt) * 0.5f;
+    EGG::Vector3f t1 = m_invInertiaTensor.multVector33(m_totalTorque) * dt;
+    m_angVel0 += (t1 + m_invInertiaTensor.multVector33(t1 + m_totalTorque) * dt) * 0.5f;
 
     m_angVel0.x = std::min(0.4f, std::max(-0.4f, m_angVel0.x));
     m_angVel0.y = std::min(0.4f, std::max(-0.4f, m_angVel0.y)) * m_angVel0YFactor;
@@ -302,7 +315,7 @@ void KartDynamicsBike::stabilize() {
     local_4c.normalise();
 
     EGG::Vector3f top = m_mainRot.rotateVector(EGG::Vector3f::ey);
-    if (top.dot(local_4c) >= 0.9999f) {
+    if (EGG::Mathf::abs(top.dot(local_4c)) >= 0.9999f) {
         return;
     }
 
diff --git a/source/game/kart/KartDynamics.hh b/source/game/kart/KartDynamics.hh
index b2e915f8..5796502e 100644
--- a/source/game/kart/KartDynamics.hh
+++ b/source/game/kart/KartDynamics.hh
@@ -15,7 +15,7 @@ public:
     virtual ~KartDynamics();
 
     virtual void forceUpright() {}
-    virtual void stabilize() {}
+    virtual void stabilize();
 
     void init();
     void resetInternalVelocity();
diff --git a/source/game/kart/KartMove.cc b/source/game/kart/KartMove.cc
index a3f09006..2c63a3b6 100644
--- a/source/game/kart/KartMove.cc
+++ b/source/game/kart/KartMove.cc
@@ -43,6 +43,11 @@ KartMove::KartMove() : m_smoothedUp(EGG::Vector3f::ey), m_scale(1.0f, 1.0f, 1.0f
 /// @addr{0x80587B78}
 KartMove::~KartMove() = default;
 
+/// @addr{0x8057821C}
+void KartMove::createSubsystems() {
+    m_jump = new KartJump(this);
+}
+
 /// @stage All
 /// @brief Each frame, looks at player input and kart stats. Saves turn-related info.
 /// @addr{0x8057A8B4}
@@ -82,11 +87,18 @@ void KartMove::calcTurn() {
 
 /// @addr{0x8057829C}
 void KartMove::setTurnParams() {
+    static constexpr std::array<DriftingParameters, 3> DRIFTING_PARAMS_ARRAY = {{
+            {10.0f, 0.5f, 0.5f, 1.0f},
+            {10.0f, 0.5f, 0.5f, 0.2f},
+            {10.0f, 0.22f, 0.5f, 0.2f},
+    }};
+
     init(false, false);
     m_dir = bodyFront();
     m_lastDir = m_dir;
     m_vel1Dir = m_dir;
     m_landingDir = m_dir;
+    m_driftingParams = &DRIFTING_PARAMS_ARRAY[static_cast<u32>(param()->stats().driftType)];
 }
 
 /// @addr{0x805784D4}
@@ -248,7 +260,7 @@ void KartMove::calcTop() {
         m_hasLandingDir = true;
     } else {
         if (state()->isHop() && m_hopPosY > 0.0f) {
-            stabilizationFactor = 0.22f;
+            stabilizationFactor = m_driftingParams->stabilizationFactor;
         } else if (state()->isTouchingGround()) {
             if (state()->trickableTimer() > 0 && inputTop.dot(m_dir) > 0.0f && m_speed > 50.0f &&
                     collide()->isNotTrickable()) {
@@ -1073,7 +1085,7 @@ void KartMove::calcStandstillBoostRot() {
                 } else {
                     next = (scalar * 0.15f) * 0.08f;
                 }
-                scalar = 0.2f;
+                scalar = m_driftingParams->boostRotFactor;
             }
         } else {
             constexpr s16 MAX_SSMT_CHARGE = 75;
@@ -1172,12 +1184,54 @@ void KartMove::calcHopPhysics() {
     }
 }
 
+/// @addr{0x8057CF0C}
+/// @brief Every frame, calculates rotation, EV, and angular velocity for the kart.
+void KartMove::calcVehicleRotation(f32 turn) {
+    f32 tiltMagnitude = 0.0f;
+
+    if (state()->isAnyWheelCollision()) {
+        EGG::Vector3f front = componentZAxis();
+        front = front.perpInPlane(m_up, true);
+        EGG::Vector3f frontSpeed = velocity().rej(front).perpInPlane(m_up, false);
+        f32 dot = frontSpeed.dot();
+
+        f32 magnitude = tiltMagnitude;
+        if (dot > FLT_EPSILON) {
+            magnitude = front.length();
+
+            if (front.z * frontSpeed.x - front.x * frontSpeed.z > 0.0f) {
+                magnitude = -magnitude;
+            }
+
+            tiltMagnitude = -1.0f;
+            if (-1.0f <= magnitude) {
+                tiltMagnitude = std::min(1.0f, magnitude);
+            }
+        }
+    } else if (!state()->isHop() || m_hopPosY <= 0.0f) {
+        EGG::Vector3f angVel0 = dynamics()->angVel0();
+        angVel0.z *= 0.98f;
+        dynamics()->setAngVel0(angVel0);
+    }
+
+    f32 lean = EGG::Mathf::abs(m_weightedTurn) * (tiltMagnitude * param()->stats().tilt);
+
+    calcStandstillBoostRot();
+
+    EGG::Vector3f angVel0 = dynamics()->angVel0();
+    angVel0.x += m_standStillBoostRot;
+    angVel0.z += lean;
+    dynamics()->setAngVel0(angVel0);
+
+    EGG::Vector3f angVel2 = dynamics()->angVel2();
+    angVel2.y += turn;
+    dynamics()->setAngVel2(angVel2);
+}
+
 /// @stage 2
 /// @brief Initializes hop information, resets upwards EV and clears upwards force.
 /// @addr{0x8057DA5C}
 void KartMove::hop() {
-    constexpr f32 INITIAL_HOP_VEL = 10.0f;
-
     state()->setHop(true);
     state()->setDriftManual(false);
     onHop();
@@ -1189,7 +1243,7 @@ void KartMove::hop() {
     m_hopFrame = 0;
     m_hopPosY = 0.0f;
     m_hopGravity = dynamics()->gravity();
-    m_hopVelY = INITIAL_HOP_VEL;
+    m_hopVelY = m_driftingParams->hopVelY;
 
     EGG::Vector3f extVel = dynamics()->extVel();
     extVel.y = 0.0f + m_hopVelY;
diff --git a/source/game/kart/KartMove.hh b/source/game/kart/KartMove.hh
index 4f6809d5..fff53aef 100644
--- a/source/game/kart/KartMove.hh
+++ b/source/game/kart/KartMove.hh
@@ -12,7 +12,7 @@ public:
     KartMove();
     virtual ~KartMove();
 
-    virtual void createSubsystems() {}
+    virtual void createSubsystems();
     virtual void calcTurn();
     virtual void calcWheelie() {}
     virtual void setTurnParams();
@@ -49,7 +49,7 @@ public:
     void calcDive();
     void calcSsmtStart();
     void calcHopPhysics();
-    virtual void calcVehicleRotation(f32 /*turn*/) {}
+    virtual void calcVehicleRotation(f32 turn);
     virtual void hop();
     virtual void onHop() {}
     virtual void onWallCollision() {}
@@ -124,6 +124,14 @@ protected:
         f32 velY;
     };
 
+    /// @brief Houses parameters that vary between the drift type (inward bike, outward bike, kart).
+    struct DriftingParameters {
+        f32 hopVelY;
+        f32 stabilizationFactor;
+        f32 _8;
+        f32 boostRotFactor;
+    };
+
     f32 m_baseSpeed;      ///< The speed associated with the current character/vehicle stats.
     f32 m_softSpeedLimit; ///< Base speed + boosts + wheelies, restricted to the hard speed limit.
     f32 m_speed;          ///< Current speed, restricted to the soft speed limit.
@@ -181,6 +189,7 @@ protected:
     bool m_bSsmtLeeway;  ///< If set, activates SSMT when not pressing A or B.
     bool m_bWallBounce;  ///< Set when our speed loss from wall collision is > 30.0f.
     KartJump *m_jump;
+    const DriftingParameters *m_driftingParams; ///< Drift-type-specific parameters.
     f32 m_rawTurn; ///< Float in range [-1, 1]. Represents stick magnitude + direction.
 };
 
diff --git a/source/game/kart/KartObject.cc b/source/game/kart/KartObject.cc
index 4a059e87..4c31b033 100644
--- a/source/game/kart/KartObject.cc
+++ b/source/game/kart/KartObject.cc
@@ -33,6 +33,46 @@ KartObject::~KartObject() {
     }
 }
 
+/// @addr{0x8058EA0C}
+void KartObject::createTires() {
+    constexpr u16 BSP_WHEEL_INDICES[8] = {0, 0, 1, 1, 2, 2, 3, 3};
+    constexpr KartSuspensionPhysics::TireType X_MIRRORED_TIRE[8] = {
+            KartSuspensionPhysics::TireType::Kart,
+            KartSuspensionPhysics::TireType::KartReflected,
+            KartSuspensionPhysics::TireType::Kart,
+            KartSuspensionPhysics::TireType::KartReflected,
+            KartSuspensionPhysics::TireType::Kart,
+            KartSuspensionPhysics::TireType::KartReflected,
+            KartSuspensionPhysics::TireType::Kart,
+            KartSuspensionPhysics::TireType::KartReflected,
+    };
+
+    auto bodyType = m_pointers.param->stats().body;
+    u32 tireCount = m_pointers.param->tireCount();
+
+    if (bodyType == KartParam::Stats::Body::Three_Wheel_Kart) {
+        tireCount = 4;
+    }
+
+    for (u16 wheelIdx = 0; wheelIdx < tireCount; ++wheelIdx) {
+        if (bodyType == KartParam::Stats::Body::Three_Wheel_Kart && wheelIdx == 0) {
+            continue;
+        }
+
+        u16 bspWheelIdx = BSP_WHEEL_INDICES[wheelIdx];
+        KartSuspensionPhysics::TireType tireType = X_MIRRORED_TIRE[wheelIdx];
+
+        KartSuspension *sus = new KartSuspension;
+        KartTire *tire = (bspWheelIdx == 0) ? new KartTireFront(tireType, bspWheelIdx) :
+                                              new KartTire(tireType, bspWheelIdx);
+
+        m_pointers.suspensions.push_back(sus);
+        m_pointers.tires.push_back(tire);
+
+        sus->init(wheelIdx, tireType, bspWheelIdx);
+    }
+}
+
 /// @addr{0x8058E5F8}
 KartBody *KartObject::createBody(KartPhysics *physics) {
     return new KartBodyKart(physics);
@@ -180,16 +220,16 @@ void KartObjectBike::createTires() {
 
         if (wheelIdx == 0 || wheelIdx == 2) {
             sus = new KartSuspensionFrontBike;
-            tire = new KartTireFrontBike(0);
+            tire = new KartTireFrontBike(KartSuspensionPhysics::TireType::Bike, 0);
         } else {
             sus = new KartSuspensionRearBike;
-            tire = new KartTireRearBike(1);
+            tire = new KartTireRearBike(KartSuspensionPhysics::TireType::Bike, 1);
         }
 
         m_pointers.suspensions.push_back(sus);
         m_pointers.tires.push_back(tire);
 
-        sus->init(wheelIdx, wheelIdx);
+        sus->init(wheelIdx, KartSuspensionPhysics::TireType::Bike, wheelIdx);
     }
 }
 } // namespace Kart
diff --git a/source/game/kart/KartObject.hh b/source/game/kart/KartObject.hh
index b0fe0184..2a223a13 100644
--- a/source/game/kart/KartObject.hh
+++ b/source/game/kart/KartObject.hh
@@ -13,7 +13,7 @@ public:
     KartObject(KartParam *param);
     virtual ~KartObject();
     [[nodiscard]] virtual KartBody *createBody(KartPhysics *physics);
-    virtual void createTires() {}
+    virtual void createTires();
 
     void init();
     void initImpl();
diff --git a/source/game/kart/KartObjectProxy.cc b/source/game/kart/KartObjectProxy.cc
index a5571a0f..5387804a 100644
--- a/source/game/kart/KartObjectProxy.cc
+++ b/source/game/kart/KartObjectProxy.cc
@@ -272,6 +272,10 @@ const EGG::Vector3f &KartObjectProxy::intVel() const {
     return dynamics()->intVel();
 }
 
+const EGG::Vector3f &KartObjectProxy::velocity() const {
+    return dynamics()->velocity();
+}
+
 /// @addr{0x80590CF8}
 f32 KartObjectProxy::speed() const {
     return move()->speed();
diff --git a/source/game/kart/KartObjectProxy.hh b/source/game/kart/KartObjectProxy.hh
index c8a70f95..730e4b52 100644
--- a/source/game/kart/KartObjectProxy.hh
+++ b/source/game/kart/KartObjectProxy.hh
@@ -112,6 +112,7 @@ public:
     [[nodiscard]] const EGG::Quatf &fullRot() const;
     [[nodiscard]] const EGG::Vector3f &extVel() const;
     [[nodiscard]] const EGG::Vector3f &intVel() const;
+    [[nodiscard]] const EGG::Vector3f &velocity() const;
     [[nodiscard]] f32 speed() const;
     [[nodiscard]] f32 acceleration() const;
     [[nodiscard]] f32 softSpeedLimit() const;
diff --git a/source/game/kart/KartSuspension.cc b/source/game/kart/KartSuspension.cc
index 74f6a887..550c5504 100644
--- a/source/game/kart/KartSuspension.cc
+++ b/source/game/kart/KartSuspension.cc
@@ -11,8 +11,8 @@ KartSuspension::~KartSuspension() {
 }
 
 /// @addr{0x80598B60}
-void KartSuspension::init(u16 wheelIdx, u16 bspWheelIdx) {
-    m_physics = new KartSuspensionPhysics(wheelIdx, bspWheelIdx);
+void KartSuspension::init(u16 wheelIdx, KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx) {
+    m_physics = new KartSuspensionPhysics(wheelIdx, tireType, bspWheelIdx);
 }
 
 /// @addr{0x80598BD4}
diff --git a/source/game/kart/KartSuspension.hh b/source/game/kart/KartSuspension.hh
index 52e6f768..e00f35fd 100644
--- a/source/game/kart/KartSuspension.hh
+++ b/source/game/kart/KartSuspension.hh
@@ -11,7 +11,7 @@ public:
     KartSuspension();
     virtual ~KartSuspension();
 
-    void init(u16 wheelIdx, u16 bspWheelIdx);
+    void init(u16 wheelIdx, KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx);
     void initPhysics();
 
     /// @beginSetters
diff --git a/source/game/kart/KartSuspensionPhysics.cc b/source/game/kart/KartSuspensionPhysics.cc
index 2ac1c8ae..707ae1f5 100644
--- a/source/game/kart/KartSuspensionPhysics.cc
+++ b/source/game/kart/KartSuspensionPhysics.cc
@@ -169,8 +169,9 @@ f32 WheelPhysics::_74() const {
 }
 
 /// @addr{0x80599ED4}
-KartSuspensionPhysics::KartSuspensionPhysics(u16 wheelIdx, u16 bspWheelIdx)
-    : m_tirePhysics(nullptr), m_bspWheelIdx(bspWheelIdx), m_wheelIdx(wheelIdx) {}
+KartSuspensionPhysics::KartSuspensionPhysics(u16 wheelIdx, TireType tireType, u16 bspWheelIdx)
+    : m_tirePhysics(nullptr), m_tireType(tireType), m_bspWheelIdx(bspWheelIdx),
+      m_wheelIdx(wheelIdx) {}
 
 /// @addr{0x8059AA04}
 KartSuspensionPhysics::~KartSuspensionPhysics() = default;
@@ -190,10 +191,13 @@ void KartSuspensionPhysics::reset() {
 
 /// @addr{0x8059A02C}
 void KartSuspensionPhysics::setInitialState() {
-    EGG::Vector3f rotatedRelPos = dynamics()->fullRot().rotateVector(m_bspWheel->relPosition);
-    rotatedRelPos += pos();
+    EGG::Vector3f relPos = m_bspWheel->relPosition;
+    if (m_tireType == TireType::KartReflected) {
+        relPos.x = -relPos.x;
+    }
 
-    EGG::Vector3f unitRotated = dynamics()->fullRot().rotateVector(-EGG::Vector3f::ey);
+    const EGG::Vector3f rotatedRelPos = dynamics()->fullRot().rotateVector(relPos) + pos();
+    const EGG::Vector3f unitRotated = dynamics()->fullRot().rotateVector(-EGG::Vector3f::ey);
 
     m_tirePhysics->setPos(rotatedRelPos + m_bspWheel->maxTravel * unitRotated);
     m_tirePhysics->setLastPos(rotatedRelPos + m_bspWheel->maxTravel * unitRotated);
@@ -209,7 +213,13 @@ void KartSuspensionPhysics::setInitialState() {
 void KartSuspensionPhysics::calcCollision(f32 dt, const EGG::Vector3f &gravity,
         const EGG::Matrix34f &mat) {
     m_maxTravelScaled = m_bspWheel->maxTravel * sub()->someScale();
-    EGG::Vector3f topmostPos = mat.ps_multVector(m_bspWheel->relPosition * scale());
+
+    EGG::Vector3f scaledRelPos = m_bspWheel->relPosition * scale();
+    if (m_tireType == TireType::KartReflected) {
+        scaledRelPos.x = -scaledRelPos.x;
+    }
+
+    const EGG::Vector3f topmostPos = mat.ps_multVector(scaledRelPos);
     EGG::Matrix34f mStack_60;
     EGG::Vector3f euler_angles(m_bspWheel->xRot * DEG2RAD, 0.0f, 0.0f);
     mStack_60.makeR(euler_angles);
diff --git a/source/game/kart/KartSuspensionPhysics.hh b/source/game/kart/KartSuspensionPhysics.hh
index 4e72b524..608ed4fa 100644
--- a/source/game/kart/KartSuspensionPhysics.hh
+++ b/source/game/kart/KartSuspensionPhysics.hh
@@ -65,7 +65,14 @@ private:
 /// @brief Physics for a single wheel's suspension.
 class KartSuspensionPhysics : KartObjectProxy {
 public:
-    KartSuspensionPhysics(u16 wheelIdx, u16 bspWheelIdx);
+    /// @brief Every other kart tire is a mirror of the first. Bikes do not leverage this.
+    enum class TireType {
+        Kart,
+        KartReflected,
+        Bike,
+    };
+
+    KartSuspensionPhysics(u16 wheelIdx, TireType TireType, u16 bspWheelIdx);
     ~KartSuspensionPhysics();
 
     void init();
@@ -79,6 +86,7 @@ public:
 private:
     const BSP::Wheel *m_bspWheel;
     WheelPhysics *m_tirePhysics;
+    TireType m_tireType;
     u16 m_bspWheelIdx;
     u16 m_wheelIdx;
     EGG::Vector3f m_topmostPos;
diff --git a/source/game/kart/KartTire.cc b/source/game/kart/KartTire.cc
index 42834c2d..1a3a6d8b 100644
--- a/source/game/kart/KartTire.cc
+++ b/source/game/kart/KartTire.cc
@@ -3,7 +3,8 @@
 namespace Kart {
 
 /// @addr{0x8059AA44}
-KartTire::KartTire(u16 bspWheelIdx) : m_bspWheelIdx(bspWheelIdx) {}
+KartTire::KartTire(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx)
+    : m_tireType(tireType), m_bspWheelIdx(bspWheelIdx) {}
 
 /// @addr{0x8058EC08}
 KartTire::~KartTire() {
@@ -30,7 +31,20 @@ WheelPhysics *KartTire::wheelPhysics() {
     return m_wheelPhysics;
 }
 
-KartTireFrontBike::KartTireFrontBike(u16 bspWheelIdx) : KartTire(bspWheelIdx) {}
+/// @addr{Inlined in 0x8058EA0C}
+KartTireFront::KartTireFront(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx)
+    : KartTire(tireType, bspWheelIdx) {}
+
+/// @addr{0x8058F4AC}
+KartTireFront::~KartTireFront() = default;
+
+/// @addr{0x8059AC1C}
+void KartTireFront::createPhysics(u16 tireIdx) {
+    m_wheelPhysics = new WheelPhysics(tireIdx, 0);
+}
+
+KartTireFrontBike::KartTireFrontBike(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx)
+    : KartTire(tireType, bspWheelIdx) {}
 
 /// @addr{0x8058F4EC}
 KartTireFrontBike::~KartTireFrontBike() = default;
@@ -40,7 +54,8 @@ void KartTireFrontBike::createPhysics(u16 tireIdx) {
     m_wheelPhysics = new WheelPhysics(tireIdx, 0);
 }
 
-KartTireRearBike::KartTireRearBike(u16 bspWheelIdx) : KartTire(bspWheelIdx) {}
+KartTireRearBike::KartTireRearBike(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx)
+    : KartTire(tireType, bspWheelIdx) {}
 
 /// @addr{0x8059B564}
 KartTireRearBike::~KartTireRearBike() = default;
diff --git a/source/game/kart/KartTire.hh b/source/game/kart/KartTire.hh
index f0bffb16..4be32623 100644
--- a/source/game/kart/KartTire.hh
+++ b/source/game/kart/KartTire.hh
@@ -8,7 +8,7 @@ namespace Kart {
 /// @nosubgrouping
 class KartTire {
 public:
-    KartTire(u16 bspWheelIdx);
+    KartTire(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx);
     virtual ~KartTire();
 
     virtual void createPhysics(u16 tireIdx);
@@ -21,14 +21,24 @@ public:
     /// @endGetters
 
 protected:
+    KartSuspensionPhysics::TireType m_tireType;
     u16 m_bspWheelIdx;
     WheelPhysics *m_wheelPhysics;
 };
 
+/// @brief A holder for a kart's front tire's physics data.
+class KartTireFront : public KartTire {
+public:
+    KartTireFront(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx);
+    ~KartTireFront();
+
+    void createPhysics(u16 tireIdx) override;
+};
+
 /// @brief A holder for a bike's front tire's physics data.
 class KartTireFrontBike : public KartTire {
 public:
-    KartTireFrontBike(u16 bspWheelIdx);
+    KartTireFrontBike(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx);
     ~KartTireFrontBike();
 
     void createPhysics(u16 tireIdx) override;
@@ -37,7 +47,7 @@ public:
 /// @brief A holder for a bike's rear tire's physics data.
 class KartTireRearBike : public KartTire {
 public:
-    KartTireRearBike(u16 bspWheelIdx);
+    KartTireRearBike(KartSuspensionPhysics::TireType tireType, u16 bspWheelIdx);
     ~KartTireRearBike();
 
     void createPhysics(u16 tireIdx) override;
diff --git a/source/game/render/KartModel.cc b/source/game/render/KartModel.cc
index b4a42349..97c969e3 100644
--- a/source/game/render/KartModel.cc
+++ b/source/game/render/KartModel.cc
@@ -59,7 +59,7 @@ void KartModel::vf_1c() {
             dVar13 = m_isInsideDrift ? 5.0f : 20.0f;
         }
     } else {
-        K_PANIC("NOT IMPLEMENTED FOR KARTS YET");
+        dVar13 = 15.0f;
     }
 
     f32 dVar12 = 0.0f;
@@ -69,22 +69,16 @@ void KartModel::vf_1c() {
         dVar12 = -_58 * dVar13;
 
         if (m_somethingLeft) {
-            if (m_isInsideDrift) {
-                dVar12 += 5.0f;
-            } else {
-                dVar12 += 10.0f;
-            }
-        } else {
-            if (m_somethingRight) {
-                if (m_isInsideDrift) {
-                    dVar12 -= 5.0f;
-                } else {
-                    dVar12 -= 10.0f;
-                }
-            }
+            dVar12 += m_isInsideDrift ? 5.0f : 10.0f;
+        } else if (m_somethingRight) {
+            dVar12 -= m_isInsideDrift ? 5.0f : 10.0f;
         }
     } else {
-        K_PANIC("NOT IMPLEMENTED FOR KARTS YET");
+        if (!m_somethingLeft && m_somethingRight) {
+            dVar12 -= 5.0f;
+        } else {
+            dVar12 += 5.0f;
+        }
     }
 
     if (dVar12 <= _5c) {


Test patch:
diff --git a/testCases.json b/testCases.json
index 1f56e740..58d8162a 100644
--- a/testCases.json
+++ b/testCases.json
@@ -1,4 +1,9 @@
 {
+    "Flare RR NG RTA WR": {
+        "rkgPath": "samples/rr-ng-rta-2-24-281.rkg",
+        "krkgPath": "samples/rr-ng-rta-2-24-281.krkg",
+        "targetFrame": 467
+    },
     "Logan rMC NG RTA WR": {
         "rkgPath": "samples/rmc-ng-rta-1-30-272.rkg",
         "krkgPath": "samples/rmc-ng-rta-1-30-272.krkg",