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

137 lines
4.3 KiB
Luau

--[[
diag_centrefix.luau -- test whether centreFix is applied in the wrong order.
The build does:
shell:PivotTo(body.CFrame * heightCF * CFrame.Angles(0,yaw,0) * centreFix)
centreFix is a translation derived from the shell's CURRENT (unrotated)
AABB centre. Placing it to the RIGHT of CFrame.Angles means it is expressed
in the ROTATED frame, so a 180-degree yaw negates its X and Z. For the three
shells with yaw ~= 0 this is harmless. For hatch_junk and hyper_x (yaw 180)
it moves the shell by -2 * centreLocal instead of cancelling the offset.
This measures the resulting AABB-centre error for BOTH orderings, per shell.
The numbers can come out either way -- if the current ordering is fine, both
errors are ~0 and the hypothesis is dead.
]]
local RS = game:GetService("ReplicatedStorage")
local SSS = game:GetService("ServerScriptService")
local out = {}
local function log(s)
table.insert(out, s)
end
local TARGETS = { "rx7_fc", "silv_s15", "gtr_r34", "hatch_junk", "hyper_x" }
local shells = {}
for _, root in ipairs({ RS, SSS, game:GetService("ServerStorage"), workspace }) do
for _, d in ipairs(root:GetDescendants()) do
if d:IsA("Model") and d:FindFirstChild("Wheels") and not shells[d.Name] then
for _, t in ipairs(TARGETS) do
if d.Name == t then
shells[d.Name] = d
end
end
end
end
end
-- Replicate trueAABB: min/max corner over all BaseParts, world space.
local function trueAABB(model)
local lo, hi
for _, d in ipairs(model:GetDescendants()) do
if d:IsA("BasePart") then
local p = d.Position
lo = lo and Vector3.new(math.min(lo.X, p.X), math.min(lo.Y, p.Y), math.min(lo.Z, p.Z)) or p
hi = hi and Vector3.new(math.max(hi.X, p.X), math.max(hi.Y, p.Y), math.max(hi.Z, p.Z)) or p
end
end
return lo, hi
end
log(string.format("%-11s %-10s %-22s %-14s %s", "shell", "yaw", "centreLocal(x,z)", "CURRENT err", "FIXED err"))
log(string.rep("-", 82))
for _, name in ipairs(TARGETS) do
local shell = shells[name]
if not shell then
log(string.format("%-11s NOT FOUND", name))
continue
end
-- yaw the solver would compute
local pivotInv = shell:GetPivot():Inverse()
local wf = shell:FindFirstChild("Wheels")
local fSum, fN, rSum, rN = Vector3.zero, 0, Vector3.zero, 0
for _, w in ipairs(wf: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 lp = pivotInv * (sum / n)
local h = w.Name:sub(1, 1):upper()
if h == "F" then
fSum, fN = fSum + lp, fN + 1
elseif h == "R" then
rSum, rN = rSum + lp, rN + 1
end
end
end
local yaw = 0
if fN > 0 and rN > 0 then
local nose = (fSum / fN) - (rSum / rN)
if math.abs(nose.X) + math.abs(nose.Z) > 0.05 then
yaw = -math.atan2(nose.X, -nose.Z)
end
end
local lo2, hi2 = trueAABB(shell)
local centreLocal = shell:GetPivot():Inverse() * ((lo2 + hi2) / 2)
local centreFix = CFrame.new(-centreLocal.X, 0, -centreLocal.Z)
-- Simulate both orderings against an identity body at the origin.
local body = CFrame.new()
local rot = CFrame.Angles(0, yaw, 0)
-- Where does the shell's AABB centre land, relative to the body axis?
-- Model the centre as a point at centreLocal in the shell's pivot frame.
local centrePoint = CFrame.new(centreLocal.X, 0, centreLocal.Z)
local currentCF = body * rot * centreFix
local fixedCF = body * centreFix * rot -- translation applied BEFORE rotation
local curCentre = (currentCF * centrePoint).Position
local fixCentre = (fixedCF * centrePoint).Position
local curErr = math.sqrt(curCentre.X ^ 2 + curCentre.Z ^ 2)
local fixErr = math.sqrt(fixCentre.X ^ 2 + fixCentre.Z ^ 2)
log(
string.format(
"%-11s %-10s (%+.2f,%+.2f)%-8s %-14s %s",
name,
string.format("%+.0f deg", math.deg(yaw)),
centreLocal.X,
centreLocal.Z,
"",
string.format("%.3f", curErr),
string.format("%.3f", fixErr)
)
)
end
log("")
log("err = how far the shell's AABB centre sits from the chassis axis (studs).")
log("A large CURRENT err on the 180-degree shells with a near-zero FIXED err")
log("confirms the ordering bug. If both columns match, the hypothesis is WRONG")
log("and the offset is not the cause of the reversal.")
return table.concat(out, "\n")