Files
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

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