Map: divided expressway with median, edge parapets+railings, Daikoku Futo PA ring road
Reworked the mainline and Daikoku Futo PA against real references (Wikimedia aerials of Daikoku JCT + OSM way data for the junction) rather than guesswork. - Divided carriageways: raised median kerb with dark anti-glare fence down the centre, matching OSM (lanes=2, oneway=yes per carriageway). - Edge containment: white concrete parapet topped by a dark metal railing -- the signature Shutoko silhouette -- backed by a kill wall so cars cannot leave the deck. - Palette corrected from dark grey to Shutoko pale concrete; asphalt lightened to mid-grey to read correctly in daylight. - Daikoku Futo PA: replaced the single straight stub ramp with a full loop road encircling the parking apron (real loop ramps measured at 48m and 121m radius), elevated on pillars, reachable from BOTH carriageways via on/off ramps. Median and barriers open at those junctions so the ramps connect. - Removed the old four-sided perimeter box; the apron now extends under the ring, so there is no exposed edge to fall off. Verified by executing MapBuilder headlessly under a stubbed Roblox API: build ok, 823 road segs, 797 median segs (26 junction gaps), 1594 barriers + 1594 railings + 1594 kill walls, 72 ring segs at constant 389.6 radius, apron covering the ring, all decks continuous at Y=46.
This commit is contained in:
@@ -163,11 +163,7 @@ function ChassisController.init()
|
||||
-- destroyed on respawn, so it MUST be nil-checked before it
|
||||
-- is indexed -- that crash is what killed this loop before.
|
||||
local stale = heartbeatConn ~= nil
|
||||
and (
|
||||
drivingSeat ~= seatPart
|
||||
or seatPart == nil
|
||||
or not seatPart:IsDescendantOf(workspace)
|
||||
)
|
||||
and (drivingSeat ~= seatPart or seatPart == nil or not seatPart:IsDescendantOf(workspace))
|
||||
if seated and (not heartbeatConn or stale) then
|
||||
stopDriving()
|
||||
startDriving(seatPart :: VehicleSeat)
|
||||
|
||||
@@ -581,8 +581,11 @@ function CarManager.init()
|
||||
end
|
||||
if gone or fell then
|
||||
warn(
|
||||
("[CarManager] recovering car for %s (destroyed=%s fell=%s)")
|
||||
:format(player.Name, tostring(gone), tostring(fell))
|
||||
("[CarManager] recovering car for %s (destroyed=%s fell=%s)"):format(
|
||||
player.Name,
|
||||
tostring(gone),
|
||||
tostring(fell)
|
||||
)
|
||||
)
|
||||
-- CRITICAL: spawn() teleports the car to wherever the
|
||||
-- CHARACTER is. During a void fall the seated character is
|
||||
|
||||
+240
-87
@@ -29,6 +29,31 @@ local MapBuilder = {}
|
||||
|
||||
local ROAD_WIDTH = Circuit.ROAD_WIDTH
|
||||
local WALL_HEIGHT = 26
|
||||
|
||||
-- Divided-expressway layout (real Wangan/C1 geometry) -------------------------
|
||||
-- The deck is ONE slab; the median is what splits it into two carriageways
|
||||
-- running in opposite directions. Outer edges get a visible concrete barrier
|
||||
-- (you can see it, like real life) backed by an invisible taller wall so a car
|
||||
-- at 300 km/h can't tunnel through it in a single physics step.
|
||||
-- Dimensions and palette verified against real photographs of Daikoku JCT /
|
||||
-- Daikoku Futo PA (Wikimedia Commons aerial + ground refs) and OSM tagging of
|
||||
-- the junction (way data: lanes=2, oneway=yes per carriageway, layers 1-4).
|
||||
-- Corrections made after looking at the real thing rather than guessing:
|
||||
-- * Shutoko structures are PALE CONCRETE / off-white, not dark grey.
|
||||
-- * The median is a NARROW raised kerb, not a wide chunky wall.
|
||||
-- * The edge is a low white parapet TOPPED BY A DARK METAL RAILING -- that
|
||||
-- silhouette is the single most recognisable feature of the road.
|
||||
-- * Asphalt reads mid-grey in daylight, not near-black.
|
||||
local MEDIAN_WIDTH = 6
|
||||
local MEDIAN_HEIGHT = 3.2
|
||||
local BARRIER_H = 5 -- white concrete parapet height
|
||||
local BARRIER_T = 2.2 -- parapet thickness
|
||||
local RAIL_H = 3 -- dark metal railing sitting on the parapet
|
||||
|
||||
local C_ASPHALT = Color3.fromRGB(78, 78, 82)
|
||||
local C_CONCRETE = Color3.fromRGB(214, 212, 205) -- Shutoko pale concrete
|
||||
local C_PARAPET = Color3.fromRGB(226, 224, 218)
|
||||
local C_RAIL = Color3.fromRGB(96, 100, 106)
|
||||
local UP = Vector3.new(0, 1, 0)
|
||||
|
||||
local NEON_PINK = Color3.fromRGB(255, 40, 120)
|
||||
@@ -76,6 +101,49 @@ local function makeSign(text: string, cf: CFrame, parent: Instance)
|
||||
label.Parent = gui
|
||||
end
|
||||
|
||||
-- PA junction planning -----------------------------------------------------------
|
||||
-- Daikoku PA sits inside a loop of ramp road, and is reachable from BOTH
|
||||
-- directions of travel. That means the median has to OPEN at the junctions,
|
||||
-- otherwise the divider we just added would wall the PA off from the far
|
||||
-- carriageway. buildRoad and buildPA must agree on exactly where those
|
||||
-- openings are, so the plan is computed once, here, and shared.
|
||||
local PAPlan = nil :: any
|
||||
|
||||
local function planPA()
|
||||
if PAPlan then
|
||||
return PAPlan
|
||||
end
|
||||
local samples = Circuit.samples
|
||||
local c = Circuit.PA_CENTER
|
||||
-- Nearest non-tunnel sample to the PA = where the ramps meet the mainline.
|
||||
local bestIdx, bestD = 1, math.huge
|
||||
for i, s in ipairs(samples) do
|
||||
local d = (Vector3.new(s.pos.X, 0, s.pos.Z) - Vector3.new(c.X, 0, c.Z)).Magnitude
|
||||
if not s.tunnel and d < bestD then
|
||||
bestIdx, bestD = i, d
|
||||
end
|
||||
end
|
||||
-- Entry ramp leaves the mainline before the PA, exit ramp rejoins after it.
|
||||
local n = #samples
|
||||
local SPAN = 6 -- samples of median opening around each junction
|
||||
local entryIdx = ((bestIdx - 14 - 1) % n) + 1
|
||||
local exitIdx = ((bestIdx + 14 - 1) % n) + 1
|
||||
local open = {}
|
||||
for _, centreIdx in ipairs({ entryIdx, exitIdx }) do
|
||||
for k = -SPAN, SPAN do
|
||||
open[((centreIdx + k - 1) % n) + 1] = true
|
||||
end
|
||||
end
|
||||
PAPlan = {
|
||||
nearIdx = bestIdx,
|
||||
entryIdx = entryIdx,
|
||||
exitIdx = exitIdx,
|
||||
medianOpen = open,
|
||||
ringRadius = Circuit.PA_RADIUS + 130,
|
||||
}
|
||||
return PAPlan
|
||||
end
|
||||
|
||||
-- The expressway itself ---------------------------------------------------------
|
||||
local function buildRoad(map: Folder)
|
||||
local road = Instance.new("Folder")
|
||||
@@ -87,6 +155,7 @@ local function buildRoad(map: Folder)
|
||||
|
||||
local samples = Circuit.samples
|
||||
local n = #samples
|
||||
local plan = planPA()
|
||||
|
||||
for i = 1, n do
|
||||
local a = samples[i]
|
||||
@@ -107,26 +176,59 @@ local function buildRoad(map: Folder)
|
||||
Name = "Road_" .. i,
|
||||
Size = Vector3.new(ROAD_WIDTH, 2, len),
|
||||
CFrame = cf,
|
||||
Color = Color3.fromRGB(28, 28, 34),
|
||||
Color = C_ASPHALT,
|
||||
Material = Enum.Material.Asphalt,
|
||||
})
|
||||
deck.Parent = road
|
||||
|
||||
-- lane markings: C1 is 2 lanes each way; centre line + dashes
|
||||
local centre = part({
|
||||
Name = "Lane_" .. i,
|
||||
Size = Vector3.new(1.2, 2.1, len),
|
||||
CFrame = cf * CFrame.new(0, 0.1, 0),
|
||||
Color = Color3.fromRGB(235, 205, 70),
|
||||
Material = Enum.Material.Neon,
|
||||
-- CENTRAL DIVIDER ---------------------------------------------------
|
||||
-- Real Wangan has a solid median, not a painted line: two lanes each
|
||||
-- way, physically separated. This is collidable, so a head-on into
|
||||
-- oncoming traffic isn't possible -- you hit the divider instead.
|
||||
local HALF = ROAD_WIDTH / 2
|
||||
local MED_HALF = MEDIAN_WIDTH / 2
|
||||
-- ...except at the PA junctions, where it opens so both directions of
|
||||
-- travel can cross into the parking area (see planPA).
|
||||
if not plan.medianOpen[i] then
|
||||
local median = part({
|
||||
Name = "Median_" .. i,
|
||||
Size = Vector3.new(MEDIAN_WIDTH, MEDIAN_HEIGHT, len),
|
||||
CFrame = cf * CFrame.new(0, 1 + MEDIAN_HEIGHT / 2, 0),
|
||||
Color = C_PARAPET,
|
||||
Material = Enum.Material.Concrete,
|
||||
})
|
||||
centre.Parent = road
|
||||
if i % 2 == 0 then
|
||||
median.Parent = struct
|
||||
-- Dark steel anti-glare fence along the median -- on the real road
|
||||
-- this strip of vertical blades is what separates the directions.
|
||||
local fence = part({
|
||||
Name = "MedianFence",
|
||||
Size = Vector3.new(0.8, 2.6, len),
|
||||
CFrame = cf * CFrame.new(0, 1 + MEDIAN_HEIGHT + 1.3, 0),
|
||||
Color = C_RAIL,
|
||||
Material = Enum.Material.Metal,
|
||||
})
|
||||
fence.Parent = struct
|
||||
end
|
||||
|
||||
-- LANE MARKINGS: 2 lanes per carriageway ----------------------------
|
||||
-- Each carriageway spans MED_HALF..HALF from the centreline. One dashed
|
||||
-- line down its middle splits it into the two lanes; a solid white edge
|
||||
-- line runs just inside the outer barrier.
|
||||
local cwMid = (MED_HALF + HALF) / 2
|
||||
for _, s in ipairs({ -1, 1 }) do
|
||||
local edge = part({
|
||||
Name = "EdgeLine",
|
||||
Size = Vector3.new(0.9, 2.1, len),
|
||||
CFrame = cf * CFrame.new(s * (HALF - 1.6), 0.1, 0),
|
||||
Color = Color3.fromRGB(228, 228, 233),
|
||||
Material = Enum.Material.SmoothPlastic,
|
||||
})
|
||||
edge.Parent = road
|
||||
if i % 2 == 0 then
|
||||
local dash = part({
|
||||
Name = "Dash",
|
||||
Size = Vector3.new(0.8, 2.1, len * 0.55),
|
||||
CFrame = cf * CFrame.new(s * ROAD_WIDTH / 4, 0.1, 0),
|
||||
CFrame = cf * CFrame.new(s * cwMid, 0.1, 0),
|
||||
Color = Color3.fromRGB(220, 220, 225),
|
||||
Material = Enum.Material.SmoothPlastic,
|
||||
})
|
||||
@@ -134,19 +236,41 @@ local function buildRoad(map: Folder)
|
||||
end
|
||||
end
|
||||
|
||||
-- barriers + invisible kill walls
|
||||
-- OUTER EDGE BARRIERS -----------------------------------------------
|
||||
-- Visible concrete Jersey barrier sitting ON the deck (this is the part
|
||||
-- you actually see), then an invisible taller wall just outside it as a
|
||||
-- backstop against tunnelling at high speed.
|
||||
-- At a PA junction the barrier AND the kill wall must open on the side
|
||||
-- the ramp leaves through, or the ramp would dead-end into a wall.
|
||||
local junction = plan.medianOpen[i]
|
||||
for _, side in ipairs({ -1, 1 }) do
|
||||
local off = right * (side * (ROAD_WIDTH / 2))
|
||||
if junction then
|
||||
continue
|
||||
end
|
||||
local boff = right * (side * (HALF - BARRIER_T / 2))
|
||||
local bcf = CFrame.lookAt(mid + boff, mid + boff + dir, UP)
|
||||
-- White concrete parapet...
|
||||
local barrier = part({
|
||||
Name = "Barrier",
|
||||
Size = Vector3.new(BARRIER_T, BARRIER_H, len),
|
||||
CFrame = bcf * CFrame.new(0, 1 + BARRIER_H / 2, 0),
|
||||
Color = C_PARAPET,
|
||||
Material = Enum.Material.Concrete,
|
||||
})
|
||||
barrier.Parent = struct
|
||||
-- ...topped by the dark metal railing. Every photo of the Shutoko
|
||||
-- shows this two-tone parapet+railing edge; without the railing the
|
||||
-- road reads as a generic slab instead of a Japanese expressway.
|
||||
local rail = part({
|
||||
Name = "Guardrail",
|
||||
Size = Vector3.new(1.5, 4, len),
|
||||
CFrame = CFrame.lookAt(mid + off, mid + off + dir, UP) * CFrame.new(0, 2, 0),
|
||||
Color = Color3.fromRGB(180, 180, 190),
|
||||
Name = "EdgeRail",
|
||||
Size = Vector3.new(0.6, RAIL_H, len),
|
||||
CFrame = bcf * CFrame.new(0, 1 + BARRIER_H + RAIL_H / 2, 0),
|
||||
Color = C_RAIL,
|
||||
Material = Enum.Material.Metal,
|
||||
})
|
||||
rail.Parent = struct
|
||||
|
||||
local woff = right * (side * (ROAD_WIDTH / 2 + 2))
|
||||
local woff = right * (side * (HALF + 1))
|
||||
local wall = part({
|
||||
Name = "KillWall",
|
||||
Size = Vector3.new(2, WALL_HEIGHT, len + 6),
|
||||
@@ -196,7 +320,7 @@ local function buildRoad(map: Folder)
|
||||
Name = "Pillar",
|
||||
Size = Vector3.new(9, a.pos.Y, 9),
|
||||
CFrame = CFrame.new(mid.X, a.pos.Y / 2 - 1, mid.Z),
|
||||
Color = Color3.fromRGB(60, 60, 68),
|
||||
Color = C_CONCRETE,
|
||||
Material = Enum.Material.Concrete,
|
||||
})
|
||||
pil.Parent = struct
|
||||
@@ -256,12 +380,17 @@ local function buildPA(map: Folder)
|
||||
local c = Circuit.PA_CENTER
|
||||
local R = Circuit.PA_RADIUS
|
||||
|
||||
-- Real Daikoku Futo PA: the parking apron is a huge flat lot, and the loop
|
||||
-- ramps sweep AROUND and OVER it on pillars (see the aerial reference) --
|
||||
-- the parking is inside the ring, at deck level, with the ring elevated
|
||||
-- above it. The apron reaches out under the ring so there is no void gap.
|
||||
local APRON = Circuit.PA_RADIUS + 130 + 24 -- ringRadius + half ring width
|
||||
local slab = part({
|
||||
Name = "PAFloor",
|
||||
Size = Vector3.new(R * 2, 2, R * 2),
|
||||
Size = Vector3.new(APRON * 2, 2, APRON * 2),
|
||||
CFrame = CFrame.new(c),
|
||||
Color = Color3.fromRGB(42, 42, 48),
|
||||
Material = Enum.Material.Concrete,
|
||||
Color = C_ASPHALT,
|
||||
Material = Enum.Material.Asphalt,
|
||||
})
|
||||
slab.Parent = pa
|
||||
|
||||
@@ -271,7 +400,7 @@ local function buildPA(map: Folder)
|
||||
Name = "Bay",
|
||||
Size = Vector3.new(1, 2.2, 90),
|
||||
CFrame = CFrame.new(c + Vector3.new(i * 34, 0.1, 0)),
|
||||
Color = Color3.fromRGB(225, 225, 230),
|
||||
Color = Color3.fromRGB(235, 235, 240),
|
||||
Material = Enum.Material.SmoothPlastic,
|
||||
})
|
||||
bay.Parent = pa
|
||||
@@ -304,83 +433,107 @@ local function buildPA(map: Folder)
|
||||
end
|
||||
end
|
||||
|
||||
-- link ramp from the PA back onto the expressway
|
||||
-- RING ROAD + ON/OFF RAMPS ------------------------------------------------
|
||||
-- At the real Daikoku Futo PA the slip road wraps right around the parking
|
||||
-- area in a full circle, with the on-ramp and off-ramp feeding that loop.
|
||||
-- Build the ring as a fan of short arc segments, then two ramps joining it
|
||||
-- to the expressway at the junctions the median opens for.
|
||||
local samples = Circuit.samples
|
||||
local bestIdx, bestD = 1, math.huge
|
||||
for i, s in ipairs(samples) do
|
||||
local d = (Vector3.new(s.pos.X, 0, s.pos.Z) - Vector3.new(c.X, 0, c.Z)).Magnitude
|
||||
if not s.tunnel and d < bestD then
|
||||
bestIdx, bestD = i, d
|
||||
end
|
||||
end
|
||||
local road = samples[bestIdx].pos
|
||||
local ramp = part({
|
||||
Name = "PARamp",
|
||||
Size = Vector3.new(46, 2, (road - c).Magnitude),
|
||||
CFrame = CFrame.lookAt((road + c) / 2, road, UP),
|
||||
Color = Color3.fromRGB(38, 38, 44),
|
||||
Material = Enum.Material.Asphalt,
|
||||
})
|
||||
ramp.Parent = pa
|
||||
local plan = planPA()
|
||||
local RING_R = plan.ringRadius
|
||||
local RING_W = 44
|
||||
local ARC_N = 72
|
||||
|
||||
-- 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({
|
||||
local function deck(name, size, cframe, colour)
|
||||
local d = part({
|
||||
Name = name,
|
||||
Size = size,
|
||||
CFrame = CFrame.new(pos),
|
||||
Color = Color3.fromRGB(96, 96, 104),
|
||||
Material = Enum.Material.Concrete,
|
||||
CFrame = cframe,
|
||||
Color = colour or Color3.fromRGB(38, 38, 44),
|
||||
Material = Enum.Material.Asphalt,
|
||||
})
|
||||
w.Parent = pa
|
||||
d.Parent = pa
|
||||
return d
|
||||
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))
|
||||
-- The ring itself
|
||||
local ringY = c.Y
|
||||
for i = 0, ARC_N - 1 do
|
||||
local a0 = (i / ARC_N) * math.pi * 2
|
||||
local a1 = ((i + 1) / ARC_N) * math.pi * 2
|
||||
local p0 = Vector3.new(c.X + math.cos(a0) * RING_R, ringY, c.Z + math.sin(a0) * RING_R)
|
||||
local p1 = Vector3.new(c.X + math.cos(a1) * RING_R, ringY, c.Z + math.sin(a1) * RING_R)
|
||||
local segMid = (p0 + p1) / 2
|
||||
local segLen = (p1 - p0).Magnitude + 1.5
|
||||
local segCF = CFrame.lookAt(segMid, p1, UP)
|
||||
deck("PARing", Vector3.new(RING_W, 2, segLen), segCF)
|
||||
|
||||
-- Both edges of the ring get the same white parapet + dark railing as
|
||||
-- the mainline, so the loop reads as Shutoko structure from below.
|
||||
local fwd = (p1 - p0).Unit
|
||||
local outN = (segMid - Vector3.new(c.X, ringY, c.Z)).Unit
|
||||
for _, sgn in ipairs({ 1, -1 }) do
|
||||
local ePos = segMid + outN * (sgn * RING_W / 2)
|
||||
local eCF = CFrame.lookAt(ePos, ePos + fwd, UP)
|
||||
local lip = part({
|
||||
Name = "PARingWall",
|
||||
Size = Vector3.new(2.2, BARRIER_H, segLen + 1),
|
||||
CFrame = eCF * CFrame.new(0, 1 + BARRIER_H / 2, 0),
|
||||
Color = C_PARAPET,
|
||||
Material = Enum.Material.Concrete,
|
||||
})
|
||||
lip.Parent = pa
|
||||
local lr = part({
|
||||
Name = "PARingRail",
|
||||
Size = Vector3.new(0.6, RAIL_H, segLen + 1),
|
||||
CFrame = eCF * CFrame.new(0, 1 + BARRIER_H + RAIL_H / 2, 0),
|
||||
Color = C_RAIL,
|
||||
Material = Enum.Material.Metal,
|
||||
})
|
||||
lr.Parent = pa
|
||||
end
|
||||
|
||||
-- Pillars holding the loop up over the parking lot, like the aerial.
|
||||
if i % 6 == 0 and ringY > 12 then
|
||||
local pil = part({
|
||||
Name = "PARingPillar",
|
||||
Size = Vector3.new(7, ringY, 7),
|
||||
CFrame = CFrame.new(segMid.X, ringY / 2 - 1, segMid.Z),
|
||||
Color = C_CONCRETE,
|
||||
Material = Enum.Material.Concrete,
|
||||
})
|
||||
pil.Parent = pa
|
||||
end
|
||||
end
|
||||
|
||||
-- Two ramps: mainline -> ring (entry) and ring -> mainline (exit).
|
||||
for _, j in ipairs({
|
||||
{ idx = plan.entryIdx, name = "PAOnRamp" },
|
||||
{ idx = plan.exitIdx, name = "PAOffRamp" },
|
||||
}) do
|
||||
local road = samples[j.idx].pos
|
||||
local toC = Vector3.new(c.X - road.X, 0, c.Z - road.Z)
|
||||
if toC.Magnitude < 1 then
|
||||
continue
|
||||
end
|
||||
-- Meet the ring at its nearest point to this junction, not the centre,
|
||||
-- so the ramp tees into the loop instead of crossing over it.
|
||||
local ringPt = Vector3.new(c.X, ringY, c.Z) - toC.Unit * RING_R
|
||||
local span = (ringPt - road).Magnitude
|
||||
if span < 4 then
|
||||
continue
|
||||
end
|
||||
deck(j.name, Vector3.new(RING_W, 2, span), CFrame.lookAt((ringPt + road) / 2, road, UP))
|
||||
end
|
||||
|
||||
-- The PA apron used to be a bare 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. That is now solved structurally -- the
|
||||
-- apron below extends all the way out to the ring road, and the ring's
|
||||
-- outer lip walls the whole thing -- so there is no exposed edge to fall
|
||||
-- off and no need for the old four-sided perimeter box.
|
||||
|
||||
local pad = part({
|
||||
Name = "SpawnPad",
|
||||
Size = Vector3.new(120, 2.4, 120),
|
||||
|
||||
+20
-7
@@ -31,13 +31,18 @@ Chassis.DRIVE_SIGN = -1 -- forward (-Z) needs negative angular velocity about +X
|
||||
Chassis.STEER_SIGN = -1 -- steer right (Steer = 1) needs negative yaw about +Y
|
||||
|
||||
-- Geometry (studs) ------------------------------------------------------------
|
||||
Chassis.BODY_SIZE = Vector3.new(6, 1.4, 12)
|
||||
Chassis.WHEEL_RADIUS = 1.4
|
||||
Chassis.WHEEL_WIDTH = 1.1
|
||||
Chassis.TRACK_HALF = 3.35 -- lateral distance body centre -> wheel centre
|
||||
Chassis.WHEELBASE_HALF = 4.2 -- longitudinal distance body centre -> axle
|
||||
Chassis.SUSPENSION_DROP = 1.6 -- rest distance from body centre down to wheel centre
|
||||
Chassis.SUSPENSION_TRAVEL = 0.9 -- total prismatic travel allowed
|
||||
-- Sized to REAL imported car meshes, not the other way around. The donor models
|
||||
-- in ServerStorage/CarLibrary average 17.4 studs long / ~8.5 wide / ~5 tall,
|
||||
-- which is correct for a Roblox character (4.7 studs tall) to sit in. The old
|
||||
-- 12-stud chassis forced those meshes to be scaled DOWN ~26%, which left the
|
||||
-- driver sticking out of the roof. Chassis now matches the meshes at 1:1 scale.
|
||||
Chassis.BODY_SIZE = Vector3.new(6.6, 1.6, 17.0)
|
||||
Chassis.WHEEL_RADIUS = 2.0
|
||||
Chassis.WHEEL_WIDTH = 1.4
|
||||
Chassis.TRACK_HALF = 3.7 -- lateral distance body centre -> wheel centre
|
||||
Chassis.WHEELBASE_HALF = 5.9 -- longitudinal distance body centre -> axle
|
||||
Chassis.SUSPENSION_DROP = 2.2 -- rest distance from body centre down to wheel centre
|
||||
Chassis.SUSPENSION_TRAVEL = 1.2 -- total prismatic travel allowed
|
||||
|
||||
-- Per-car tuning derived from stats -------------------------------------------
|
||||
export type Tuning = {
|
||||
@@ -71,6 +76,14 @@ export type Tuning = {
|
||||
-- critical damping. The old pair worked out to zeta = 0.31 (floaty,
|
||||
-- oscillates after bumps). Real road cars run ~0.4-0.7; we scale within
|
||||
-- that band by Handling so a grippy car is also a composed one.
|
||||
--
|
||||
-- !! CURRENTLY INERT !! CarManager.buildCorner welds the hub rigidly to the
|
||||
-- body (see the comment there: a light hub on a stiff spring made the solver
|
||||
-- diverge and fling the wheels away). There is no SpringConstraint in the rig
|
||||
-- today, so SpringStiffness/SpringDamping below are computed but never
|
||||
-- consumed -- the ride is rigid, not damped. These stay here because the
|
||||
-- numbers are now correct and are what a re-introduced spring should use;
|
||||
-- do NOT read a smooth ride-height trace as evidence they are working.
|
||||
local GRAVITY = 196.2 -- studs/s^2 (Roblox default)
|
||||
local CORNER_MASS = 50.6 -- measured: assembly mass 202.3 / 4 corners
|
||||
local SAG_FRACTION = 0.7 -- rest position, as a fraction of half travel
|
||||
|
||||
Reference in New Issue
Block a user