--[[ diag_centrefix.luau -- test whether centreFix is applied in the wrong order. The build does: shell:PivotTo(body.CFrame * heightCF * CFrame.Angles(0,yaw,0) * centreFix) centreFix is a translation derived from the shell's CURRENT (unrotated) AABB centre. Placing it to the RIGHT of CFrame.Angles means it is expressed in the ROTATED frame, so a 180-degree yaw negates its X and Z. For the three shells with yaw ~= 0 this is harmless. For hatch_junk and hyper_x (yaw 180) it moves the shell by -2 * centreLocal instead of cancelling the offset. This measures the resulting AABB-centre error for BOTH orderings, per shell. The numbers can come out either way -- if the current ordering is fine, both errors are ~0 and the hypothesis is dead. ]] local RS = game:GetService("ReplicatedStorage") local SSS = game:GetService("ServerScriptService") local out = {} local function log(s) table.insert(out, s) end local TARGETS = { "rx7_fc", "silv_s15", "gtr_r34", "hatch_junk", "hyper_x" } local shells = {} for _, root in ipairs({ RS, SSS, game:GetService("ServerStorage"), workspace }) do for _, d in ipairs(root:GetDescendants()) do if d:IsA("Model") and d:FindFirstChild("Wheels") and not shells[d.Name] then for _, t in ipairs(TARGETS) do if d.Name == t then shells[d.Name] = d end end end end end -- Replicate trueAABB: min/max corner over all BaseParts, world space. local function trueAABB(model) local lo, hi for _, d in ipairs(model:GetDescendants()) do if d:IsA("BasePart") then local p = d.Position lo = lo and Vector3.new(math.min(lo.X, p.X), math.min(lo.Y, p.Y), math.min(lo.Z, p.Z)) or p hi = hi and Vector3.new(math.max(hi.X, p.X), math.max(hi.Y, p.Y), math.max(hi.Z, p.Z)) or p end end return lo, hi end log(string.format("%-11s %-10s %-22s %-14s %s", "shell", "yaw", "centreLocal(x,z)", "CURRENT err", "FIXED err")) log(string.rep("-", 82)) for _, name in ipairs(TARGETS) do local shell = shells[name] if not shell then log(string.format("%-11s NOT FOUND", name)) continue end -- yaw the solver would compute local pivotInv = shell:GetPivot():Inverse() local wf = shell:FindFirstChild("Wheels") local fSum, fN, rSum, rN = Vector3.zero, 0, Vector3.zero, 0 for _, w in ipairs(wf:GetChildren()) do local sum, n = Vector3.zero, 0 if w:IsA("BasePart") then sum, n = w.Position, 1 end for _, d in ipairs(w:GetDescendants()) do if d:IsA("BasePart") then sum, n = sum + d.Position, n + 1 end end if n > 0 then local lp = pivotInv * (sum / n) local h = w.Name:sub(1, 1):upper() if h == "F" then fSum, fN = fSum + lp, fN + 1 elseif h == "R" then rSum, rN = rSum + lp, rN + 1 end end end local yaw = 0 if fN > 0 and rN > 0 then local nose = (fSum / fN) - (rSum / rN) if math.abs(nose.X) + math.abs(nose.Z) > 0.05 then yaw = -math.atan2(nose.X, -nose.Z) end end local lo2, hi2 = trueAABB(shell) local centreLocal = shell:GetPivot():Inverse() * ((lo2 + hi2) / 2) local centreFix = CFrame.new(-centreLocal.X, 0, -centreLocal.Z) -- Simulate both orderings against an identity body at the origin. local body = CFrame.new() local rot = CFrame.Angles(0, yaw, 0) -- Where does the shell's AABB centre land, relative to the body axis? -- Model the centre as a point at centreLocal in the shell's pivot frame. local centrePoint = CFrame.new(centreLocal.X, 0, centreLocal.Z) local currentCF = body * rot * centreFix local fixedCF = body * centreFix * rot -- translation applied BEFORE rotation local curCentre = (currentCF * centrePoint).Position local fixCentre = (fixedCF * centrePoint).Position local curErr = math.sqrt(curCentre.X ^ 2 + curCentre.Z ^ 2) local fixErr = math.sqrt(fixCentre.X ^ 2 + fixCentre.Z ^ 2) log( string.format( "%-11s %-10s (%+.2f,%+.2f)%-8s %-14s %s", name, string.format("%+.0f deg", math.deg(yaw)), centreLocal.X, centreLocal.Z, "", string.format("%.3f", curErr), string.format("%.3f", fixErr) ) ) end log("") log("err = how far the shell's AABB centre sits from the chassis axis (studs).") log("A large CURRENT err on the 180-degree shells with a near-zero FIXED err") log("confirms the ordering bug. If both columns match, the hypothesis is WRONG") log("and the offset is not the cause of the reversal.") return table.concat(out, "\n")