Files
tokyo-wangan-racing/tools/diag_shellyaw.luau
T
jesse c8dabea53a Daikoku PA: lift mainline over the apron, rebuild both ramps
The PA ring road and the expressway mainline were both at ground level, so
the ring parapet stood INSIDE the carriageway (measured -10.0 studs of
vertical overlap: a wall in the middle of the road).

Reference photos of the real Daikoku Futo PA show the expressway flying
OVER the parking apron on pillars, with the PA at ground level and ramps
climbing up to meet it. Rebuilt to match:

- mainline lifted 38 studs through the junction, eased in/out (max 5.26%
  grade, within real expressway limits)
- PA ring + apron stay at ground level; legacy PARingPillar props removed
- both on/off ramps regraded over the full rise with smoothstep easing,
  segments overlapped 3 studs so wheels can't catch a seam, plus support
  pillars and edge kerbs where the deck is high

Verified in-engine against built parts (tools/diag_daikoku.luau), not just
source: clearance now +28.0 studs, both ramps continuous (max step 5.62,
no gaps). tools/plot_daikoku.py renders the plan/section for comparison.
2026-08-02 15:56:13 -04:00

141 lines
4.1 KiB
Luau

--[[
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")