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

189 lines
5.3 KiB
Luau

--[[
diag_steer3.luau -- WHAT is holding the knuckle rigid?
diag_steer2 proved the servo will not rotate even hoisted with zero tyre
load and ServoMaxTorque up to 1e9, with both hinge axes correctly +Y.
That means the knuckle is rigidly bonded to the hub by something other
than the servo.
Decisive test: parts joined by a WELD share an AssemblyRootPart. Parts
joined by a moving CONSTRAINT do not. So if Hub_FL and Knuckle_FL report
the same AssemblyRootPart, a weld is bypassing the servo.
Also builds an isolated 2-part servo to confirm the engine/API itself
behaves, ruling out a bad assumption about HingeConstraint.
]]
local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
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
-- 1. ISOLATED SERVO --------------------------------------------------------
-- Two free parts, one servo, nothing else. If this turns, the API is fine
-- and the fault is in how the car is assembled.
do
local folder = Instance.new("Folder")
folder.Name = "IsolatedServoTest"
folder.Parent = workspace
local base = Instance.new("Part")
base.Size = Vector3.new(4, 1, 4)
base.Position = Vector3.new(0, 5100, 0)
base.Anchored = true
base.Parent = folder
local arm = Instance.new("Part")
arm.Size = Vector3.new(1, 1, 6)
arm.Position = Vector3.new(0, 5102, 0)
arm.Parent = folder
local VERT = CFrame.Angles(0, 0, math.rad(90))
local a0 = Instance.new("Attachment")
a0.CFrame = VERT
a0.Parent = base
local a1 = Instance.new("Attachment")
a1.CFrame = VERT
a1.Parent = arm
local s = Instance.new("HingeConstraint")
s.Attachment0 = a0
s.Attachment1 = a1
s.ActuatorType = Enum.ActuatorType.Servo
s.ServoMaxTorque = 1e6
s.AngularSpeed = 6
s.LimitsEnabled = true
s.LowerAngle = -35
s.UpperAngle = 35
s.TargetAngle = -30
s.Parent = base
for _ = 1, 90 do
RunService.Heartbeat:Wait()
end
log(
string.format(
"1. ISOLATED servo: target -30 -> CurrentAngle %.2f (%s)",
s.CurrentAngle,
math.abs(s.CurrentAngle + 30) < 5 and "WORKS" or "BROKEN"
)
)
folder:Destroy()
end
-- 2. ASSEMBLY TOPOLOGY OF A REAL CAR ---------------------------------------
local Shared = ReplicatedStorage:FindFirstChild("Shared")
local Cars = require(Shared.Cars)
local Chassis = require(Shared.Chassis)
local Server = ServerScriptService:FindFirstChild("Server", true)
local CarManager = require(Server.CarManager)
local pad = Instance.new("Part")
pad.Size = Vector3.new(2000, 8, 2000)
pad.Position = Vector3.new(0, 5000, 0)
pad.Anchored = true
pad.Parent = workspace
local car = Cars.Get("gtr_r34")
local tuning = Chassis.tuningFor(car)
local model = CarManager._buildForDiag(car, tuning)
model.Parent = workspace
model:PivotTo(CFrame.new(0, 5007, 0))
for _ = 1, 60 do
RunService.Heartbeat:Wait()
end
local function get(name)
for _, d in ipairs(model:GetDescendants()) do
if d:IsA("BasePart") and d.Name == name then
return d
end
end
return nil
end
local body = model.PrimaryPart
local hubFL = get("Hub_FL")
local knuckleFL = get("Knuckle_FL")
local wheelFL = get("Wheel_FL")
log("")
log("2. ASSEMBLY ROOTS (same root == rigidly welded together):")
local function rootName(p)
if not p then
return "<missing>"
end
local r = p.AssemblyRootPart
return r and r:GetFullName():gsub("^.*%.", "") or "<none>"
end
log(" Body root:", rootName(body))
log(" Hub_FL root:", rootName(hubFL))
log(" Knuckle_FL root:", rootName(knuckleFL))
log(" Wheel_FL root:", rootName(wheelFL))
if hubFL and knuckleFL then
local same = hubFL.AssemblyRootPart == knuckleFL.AssemblyRootPart
log(
" >>> Hub and Knuckle share an assembly:",
tostring(same),
same and "<-- WELDED, SERVO BYPASSED" or "(ok, servo is free)"
)
end
-- 3. EVERY joint touching those parts --------------------------------------
log("")
log("3. ALL joints touching Hub_FL / Knuckle_FL / Wheel_FL:")
local targets = { [hubFL] = "Hub_FL", [knuckleFL] = "Knuckle_FL", [wheelFL] = "Wheel_FL" }
for _, d in ipairs(model:GetDescendants()) do
local p0, p1, kind
if d:IsA("WeldConstraint") then
p0, p1, kind = d.Part0, d.Part1, "WeldConstraint"
elseif d:IsA("Weld") or d:IsA("Motor6D") then
p0, p1, kind = d.Part0, d.Part1, d.ClassName
elseif d:IsA("Constraint") then
local a0, a1 = d.Attachment0, d.Attachment1
p0 = a0 and a0.Parent or nil
p1 = a1 and a1.Parent or nil
kind = d.ClassName .. "/" .. d.Name
end
if p0 and p1 and (targets[p0] or targets[p1]) then
log(string.format(" %-28s %s <-> %s", kind, p0.Name, p1.Name))
end
end
-- 4. Servo runtime state ---------------------------------------------------
log("")
log("4. SERVO RUNTIME STATE:")
for _, d in ipairs(model:GetDescendants()) do
if d:IsA("HingeConstraint") and d.Name == Chassis.STEER_SERVO_NAME then
log(
string.format(
" %s Enabled=%s Active=%s Actuator=%s Torque=%.0e Speed=%.1f Limits=%s [%.1f..%.1f] Target=%.1f Current=%.2f",
d.Parent.Name,
tostring(d.Enabled),
tostring(d.Active),
d.ActuatorType.Name,
d.ServoMaxTorque,
d.AngularSpeed,
tostring(d.LimitsEnabled),
d.LowerAngle,
d.UpperAngle,
d.TargetAngle,
d.CurrentAngle
)
)
end
end
model:Destroy()
pad:Destroy()
return table.concat(out, "\n")