--[[ diag_shellgeom.luau -- measure the SHELL BODYWORK directly. Two prior attempts both returned dZ = -11.80 for every car, which is the chassis wheelbase: the "shell wheels" being matched are in fact the chassis Wheel_* parts, so those runs proved nothing about shell facing. Stop relying on wheel names. Instead: 1. Dump the real child structure of one shell so we can see what is actually in there after the build. 2. Measure asymmetric BODYWORK mass: a car's cabin/rear sits behind its nose, so the shell's own parts are not symmetric front-to-back. Compare the shell part centroid against the shell AABB centre along body-local Z -- the sign flips when the shell is rotated 180. 3. Cross-check against the same measurement on the raw TEMPLATE, where diag_shellyaw2 already established ground truth (hatch_junk and hyper_x authored nose-toward +Z, the other three nose-toward -Z). If the built car's sign matches the template's, the flip never happened. ]] 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 cloneMod = orig:Clone() cloneMod.Parent = orig.Parent local CarManager = require(cloneMod) -- 1. What is actually inside a built shell? local car0 = Cars.Get("hyper_x") local m0 = CarManager._buildForDiag(car0, Chassis.tuningFor(car0)) m0.Parent = workspace m0:PivotTo(CFrame.new(0, 5010, 0)) local shell0 = m0:FindFirstChild("Shell") log("=== STRUCTURE of built hyper_x Shell ===") if shell0 then for _, c in ipairs(shell0:GetChildren()) do local extra = "" if c:IsA("Folder") or c:IsA("Model") then local kids = {} for _, k in ipairs(c:GetChildren()) do kids[#kids + 1] = k.Name end extra = " {" .. table.concat(kids, ",") .. "}" end log(string.format(" %-16s %s%s", c.Name, c.ClassName, extra)) end else log(" ") end m0:Destroy() log("") -- 2/3. Bodywork asymmetry, template vs built. local function asymmetry(root: Instance, frame: CFrame): number? -- Volume-weighted centroid of bodywork, minus AABB centre, along local Z. local inv = frame:Inverse() local sum, wsum = 0, 0 local lo, hi = math.huge, -math.huge for _, p in ipairs(root:GetDescendants()) do if p:IsA("BasePart") then local n = p.Name:lower() -- exclude wheels/tyres of any naming convention if not (n:find("wheel") or n:find("tyre") or n:find("tire") or n:find("rim")) then local z = (inv * p.Position).Z local vol = p.Size.X * p.Size.Y * p.Size.Z sum += z * vol wsum += vol lo = math.min(lo, z) hi = math.max(hi, z) end end end if wsum <= 0 then return nil end local centroid = sum / wsum local aabbCentre = (lo + hi) / 2 return centroid - aabbCentre end log("=== BODYWORK ASYMMETRY along local Z (centroid - aabb centre) ===") log("A car has more mass toward the REAR (cabin/boot), so on a correctly") log("faced shell this should be POSITIVE (mass behind centre, nose at -Z).") log("") log(string.format("%-14s %14s %14s %s", "car", "template", "built", "verdict")) for _, template in ipairs(ReplicatedStorage.CarModels:GetChildren()) do local id = template.Name local tA = asymmetry(template, template:GetPivot()) local car = Cars.Get(id) local bA = nil if car then local m = CarManager._buildForDiag(car, Chassis.tuningFor(car)) m.Parent = workspace m:PivotTo(CFrame.new(0, 5010, 0)) for _ = 1, 3 do RunService.Heartbeat:Wait() end local sh = m:FindFirstChild("Shell") if sh then bA = asymmetry(sh, m.PrimaryPart.CFrame) end m:Destroy() end local verdict = "?" if tA and bA then local flipped = (tA > 0) ~= (bA > 0) verdict = flipped and "rotated by build" or "NOT rotated" end log( string.format( "%-14s %14s %14s %s", id, tA and string.format("%+.3f", tA) or "n/a", bA and string.format("%+.3f", bA) or "n/a", verdict ) ) end cloneMod:Destroy() return table.concat(out, "\n")