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.
189 lines
5.3 KiB
Luau
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")
|