--[[ diag_nosecue2.luau -- settle which is lying on hatch_junk / hyper_x: the wheel LABELS, or the shell MESH itself. Background: diag_shelllabels.luau showed those two shells label their F axle toward +Z while rx7_fc / silv_s15 / gtr_r34 label F toward -Z. That matches the reported symptom exactly, but it does NOT say which side is wrong -- either the labels are swapped, or the whole mesh is authored facing the other way and the labels are honest. So: gather cues that are INDEPENDENT of the wheel names, from the shell's own parts. Every cue is read off part names/positions in the shell's local frame; none is derived from a constant, and each can come out either way. headlights -- sit at the NOSE taillights -- sit at the TAIL *front* / *rear* named parts (bumpers, grille) -- self-describing seat -- a VehicleSeat/Seat inside the shell looks FORWARD Interpretation: * cues agree with labels -> the MESH is rotated; labels are honest * cues oppose the labels -> the LABELS are swapped; mesh is fine A shell where cues themselves disagree is reported as INCONCLUSIVE rather than being forced into a verdict. ]] local RS = game:GetService("ReplicatedStorage") local SSS = game:GetService("ServerScriptService") local SS = game:GetService("ServerStorage") local TARGETS = { rx7_fc = true, hatch_junk = true, silv_s15 = true, gtr_r34 = true, hyper_x = true } local out = {} local function log(s) table.insert(out, s) end -- Collect the five target shells wherever they live. local shells = {} for _, root in ipairs({ RS, SSS, SS, workspace }) do for _, d in ipairs(root:GetDescendants()) do if d:IsA("Model") and TARGETS[d.Name] and d:FindFirstChild("Wheels") and not shells[d.Name] then shells[d.Name] = d end end end -- Mean local Z of every part whose lowercased name matches a pattern. local function meanZ(shell, pivotInv, patterns) local sum, n = 0, 0 for _, d in ipairs(shell:GetDescendants()) do if d:IsA("BasePart") then local nm = d.Name:lower() for _, pat in ipairs(patterns) do if nm:find(pat, 1, true) then sum += (pivotInv * d.Position).Z n += 1 break end end end end if n == 0 then return nil, 0 end return sum / n, n end local order = { "rx7_fc", "silv_s15", "gtr_r34", "hatch_junk", "hyper_x" } log( string.format( "%-11s %-14s %-14s %-14s %-14s %s", "shell", "labelF.Z", "headlight.Z", "taillight.Z", "seatFwd.Z", "verdict" ) ) log(string.rep("-", 104)) local seen = 0 for _, name in ipairs(order) do local shell = shells[name] if not shell then log(string.format("%-11s NOT FOUND", name)) continue end seen += 1 local pivotInv = shell:GetPivot():Inverse() -- Wheel-label cue: sign of (F centroid - R centroid).Z local wheels = shell:FindFirstChild("Wheels") local fSum, fN, rSum, rN = 0, 0, 0, 0 for _, w in ipairs(wheels: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 z = (pivotInv * (sum / n)).Z local head = w.Name:sub(1, 1):upper() if head == "F" then fSum, fN = fSum + z, fN + 1 elseif head == "R" then rSum, rN = rSum + z, rN + 1 end end end local labelZ = (fN > 0 and rN > 0) and ((fSum / fN) - (rSum / rN)) or nil local headZ, headN = meanZ(shell, pivotInv, { "headlight", "headlamp", "grille", "grill" }) local tailZ, tailN = meanZ(shell, pivotInv, { "taillight", "taillamp", "tailight", "brakelight" }) -- Seat cue: a seat's LookVector, expressed in the SHELL's frame. local seatZ = nil for _, d in ipairs(shell:GetDescendants()) do if d:IsA("VehicleSeat") or d:IsA("Seat") then -- Rotate (not translate) the seat's world look into shell-local axes. seatZ = (shell:GetPivot():Inverse() * (d.CFrame.LookVector + shell:GetPivot().Position)).Z break end end -- Each cue votes for which local Z direction is the NOSE. -- Convention target: nose should be at -Z. local votes = {} if headZ then votes[#votes + 1] = (headZ < 0) and -1 or 1 end if tailZ then votes[#votes + 1] = (tailZ < 0) and 1 or -1 end -- tail opposes nose if seatZ then votes[#votes + 1] = (seatZ < 0) and -1 or 1 end local noseSign = nil if #votes > 0 then local s = 0 for _, v in ipairs(votes) do s += v end if s > 0 then noseSign = 1 elseif s < 0 then noseSign = -1 end end local verdict if not labelZ then verdict = "no F/R labels" elseif #votes == 0 then verdict = "NO INDEPENDENT CUE (no lights/seat found)" elseif noseSign == nil then verdict = "INCONCLUSIVE (cues split)" else local labelSign = (labelZ < 0) and -1 or 1 if labelSign == noseSign then verdict = (noseSign < 0) and "consistent, nose -Z (fine)" or "LABELS HONEST -> MESH is rotated 180" else verdict = "LABELS SWAPPED -> mesh is fine" end end local function fmt(v, n) return v and string.format("%+.1f(%d)", v, n or 0) or "--" end log( string.format( "%-11s %-14s %-14s %-14s %-14s %s", name, labelZ and string.format("%+.1f", labelZ) or "--", fmt(headZ, headN), fmt(tailZ, tailN), seatZ and string.format("%+.2f", seatZ) or "--", verdict ) ) end log("") if seen == 0 then log(">>> NOTHING INSPECTED -- this is a FAILURE, not a pass.") else log(string.format(">>> inspected %d/5 shells", seen)) log(" labelF.Z = (F centroid - R centroid).Z; negative means labels put F toward -Z") log(" headlight.Z = mean local Z of headlight/grille parts (count); nose should be -Z") log(" seatFwd.Z = seat LookVector Z in shell frame; negative = seat faces -Z") end return table.concat(out, "\n")