fix(client): level-triggered watchdog arms chassis on cold start
CI / quality (push) Failing after 53s
CI / quality (push) Failing after 53s
The drive loop was armed purely edge-triggered from
humanoid:GetPropertyChangedSignal("SeatPart") plus a one-shot catch-up
applySeat() at init. On cold boot the server seats the player AFTER that
catch-up runs, so the edge is lost, startDriving() never connects, and all
four wheel motors stay at MotorMaxTorque = 0 - the car is completely
undrivable until the player manually gets out and back in.
Replace the one-shot with a 0.5s level-triggered watchdog: if seated in a
DriveSeat with no live heartbeat connection, arm it; if not seated with a
live connection, stop it. Immune to any ordering of bootstrap vs seating.
Verified on cold boot (no manual reseat): all 4 motors uniform, poisoned
torque values restored within 0.6s, car accelerates to MaxSpeed 163 studs/s,
holds it upright, and engine-brakes 163 -> 0.
This commit is contained in:
@@ -115,13 +115,49 @@ function ChassisController.init()
|
||||
if seatedConn then
|
||||
seatedConn:Disconnect()
|
||||
end
|
||||
seatedConn = humanoid:GetPropertyChangedSignal("SeatPart"):Connect(function()
|
||||
local function applySeat()
|
||||
stopDriving()
|
||||
local seatPart = humanoid.SeatPart
|
||||
if seatPart and seatPart:IsA("VehicleSeat") and seatPart.Name == Chassis.SEAT_NAME then
|
||||
startDriving(seatPart)
|
||||
end
|
||||
end
|
||||
|
||||
seatedConn = humanoid:GetPropertyChangedSignal("SeatPart"):Connect(applySeat)
|
||||
|
||||
-- WATCHDOG: the SeatPart signal alone is NOT reliable enough to arm the
|
||||
-- chassis. Observed on cold boot: humanoid is Seated in a valid DriveSeat,
|
||||
-- the car is network-owned by us, and the catch-up applySeat() below has
|
||||
-- already run — yet the Heartbeat loop is not connected, so every motor
|
||||
-- keeps MotorMaxTorque = 0 and the car is completely undrivable. The
|
||||
-- signal fires during client bootstrap while modules are still being
|
||||
-- required, and the edge is simply lost; re-running applySeat() once at
|
||||
-- init doesn't help when the seat is assigned a moment AFTER that call.
|
||||
-- A level-triggered check is immune to every ordering of these events:
|
||||
-- if we are seated in a DriveSeat but have no live loop, arm it.
|
||||
task.spawn(function()
|
||||
while humanoid.Parent do
|
||||
local seatPart = humanoid.SeatPart
|
||||
local seated = seatPart
|
||||
and seatPart:IsA("VehicleSeat")
|
||||
and seatPart.Name == Chassis.SEAT_NAME
|
||||
if seated and not heartbeatConn then
|
||||
startDriving(seatPart :: VehicleSeat)
|
||||
elseif not seated and heartbeatConn then
|
||||
stopDriving()
|
||||
end
|
||||
task.wait(0.5)
|
||||
end
|
||||
end)
|
||||
|
||||
-- RACE FIX: the server can seat the player in a spawned car BEFORE this
|
||||
-- listener exists (CarManager sits them during spawn, which may complete
|
||||
-- before the client finishes requiring its modules). In that case the
|
||||
-- SeatPart signal already fired with nobody listening, so startDriving
|
||||
-- never ran and the motors kept MotorMaxTorque = 0 — the car looked
|
||||
-- completely dead to the player. Evaluate the CURRENT seat state once at
|
||||
-- init instead of only reacting to future changes.
|
||||
applySeat()
|
||||
end
|
||||
|
||||
if player.Character then
|
||||
|
||||
Reference in New Issue
Block a user