Real car meshes + physics-derived suspension + inPA/void fixes
CI / quality (push) Failing after 56s

Car meshes:
- CarManager: clone ReplicatedStorage/CarModels/<carId> as a visual Shell,
  weld to Body, hide placeholder blocks. Falls back to procedural CarBody
  when no mesh exists, so cars can be converted one at a time.
- Shell parts are Massless/CanCollide=false/CanQuery=false: the constraint
  chassis and driving feel are untouched. Verified 0 violating parts live.
- 5 catalogue cars mapped from the 67-model ServerStorage/CarLibrary import.

Suspension (ref docs/CAR_PHYSICS_REFERENCE.txt, devforum 3915628):
- BUG: old k=9000 gave 1.10 studs static sag against 0.90 studs total travel,
  i.e. the suspension sat permanently bottomed out and the spring did no work.
- Stiffness now solved from a sag target (70%% of half travel); damping now
  derived as a damping ratio c = zeta*2*sqrt(k*m) instead of an unrelated
  constant. Old pair worked out to zeta=0.31 (floaty); now 0.55-0.63.
- Measured after fix: ride height 1.97 studs, bounce 0.000, body roll 0.002.

Also: Circuit.inPA square test (PAFloor is 520x520, test was a r=260 circle,
corners falsely reported outside and spuriously armed runs), and void-fall
recovery now moves the character to the spawn pad BEFORE building the car.
This commit is contained in:
Hermes
2026-07-31 19:04:32 -04:00
parent f70a0b06bb
commit 0c5ecd2487
7 changed files with 2050 additions and 13 deletions
+58
View File
@@ -323,6 +323,64 @@ local function buildPA(map: Folder)
})
ramp.Parent = pa
-- PERIMETER BARRIERS: the PA floor is a 520x520 slab floating ~46 studs up
-- with nothing at its edges. Endurance testing drove straight off the side
-- and fell past FallenPartsDestroyHeight (-500), destroying the car and
-- stranding the player seatless until the death/respawn cycle caught it.
-- Wall every edge, leaving a gap on the side the ramp exits through so the
-- route to the expressway stays open.
do
local rampDir = Vector3.new(road.X - c.X, 0, road.Z - c.Z)
-- Which edge does the ramp leave from? Whichever axis dominates.
local rampOnX = math.abs(rampDir.X) > math.abs(rampDir.Z)
local rampSign = rampOnX and math.sign(rampDir.X) or math.sign(rampDir.Z)
local GAP = 70 -- half-width of the opening (ramp is 46 wide)
local H, T = 14, 4
local function wall(name, size, pos)
local w = part({
Name = name,
Size = size,
CFrame = CFrame.new(pos),
Color = Color3.fromRGB(96, 96, 104),
Material = Enum.Material.Concrete,
})
w.Parent = pa
end
-- Four edges. On the ramp's edge, build two stubs flanking the opening.
local edges = {
{ onX = true, sign = 1 },
{ onX = true, sign = -1 },
{ onX = false, sign = 1 },
{ onX = false, sign = -1 },
}
for _, e in ipairs(edges) do
local isRampEdge = (e.onX == rampOnX) and (e.sign == rampSign)
-- Offset along the edge normal; span the full slab on the other axis.
local normal = e.onX and Vector3.new(e.sign * R, 0, 0) or Vector3.new(0, 0, e.sign * R)
local base = c + normal + Vector3.new(0, H / 2, 0)
local fullSize = e.onX and Vector3.new(T, H, R * 2) or Vector3.new(R * 2, H, T)
if not isRampEdge then
wall("PAWall", fullSize, base)
else
-- two stubs, each running from the corner in to the gap edge
local stubLen = R - GAP
if stubLen > 4 then
local half = stubLen / 2
local off = GAP + half
if e.onX then
wall("PAWall", Vector3.new(T, H, stubLen), base + Vector3.new(0, 0, off))
wall("PAWall", Vector3.new(T, H, stubLen), base + Vector3.new(0, 0, -off))
else
wall("PAWall", Vector3.new(stubLen, H, T), base + Vector3.new(off, 0, 0))
wall("PAWall", Vector3.new(stubLen, H, T), base + Vector3.new(-off, 0, 0))
end
end
end
end
end
local pad = part({
Name = "SpawnPad",
Size = Vector3.new(120, 2.4, 120),