--[[ diag_steer3.luau -- WHAT is holding the knuckle rigid? diag_steer2 proved the servo will not rotate even hoisted with zero tyre load and ServoMaxTorque up to 1e9, with both hinge axes correctly +Y. That means the knuckle is rigidly bonded to the hub by something other than the servo. Decisive test: parts joined by a WELD share an AssemblyRootPart. Parts joined by a moving CONSTRAINT do not. So if Hub_FL and Knuckle_FL report the same AssemblyRootPart, a weld is bypassing the servo. Also builds an isolated 2-part servo to confirm the engine/API itself behaves, ruling out a bad assumption about HingeConstraint. ]] local ServerScriptService = game:GetService("ServerScriptService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local RunService = game:GetService("RunService") local out = {} local function log(...) local parts = {} for i = 1, select("#", ...) do parts[#parts + 1] = tostring((select(i, ...))) end table.insert(out, table.concat(parts, " ")) end -- 1. ISOLATED SERVO -------------------------------------------------------- -- Two free parts, one servo, nothing else. If this turns, the API is fine -- and the fault is in how the car is assembled. do local folder = Instance.new("Folder") folder.Name = "IsolatedServoTest" folder.Parent = workspace local base = Instance.new("Part") base.Size = Vector3.new(4, 1, 4) base.Position = Vector3.new(0, 5100, 0) base.Anchored = true base.Parent = folder local arm = Instance.new("Part") arm.Size = Vector3.new(1, 1, 6) arm.Position = Vector3.new(0, 5102, 0) arm.Parent = folder local VERT = CFrame.Angles(0, 0, math.rad(90)) local a0 = Instance.new("Attachment") a0.CFrame = VERT a0.Parent = base local a1 = Instance.new("Attachment") a1.CFrame = VERT a1.Parent = arm local s = Instance.new("HingeConstraint") s.Attachment0 = a0 s.Attachment1 = a1 s.ActuatorType = Enum.ActuatorType.Servo s.ServoMaxTorque = 1e6 s.AngularSpeed = 6 s.LimitsEnabled = true s.LowerAngle = -35 s.UpperAngle = 35 s.TargetAngle = -30 s.Parent = base for _ = 1, 90 do RunService.Heartbeat:Wait() end log( string.format( "1. ISOLATED servo: target -30 -> CurrentAngle %.2f (%s)", s.CurrentAngle, math.abs(s.CurrentAngle + 30) < 5 and "WORKS" or "BROKEN" ) ) folder:Destroy() end -- 2. ASSEMBLY TOPOLOGY OF A REAL CAR --------------------------------------- local Shared = ReplicatedStorage:FindFirstChild("Shared") local Cars = require(Shared.Cars) local Chassis = require(Shared.Chassis) local Server = ServerScriptService:FindFirstChild("Server", true) local CarManager = require(Server.CarManager) local pad = Instance.new("Part") pad.Size = Vector3.new(2000, 8, 2000) pad.Position = Vector3.new(0, 5000, 0) pad.Anchored = true pad.Parent = workspace local car = Cars.Get("gtr_r34") local tuning = Chassis.tuningFor(car) local model = CarManager._buildForDiag(car, tuning) model.Parent = workspace model:PivotTo(CFrame.new(0, 5007, 0)) for _ = 1, 60 do RunService.Heartbeat:Wait() end local function get(name) for _, d in ipairs(model:GetDescendants()) do if d:IsA("BasePart") and d.Name == name then return d end end return nil end local body = model.PrimaryPart local hubFL = get("Hub_FL") local knuckleFL = get("Knuckle_FL") local wheelFL = get("Wheel_FL") log("") log("2. ASSEMBLY ROOTS (same root == rigidly welded together):") local function rootName(p) if not p then return "" end local r = p.AssemblyRootPart return r and r:GetFullName():gsub("^.*%.", "") or "" end log(" Body root:", rootName(body)) log(" Hub_FL root:", rootName(hubFL)) log(" Knuckle_FL root:", rootName(knuckleFL)) log(" Wheel_FL root:", rootName(wheelFL)) if hubFL and knuckleFL then local same = hubFL.AssemblyRootPart == knuckleFL.AssemblyRootPart log( " >>> Hub and Knuckle share an assembly:", tostring(same), same and "<-- WELDED, SERVO BYPASSED" or "(ok, servo is free)" ) end -- 3. EVERY joint touching those parts -------------------------------------- log("") log("3. ALL joints touching Hub_FL / Knuckle_FL / Wheel_FL:") local targets = { [hubFL] = "Hub_FL", [knuckleFL] = "Knuckle_FL", [wheelFL] = "Wheel_FL" } for _, d in ipairs(model:GetDescendants()) do local p0, p1, kind if d:IsA("WeldConstraint") then p0, p1, kind = d.Part0, d.Part1, "WeldConstraint" elseif d:IsA("Weld") or d:IsA("Motor6D") then p0, p1, kind = d.Part0, d.Part1, d.ClassName elseif d:IsA("Constraint") then local a0, a1 = d.Attachment0, d.Attachment1 p0 = a0 and a0.Parent or nil p1 = a1 and a1.Parent or nil kind = d.ClassName .. "/" .. d.Name end if p0 and p1 and (targets[p0] or targets[p1]) then log(string.format(" %-28s %s <-> %s", kind, p0.Name, p1.Name)) end end -- 4. Servo runtime state --------------------------------------------------- log("") log("4. SERVO RUNTIME STATE:") for _, d in ipairs(model:GetDescendants()) do if d:IsA("HingeConstraint") and d.Name == Chassis.STEER_SERVO_NAME then log( string.format( " %s Enabled=%s Active=%s Actuator=%s Torque=%.0e Speed=%.1f Limits=%s [%.1f..%.1f] Target=%.1f Current=%.2f", d.Parent.Name, tostring(d.Enabled), tostring(d.Active), d.ActuatorType.Name, d.ServoMaxTorque, d.AngularSpeed, tostring(d.LimitsEnabled), d.LowerAngle, d.UpperAngle, d.TargetAngle, d.CurrentAngle ) ) end end model:Destroy() pad:Destroy() return table.concat(out, "\n")