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.
145 lines
4.4 KiB
Luau
145 lines
4.4 KiB
Luau
--[[
|
|
diag_solveryaw.luau -- does the shell yaw solver actually fire for
|
|
hatch_junk / hyper_x, and what does it produce?
|
|
|
|
Hypothesis under test: the solver derives "nose" from the F/R wheel labels,
|
|
so for a shell whose F wheels sit at +Z it should compute a 180-degree yaw
|
|
and rotate the shell to face -Z. If that is happening, those two cars would
|
|
look CORRECT -- but they look backwards. So either the solver is not being
|
|
reached, or it computes something other than 180.
|
|
|
|
CACHE GUARD FIRST. require() caches for the whole Studio session, so a
|
|
build test can silently run OLD code. Before trusting anything below, the
|
|
script re-reads Chassis.DRIVE_SIGN via require() and compares it to the
|
|
.Source text. If they disagree, the cache is stale and the script ABORTS
|
|
rather than reporting a number that means nothing.
|
|
]]
|
|
|
|
local RS = game:GetService("ReplicatedStorage")
|
|
local SSS = game:GetService("ServerScriptService")
|
|
|
|
local out = {}
|
|
local function log(s)
|
|
table.insert(out, s)
|
|
end
|
|
|
|
local function find(name)
|
|
for _, root in ipairs({ SSS, RS, game:GetService("ServerStorage"), workspace }) do
|
|
local m = root:FindFirstChild(name, true)
|
|
if m and m:IsA("ModuleScript") then
|
|
return m
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- ---- cache guard -------------------------------------------------------
|
|
local chMod = find("Chassis")
|
|
if not chMod then
|
|
return "FATAL: Chassis not found"
|
|
end
|
|
local srcHasNeg1 = chMod.Source:find("DRIVE_SIGN = -1", 1, true) ~= nil
|
|
local okReq, Chassis = pcall(require, chMod)
|
|
if not okReq then
|
|
return "FATAL: require(Chassis) failed: " .. tostring(Chassis)
|
|
end
|
|
local liveNeg1 = (Chassis.DRIVE_SIGN == -1)
|
|
|
|
log(
|
|
string.format(
|
|
"cache guard: source DRIVE_SIGN=-1? %s require() DRIVE_SIGN=%s",
|
|
tostring(srcHasNeg1),
|
|
tostring(Chassis.DRIVE_SIGN)
|
|
)
|
|
)
|
|
if srcHasNeg1 ~= liveNeg1 then
|
|
log("")
|
|
log(">>> ABORT: require() cache disagrees with source. Any build measured now")
|
|
log(" would use STALE code. Restart Studio (or reopen the place) and re-run.")
|
|
return table.concat(out, "\n")
|
|
end
|
|
log("cache guard: PASS (require matches source)")
|
|
log("")
|
|
|
|
-- ---- what does the solver compute? -------------------------------------
|
|
-- Recompute the solver's own input the same way the source does: nose is the
|
|
-- F-centroid minus R-centroid, in shell-local space; yaw = -atan2(nose.X, -nose.Z).
|
|
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
|
|
|
|
log(string.format("%-11s %-12s %-14s %s", "shell", "nose.Z", "solver yaw", "meaning"))
|
|
log(string.rep("-", 74))
|
|
|
|
for _, name in ipairs(TARGETS) do
|
|
local shell = shells[name]
|
|
if not shell then
|
|
log(string.format("%-11s NOT FOUND", name))
|
|
continue
|
|
end
|
|
local pivotInv = shell:GetPivot():Inverse()
|
|
local wheels = shell:FindFirstChild("Wheels")
|
|
local fSum, fN, rSum, rN = Vector3.zero, 0, Vector3.zero, 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 lp = pivotInv * (sum / n)
|
|
local head = w.Name:sub(1, 1):upper()
|
|
if head == "F" then
|
|
fSum, fN = fSum + lp, fN + 1
|
|
elseif head == "R" then
|
|
rSum, rN = rSum + lp, rN + 1
|
|
end
|
|
end
|
|
end
|
|
if fN == 0 or rN == 0 then
|
|
log(string.format("%-11s no F/R labels", name))
|
|
continue
|
|
end
|
|
local nose = (fSum / fN) - (rSum / rN)
|
|
local yaw = -math.atan2(nose.X, -nose.Z)
|
|
local deg = math.deg(yaw)
|
|
local meaning
|
|
if math.abs(deg) < 15 then
|
|
meaning = "no rotation applied"
|
|
elseif math.abs(math.abs(deg) - 180) < 15 then
|
|
meaning = "180 flip -> solver DOES correct this shell"
|
|
else
|
|
meaning = "partial rotation"
|
|
end
|
|
log(
|
|
string.format(
|
|
"%-11s %-12s %-14s %s",
|
|
name,
|
|
string.format("%+.1f", nose.Z),
|
|
string.format("%+.1f deg", deg),
|
|
meaning
|
|
)
|
|
)
|
|
end
|
|
|
|
log("")
|
|
log("If the two reported-backwards shells show a 180 flip here, the solver is")
|
|
log("already correcting them in theory -- so the fault is that this code path")
|
|
log("is not reached at build time, NOT the maths. That distinction decides the fix.")
|
|
|
|
return table.concat(out, "\n")
|