From c8809efa7b4503ea70050f3197bbfb487513f257 Mon Sep 17 00:00:00 2001 From: "R3AL.CH1CK3N" Date: Fri, 31 Jul 2026 16:00:06 -0400 Subject: [PATCH] fix(client): re-arm chassis when the drive loop is bound to a stale car Respawning left the car undrivable. CarManager.spawn() destroys the old car and builds a new one, but the client's Heartbeat connection survived and kept driving the DESTROYED car's motors. Because heartbeatConn stayed non-nil, the cold-start watchdog concluded the chassis was already armed and never armed the new car - torque stayed at the idle brake value and the car never moved. Track the seat the live loop is bound to (drivingSeat) and treat the loop as stale when that seat isn't the one we're sitting in, or its car has left the workspace. On stale, stop and re-arm against the new car. Verified: after 4 respawn cycles the car accelerates 49 -> 163 studs/s, travels 273 studs, stays upright at road level. --- src/client/ChassisController.luau | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/client/ChassisController.luau b/src/client/ChassisController.luau index abd3a5d..2b6a5ae 100644 --- a/src/client/ChassisController.luau +++ b/src/client/ChassisController.luau @@ -29,6 +29,11 @@ local ChassisController = {} local heartbeatConn: RBXScriptConnection? = nil local seatedConn: RBXScriptConnection? = nil +-- The seat the live heartbeatConn is bound to. On respawn the old car is +-- destroyed and a new one spawned; the connection stays alive but drives the +-- DESTROYED car's motors. Tracking the seat lets the watchdog notice that the +-- loop is stale and re-arm against the new car. +local drivingSeat: VehicleSeat? = nil local function findCarModel(seat: VehicleSeat): Model? local model = seat:FindFirstAncestorOfClass("Model") @@ -70,6 +75,7 @@ local function startDriving(seat: VehicleSeat) return end + drivingSeat = seat heartbeatConn = RunService.Heartbeat:Connect(function() local throttle = seat.ThrottleFloat local steer = seat.SteerFloat @@ -106,6 +112,7 @@ local function stopDriving() heartbeatConn:Disconnect() heartbeatConn = nil end + drivingSeat = nil end function ChassisController.init() @@ -141,7 +148,15 @@ function ChassisController.init() local seated = seatPart and seatPart:IsA("VehicleSeat") and seatPart.Name == Chassis.SEAT_NAME - if seated and not heartbeatConn then + -- A live connection bound to a different seat (or to a car that + -- has since been destroyed) is STALE: it keeps driving the old + -- car's motors, so heartbeatConn stays non-nil and the new car + -- never gets armed. Re-arm whenever the bound seat isn't the + -- one we're actually sitting in, or its car left the world. + local stale = heartbeatConn ~= nil + and (drivingSeat ~= seatPart or not seatPart:IsDescendantOf(workspace)) + if seated and (not heartbeatConn or stale) then + stopDriving() startDriving(seatPart :: VehicleSeat) elseif not seated and heartbeatConn then stopDriving()