--[[ diag_wheelroot.luau -- two suspected bugs in the shell wheel re-hang (CarManager ~line 585-641). (1) ROOT CHOICE. Line 589 picks root = w:IsA("BasePart") and w or w:FindFirstChildWhichIsA("BasePart", true) For a wheel group that is a Model, FindFirstChildWhichIsA returns an ARBITRARY first BasePart -- a rotor, caliper or hub, not necessarily the tyre. Line 621 then does delta = phys.Position - root.Position and shifts the whole group, making THAT part concentric with the axle. If root is not the tyre centre, the visible tyre is displaced by the root-to-tyre offset. User reports rx7_fc wheels "sticking out from the side of the car", which is this signature. (2) SURVIVING OUTSIDE WELDS. Line 605 only scans shell:GetDescendants() for welds to cut. Any weld parented OUTSIDE the shell (on the model, on body, etc.) that ties a wheel part to the bodywork survives, which would rigidly glue the wheel to the chassis and pin the knuckle -- the measured steering lock. Reports, per wheel group: which part was chosen as root, how far that root sits from the group's true centre, and any surviving weld from the group to a part outside it. ]] 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 local Shared = ReplicatedStorage:FindFirstChild("Shared") local Cars = require(Shared.Cars) local Chassis = require(Shared.Chassis) local orig = ServerScriptService:FindFirstChild("Server", true):FindFirstChild("CarManager") local cm = orig:Clone() cm.Parent = orig.Parent local CarManager = require(cm) -- ---------------------------------------------------------------- template log("=== TEMPLATE wheel groups: what does line 589 pick as root? ===") log(string.format("%-12s %-5s %-16s %8s %8s", "car", "grp", "root picked", "off|X|", "nParts")) for _, t in ipairs(ReplicatedStorage.CarModels:GetChildren()) do local wg = t:FindFirstChild("Wheels") if wg then for _, w in ipairs(wg:GetChildren()) do local root = w:IsA("BasePart") and w or w:FindFirstChildWhichIsA("BasePart", true) -- true centre of the group = AABB centre of all its parts local lo = Vector3.new(math.huge, math.huge, math.huge) local hi = -lo local n = 0 if w:IsA("BasePart") then lo, hi, n = w.Position, w.Position, 1 end for _, d in ipairs(w:GetDescendants()) do if d:IsA("BasePart") then lo = Vector3.new( math.min(lo.X, d.Position.X), math.min(lo.Y, d.Position.Y), math.min(lo.Z, d.Position.Z) ) hi = Vector3.new( math.max(hi.X, d.Position.X), math.max(hi.Y, d.Position.Y), math.max(hi.Z, d.Position.Z) ) n += 1 end end if root and n > 0 then local centre = (lo + hi) / 2 local off = root.Position - centre log(string.format("%-12s %-5s %-16s %8.3f %8d", t.Name, w.Name, root.Name, math.abs(off.X), n)) end end end end -- ---------------------------------------------------------------- built log("") log("=== BUILT cars: tyre alignment + surviving outside welds ===") log("tyreErrX = how far the widest shell wheel part sits from its physics") log("cylinder along the car's lateral axis. Should be ~0 on every corner.") log("") log(string.format("%-12s %-5s %9s %s", "car", "grp", "tyreErrX", "welds tying group to OUTSIDE")) for _, t in ipairs(ReplicatedStorage.CarModels:GetChildren()) do local car = Cars.Get(t.Name) if car then local m = CarManager._buildForDiag(car, Chassis.tuningFor(car)) m.Parent = workspace m:PivotTo(CFrame.new(0, 5010, 0)) for _ = 1, 4 do RunService.Heartbeat:Wait() end local body = m.PrimaryPart local shell = m:FindFirstChild("Shell") local wg = shell and shell:FindFirstChild("Wheels") if wg then local inv = body.CFrame:Inverse() for _, w in ipairs(wg:GetChildren()) do local phys = m:FindFirstChild("Wheel_" .. w.Name:upper()) local group, inGroup = {}, {} if w:IsA("BasePart") then group[1], inGroup[w] = w, true end for _, d in ipairs(w:GetDescendants()) do if d:IsA("BasePart") then group[#group + 1] = d inGroup[d] = true end end if phys and #group > 0 then -- widest part = the tyre; compare its |X| to the cylinder's local physX = (inv * phys.Position).X local best, bestW = nil, -1 for _, p in ipairs(group) do local vol = p.Size.X * p.Size.Y * p.Size.Z if vol > bestW then best, bestW = p, vol end end local errX = (inv * best.Position).X - physX -- any weld from this group to something outside it, -- searched across the WHOLE model, not just the shell local bad = {} for _, c in ipairs(m:GetDescendants()) do if c:IsA("WeldConstraint") or c:IsA("Weld") then local a, b = c.Part0, c.Part1 local aIn = a ~= nil and inGroup[a] or false local bIn = b ~= nil and inGroup[b] or false if (aIn or bIn) and not (aIn and bIn) then local other = aIn and b or a local oname = other and other.Name or "nil" -- weld to its own physics cylinder is CORRECT if oname ~= ("Wheel_" .. w.Name:upper()) then bad[#bad + 1] = oname .. "(" .. c.Parent.Name .. ")" end end end end log( string.format( "%-12s %-5s %9.3f %s", t.Name, w.Name, errX, #bad > 0 and table.concat(bad, ", ") or "-none-" ) ) end end end m:Destroy() end end cm:Destroy() return table.concat(out, "\n")