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.
180 lines
4.9 KiB
Luau
180 lines
4.9 KiB
Luau
--[[
|
|
diag_steer5.luau -- find a FIX for the servo/motor conflict.
|
|
|
|
Established by diag_steer4: an ENABLED WheelMotor hinge locks the knuckle
|
|
so the steer servo cannot yaw it. Disabling the motor (or deleting the
|
|
wheel) restores perfect tracking (-19.99 of -20.0). MotorMaxTorque is 0 at
|
|
build time, so this is NOT a torque contest -- it is a degenerate
|
|
constraint configuration.
|
|
|
|
Suspicion: Hub, Knuckle and Wheel are all built at the SAME CFrame, and
|
|
the motor's two attachments are both identity CFrames, so every attachment
|
|
sits at the same world point. Two hinges sharing a point with perpendicular
|
|
axes is ill-conditioned for the solver.
|
|
|
|
Each variant below changes ONE thing and re-measures.
|
|
]]
|
|
|
|
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
|
|
|
|
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(3000, 8, 3000)
|
|
pad.Position = Vector3.new(0, 5000, 0)
|
|
pad.Anchored = true
|
|
pad.Material = Enum.Material.Concrete
|
|
pad.Parent = workspace
|
|
local padTop = pad.Position.Y + 4
|
|
|
|
local car = Cars.Get("gtr_r34")
|
|
local tuning = Chassis.tuningFor(car)
|
|
local TARGET = -20
|
|
|
|
local function parts(model)
|
|
local servos, motors, wheels, knuckles = {}, {}, {}, {}
|
|
for _, d in ipairs(model:GetDescendants()) do
|
|
if d:IsA("HingeConstraint") then
|
|
if d.Name == Chassis.STEER_SERVO_NAME then
|
|
table.insert(servos, d)
|
|
elseif d.Name == Chassis.WHEEL_MOTOR_NAME then
|
|
table.insert(motors, d)
|
|
end
|
|
elseif d:IsA("BasePart") then
|
|
if d.Name:sub(1, 6) == "Wheel_" then
|
|
table.insert(wheels, d)
|
|
elseif d.Name:sub(1, 8) == "Knuckle_" then
|
|
table.insert(knuckles, d)
|
|
end
|
|
end
|
|
end
|
|
return servos, motors, wheels, knuckles
|
|
end
|
|
|
|
-- mutate: applied after build, before settling. drive: applied after settling.
|
|
local function variant(label, mutate, drive)
|
|
local m = CarManager._buildForDiag(car, tuning)
|
|
m.Parent = workspace
|
|
m:PivotTo(CFrame.new(0, padTop + 3, 0))
|
|
local servos, motors, wheels, knuckles = parts(m)
|
|
if mutate then
|
|
mutate(servos, motors, wheels, knuckles, m)
|
|
end
|
|
for _ = 1, 60 do
|
|
RunService.Heartbeat:Wait()
|
|
end
|
|
local body = m.PrimaryPart
|
|
body.AssemblyLinearVelocity = body.CFrame.LookVector * 80
|
|
if drive then
|
|
drive(servos, motors, wheels, knuckles, m)
|
|
end
|
|
for _, s in ipairs(servos) do
|
|
s.TargetAngle = TARGET
|
|
end
|
|
local trace, yaw = {}, {}
|
|
for step = 1, 120 do
|
|
for _, s in ipairs(servos) do
|
|
s.TargetAngle = TARGET
|
|
end
|
|
if step % 24 == 0 then
|
|
trace[#trace + 1] = string.format("%.1f", servos[1].CurrentAngle)
|
|
yaw[#yaw + 1] = string.format("%.2f", body.AssemblyAngularVelocity.Y)
|
|
end
|
|
RunService.Heartbeat:Wait()
|
|
end
|
|
local final = servos[1].CurrentAngle
|
|
local ok = math.abs(final - TARGET) < 5
|
|
log(
|
|
string.format(
|
|
"%-40s [%s] final %6.2f yaw[%s] %s",
|
|
label,
|
|
table.concat(trace, " "),
|
|
final,
|
|
table.concat(yaw, " "),
|
|
ok and "*** STEERS ***" or "stuck"
|
|
)
|
|
)
|
|
m:Destroy()
|
|
end
|
|
|
|
log(string.format("target %.1f deg, rolling at 80 studs/s, motors at build-default torque", TARGET))
|
|
log("")
|
|
|
|
variant("V0 baseline (unmodified)", nil, nil)
|
|
|
|
variant("V1 motor ActuatorType = None", function(_, motors)
|
|
for _, mo in ipairs(motors) do
|
|
mo.ActuatorType = Enum.ActuatorType.None
|
|
end
|
|
end)
|
|
|
|
variant("V2 motor attachments offset 0.01 X", function(_, motors)
|
|
for _, mo in ipairs(motors) do
|
|
mo.Attachment0.CFrame = mo.Attachment0.CFrame * CFrame.new(0.01, 0, 0)
|
|
end
|
|
end)
|
|
|
|
variant("V3 knuckle mass x20 (density)", function(_, _, _, knuckles)
|
|
for _, k in ipairs(knuckles) do
|
|
k.CustomPhysicalProperties = PhysicalProperties.new(20, 0.3, 0.5, 1, 1)
|
|
end
|
|
end)
|
|
|
|
variant("V4 wheel Massless=true", function(_, _, wheels)
|
|
for _, w in ipairs(wheels) do
|
|
w.Massless = true
|
|
end
|
|
end)
|
|
|
|
variant("V5 servo torque 1e9 + speed 30", function(servos)
|
|
for _, s in ipairs(servos) do
|
|
s.ServoMaxTorque = 1e9
|
|
s.AngularSpeed = 30
|
|
end
|
|
end)
|
|
|
|
variant("V6 motor torque 0 held every frame", nil, function(_, motors)
|
|
for _, mo in ipairs(motors) do
|
|
mo.MotorMaxTorque = 0
|
|
mo.AngularVelocity = 0
|
|
end
|
|
end)
|
|
|
|
variant("V7 front motors disabled only", function(_, motors)
|
|
-- rear-wheel drive: only the steered corners freewheel
|
|
for _, mo in ipairs(motors) do
|
|
local n = mo.Parent and mo.Parent.Name or ""
|
|
if n:find("F") then
|
|
mo.Enabled = false
|
|
end
|
|
end
|
|
end)
|
|
|
|
variant("V8 V1 + V5 combined", function(servos, motors)
|
|
for _, mo in ipairs(motors) do
|
|
mo.ActuatorType = Enum.ActuatorType.None
|
|
end
|
|
for _, s in ipairs(servos) do
|
|
s.ServoMaxTorque = 1e9
|
|
s.AngularSpeed = 30
|
|
end
|
|
end)
|
|
|
|
pad:Destroy()
|
|
return table.concat(out, "\n")
|