--[[ diag_wheelroot2.luau -- same investigation as diag_wheelroot, but wrapped so a failure reports WHERE. The first attempt died with a bare "attempt to call a nil value" and no line, which tells us nothing; both _buildForDiag (CarManager:988) and Chassis.tuningFor (Chassis:158) are confirmed to exist, so the nil is something else in the script. Runs each stage under pcall + traceback and prints progress markers, so the last marker printed localises the fault even if the traceback is swallowed by the executor. ]] 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 function stage(name, fn) local ok, err = xpcall(fn, function(e) return tostring(e) .. "\n" .. debug.traceback("", 2) end) if not ok then log("!! FAILED at stage: " .. name) log(err) return false end log(" ok: " .. name) return true end local Shared, Cars, Chassis, CarManager, cm stage("require shared", function() Shared = ReplicatedStorage:FindFirstChild("Shared") Cars = require(Shared.Cars) Chassis = require(Shared.Chassis) end) stage("clone+require CarManager", function() local orig = ServerScriptService:FindFirstChild("Server", true):FindFirstChild("CarManager") cm = orig:Clone() cm.Parent = orig.Parent CarManager = require(cm) assert(typeof(CarManager._buildForDiag) == "function", "_buildForDiag is " .. typeof(CarManager._buildForDiag)) assert(typeof(Chassis.tuningFor) == "function", "tuningFor is " .. typeof(Chassis.tuningFor)) assert(typeof(Cars.Get) == "function", "Cars.Get is " .. typeof(Cars.Get)) end) -- ------------------------------------------------- stage 1: template roots stage("template root pick", function() log("") log("=== TEMPLATE: what does line 589 pick as root? ===") log(string.format("%-12s %-5s %-18s %8s %7s", "car", "grp", "root picked", "offX", "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) local parts = {} if w:IsA("BasePart") then parts[1] = w end for _, d in ipairs(w:GetDescendants()) do if d:IsA("BasePart") then parts[#parts + 1] = d end end if root and #parts > 0 then local lo, hi = parts[1].Position, parts[1].Position for _, p in ipairs(parts) do lo = Vector3.new( math.min(lo.X, p.Position.X), math.min(lo.Y, p.Position.Y), math.min(lo.Z, p.Position.Z) ) hi = Vector3.new( math.max(hi.X, p.Position.X), math.max(hi.Y, p.Position.Y), math.max(hi.Z, p.Position.Z) ) end local centre = (lo + hi) / 2 log( string.format( "%-12s %-5s %-18s %8.3f %7d", t.Name, w.Name, root.Name, math.abs(root.Position.X - centre.X), #parts ) ) end end end end end) -- ------------------------------------------------- stage 2: built cars stage("built car check", function() log("") log("=== BUILT: tyre offset from its physics cylinder + stray welds ===") log(string.format("%-12s %-5s %9s %s", "car", "grp", "tyreErrX", "welds to OUTSIDE the group")) 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 and body then local inv = body.CFrame:Inverse() for _, w in ipairs(wg:GetChildren()) do local physName = "Wheel_" .. w.Name:upper() local phys = m:FindFirstChild(physName) 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 local physX = (inv * phys.Position).X local best, bestV = group[1], -1 for _, p in ipairs(group) do local v = p.Size.X * p.Size.Y * p.Size.Z if v > bestV then best, bestV = p, v end end local errX = (inv * best.Position).X - physX 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" if oname ~= physName then bad[#bad + 1] = oname 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 end) if cm then cm:Destroy() end return table.concat(out, "\n")