--[[ diag_shellyaw.luau -- measure the ACTUAL shell orientation on LIVE cars. Why this exists: the previous harness measured Wheel_* positions, which come from the constant Chassis.WHEEL_MOTOR/WHEELBASE_HALF (5.9). Those are identical for every car whether or not any fix applied, so the test passed vacuously. The visible "backwards" car is the COSMETIC SHELL, which is a separate model attached to the chassis -- that is what must be measured. Runs against the running game (Server datamodel), inspecting cars that the real spawn path actually produced. Reports, per live car: bodyLook : chassis Body forward (should be the car's true nose direction) shellLook : shell model forward yawDelta : signed angle between them, degrees. ~0 = aligned, ~180 = shell rendering backwards relative to chassis. Also dumps the shell's own internal cue (mean Z of its F*/R* wheel labels) so we can see WHY the solver chose what it chose, and whether SHELL_YAW_OVERRIDE actually took effect on this car. ]] 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 yawBetween(a: Vector3, b: Vector3): number local av = Vector3.new(a.X, 0, a.Z) local bv = Vector3.new(b.X, 0, b.Z) if av.Magnitude < 1e-4 or bv.Magnitude < 1e-4 then return 0 / 0 end av, bv = av.Unit, bv.Unit local dot = math.clamp(av:Dot(bv), -1, 1) local ang = math.deg(math.acos(dot)) local cross = av:Cross(bv).Y return (cross < 0) and -ang or ang end -- Find live cars anywhere in workspace (folder name varies). local cars = {} for _, d in ipairs(workspace:GetDescendants()) do if d:IsA("Model") and d.Name:match("^Car_") then cars[#cars + 1] = d end end log(string.format("live Car_* models found: %d", #cars)) if #cars == 0 then log("") log("No spawned cars. Drive/spawn a car, or this is the wrong datamodel.") return table.concat(out, "\n") end for _, model in ipairs(cars) do log("") log("=== " .. model.Name .. " ===") local body = model:FindFirstChild("Body") if not body or not body:IsA("BasePart") then log(" no Body part -- cannot establish chassis frame") continue end local bodyLook = body.CFrame.LookVector log(string.format(" bodyLook = (%.2f, %.2f, %.2f)", bodyLook.X, bodyLook.Y, bodyLook.Z)) -- The shell: a Model child that is not the chassis scaffolding. local shell = nil for _, c in ipairs(model:GetChildren()) do if c:IsA("Model") then shell = c break end end if not shell then log(" NO SHELL MODEL as a child -- cosmetic mesh may be parented elsewhere") local kids = {} for _, c in ipairs(model:GetChildren()) do kids[#kids + 1] = c.ClassName .. ":" .. c.Name end log(" children: " .. table.concat(kids, ", ")) continue end local shellLook = shell:GetPivot().LookVector log(string.format(" shell = %s", shell.Name)) log(string.format(" shellLook = (%.2f, %.2f, %.2f)", shellLook.X, shellLook.Y, shellLook.Z)) local delta = yawBetween(bodyLook, shellLook) log( string.format( " yawDelta = %.1f deg --> %s", delta, (math.abs(delta) < 30) and "ALIGNED" or (math.abs(math.abs(delta) - 180) < 30) and "BACKWARDS (180 flip)" or "SKEWED" ) ) -- Why: the shell's own wheel-label cue, in the shell's local frame. local wf = shell:FindFirstChild("Wheels") if wf then local pivotInv = shell:GetPivot():Inverse() local fS, fN, rS, rN = 0, 0, 0, 0 for _, d in ipairs(wf:GetDescendants()) do if d:IsA("BasePart") then local nm = d.Name:upper() local z = (pivotInv * d.CFrame).Z if nm:match("^F") then fS, fN = fS + z, fN + 1 elseif nm:match("^R") then rS, rN = rS + z, rN + 1 end end end if fN > 0 and rN > 0 then log( string.format( " shell wheel cue: frontZ=%.2f rearZ=%.2f (labels say nose at %s)", fS / fN, rS / rN, (fS / fN < rS / rN) and "-Z" or "+Z" ) ) else log(string.format(" shell wheel cue: unusable (F parts=%d, R parts=%d)", fN, rN)) end else log(" shell has no Wheels folder -- no cue available") end end return table.concat(out, "\n")