Files
tokyo-wangan-racing/tools/diag_forward.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

183 lines
5.0 KiB
Luau

--[[
diag_forward.luau -- settle which end of the car is the NOSE.
Contradiction to resolve: the chassis places its FRONT axle at local -Z
(WHEELBASE_HALF = 5.9, frontZ = -5.90) but Body.LookVector is +Z. Those
disagree: the part the code calls "front" is BEHIND the direction the body
looks. One of the two conventions is wrong, and the shell is being aligned
to the wrong one -- which is why the car renders backwards even though
shell and body agree (yawDelta 0).
Ground truth used here, in order of reliability:
1. VehicleSeat.LookVector -- in Roblox a seated driver faces the seat's
+Z look direction and positive throttle drives that way. This is the
direction the car ACTUALLY moves, so it defines "forward" for gameplay.
2. Steering: which wheels have a steering constraint. Only the FRONT
wheels steer, so a steered wheel's local Z tells us where the front is.
3. Shell art extents: the visual mesh bounding box relative to its pivot.
Reports each independently so a disagreement is visible rather than assumed.
]]
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 cars = {}
for _, d in ipairs(workspace:GetDescendants()) do
if d:IsA("Model") and d.Name:match("^Car_") then
cars[#cars + 1] = d
end
end
if #cars == 0 then
return "No live Car_* models."
end
for _, model in ipairs(cars) do
log("=== " .. model.Name .. " ===")
local body = model:FindFirstChild("Body")
if not (body and body:IsA("BasePart")) then
log(" no Body part")
continue
end
local bodyCF = body.CFrame
local inv = bodyCF:Inverse()
log(
string.format(
" bodyLook (world) = (%.2f, %.2f, %.2f)",
bodyCF.LookVector.X,
bodyCF.LookVector.Y,
bodyCF.LookVector.Z
)
)
-- 1. VehicleSeat: the direction the car actually drives.
local seat = nil
for _, d in ipairs(model:GetDescendants()) do
if d:IsA("VehicleSeat") then
seat = d
break
end
end
if seat then
local seatLocalZ = (inv * seat.CFrame).Z
local dot = bodyCF.LookVector:Dot(seat.CFrame.LookVector)
log(
string.format(
" [1] VehicleSeat: localZ=%.2f, seatLook.dot(bodyLook)=%.2f -> seat faces %s body",
seatLocalZ,
dot,
(dot > 0) and "SAME as" or "OPPOSITE to"
)
)
log(string.format(" => car drives toward body %s Z", (dot > 0) and "+" or "-"))
else
log(" [1] no VehicleSeat found")
end
-- 2. Which wheels steer? Only the front axle has a steering servo.
local steered, plain = {}, {}
for _, d in ipairs(model:GetDescendants()) do
if d:IsA("BasePart") and d.Name:match("^Wheel_") then
local z = (inv * d.CFrame).Z
local hasServo = false
for _, c in ipairs(d:GetDescendants()) do
if c:IsA("HingeConstraint") and c.ActuatorType == Enum.ActuatorType.Servo then
hasServo = true
break
end
end
-- servo may live on the attachment's parent chain instead
if not hasServo and d.Parent then
for _, c in ipairs(d.Parent:GetChildren()) do
if c:IsA("HingeConstraint") and c.ActuatorType == Enum.ActuatorType.Servo then
local a1, a0 = c.Attachment1, c.Attachment0
if (a1 and a1:IsDescendantOf(d)) or (a0 and a0:IsDescendantOf(d)) then
hasServo = true
break
end
end
end
end
table.insert(hasServo and steered or plain, { name = d.Name, z = z })
end
end
local function meanZ(t)
if #t == 0 then
return 0 / 0
end
local s = 0
for _, v in ipairs(t) do
s = s + v.z
end
return s / #t
end
local sz, pz = meanZ(steered), meanZ(plain)
log(string.format(" [2] steered wheels=%d meanZ=%.2f | unsteered=%d meanZ=%.2f", #steered, sz, #plain, pz))
if #steered > 0 and #plain > 0 then
log(string.format(" => FRONT (steered) axle is at body %s Z", (sz < pz) and "-" or "+"))
end
for _, v in ipairs(steered) do
log(string.format(" steered: %-14s z=%.2f", v.name, v.z))
end
for _, v in ipairs(plain) do
log(string.format(" plain : %-14s z=%.2f", v.name, v.z))
end
-- 3. Shell art: where is the visual mass, and is it symmetric?
local shell = nil
for _, c in ipairs(model:GetChildren()) do
if c:IsA("Model") then
shell = c
break
end
end
if shell then
local cf, size = shell:GetBoundingBox()
local localCentre = (inv * cf.Position)
log(
string.format(
" [3] shell bbox size=(%.1f, %.1f, %.1f) centre localZ=%.2f",
size.X,
size.Y,
size.Z,
localCentre.Z
)
)
-- named bodywork gives an independent nose/tail cue
for _, d in ipairs(shell:GetDescendants()) do
local n = d.Name:lower()
if
d:IsA("BasePart")
and (
n:match("hood")
or n:match("bonnet")
or n:match("boot")
or n:match("trunk")
or n:match("spoiler")
or n:match("windscreen")
or n:match("bumper")
)
then
log(string.format(" %-16s localZ=%.2f", d.Name, (inv * d.CFrame).Z))
end
end
else
log(" [3] no shell model child")
end
log("")
end
return table.concat(out, "\n")