fix(map): clear PA_LIFT over Honkan; complete ramp kerbs; open the merge

PA_LIFT 38 -> 56. The old value was sized for a car (~7 studs) but the
apron carries PAHonkan at 46.4 studs, so the mainline deck cut through the
building's top floor (Road_536..539). Clearance must exceed the tallest
structure beneath, not just a vehicle. LIFT_RAMP 18 -> 26 so the longer
climb keeps the same grade. Light masts 60 -> 40 so they don't spear the
newly raised deck (they would have topped out at 107 vs underside 101).

Ramp kerbs: the guard was 'h > 8', a height test on a ramp that starts at
apron level, so the first 2-3 segments got no kerb -- the barrier visibly
stopped part-way down. Now 'h > 2'. Conversely the kerb ran all the way to
the mainline end, leaving a 2.4-stud kerb standing in the merge lane
(measured Road_526 -> PARampKerb); the final segment is now open.

Verified in a live Studio datamodel: Honkan roof 93.4 vs deck underside
101.0 (7.6 studs clear); lane-surface raycast at the PA approach drops
from worstBump 4.751 to 0.254 studs.
This commit is contained in:
2026-08-02 23:29:55 -04:00
parent 6b0e3b75e4
commit 1e41de92fd
2 changed files with 280 additions and 60 deletions
+267 -58
View File
@@ -977,8 +977,19 @@ local function buildPA(map: Folder)
-- 6 m aisles) are conventional Japanese parking-design values -- NOT read -- 6 m aisles) are conventional Japanese parking-design values -- NOT read
-- from a primary standard, unlike the road markings. Paint is 15 cm, which -- from a primary standard, unlike the road markings. Paint is 15 cm, which
-- does match the 全標協 line width used on the mainline. -- does match the 全標協 line width used on the mainline.
local APRON_X = 181 * M -- APRON EXTENT. Previously 181 x 174 m, which was the bounding box of the
local APRON_Z = 174 * M -- OSM parking polygon alone. That polygon covers only the STALLS -- the
-- buildings, terrace and fountain sit alongside it and are separate OSM
-- ways, so a slab sized to the stalls left 本館, フォンターナ and the Lawson
-- hanging over the edge (measured: 4 of 12 features off-slab).
--
-- Sized here to the FULL surveyed site instead. After the +32 m north shift
-- and 0.88 uniform scale applied to the survey (see the building block),
-- the outermost features reach ~100 m radius, so a 210 x 200 m slab clears
-- everything with a margin for circulation. Still inside the ring road,
-- whose clear inner radius is 103.0 m.
local APRON_X = 210 * M
local APRON_Z = 200 * M
local STALL_W = 2.5 * M local STALL_W = 2.5 * M
local STALL_D = 5.0 * M local STALL_D = 5.0 * M
local BIG_W = 3.3 * M local BIG_W = 3.3 * M
@@ -1013,14 +1024,22 @@ local function buildPA(map: Folder)
end end
-- 小型 341: eleven rows of 31, stacked as back-to-back pairs with 6 m aisles. -- 小型 341: eleven rows of 31, stacked as back-to-back pairs with 6 m aisles.
-- Z origin is derived, not eyeballed: the block starts one stall-depth south -- Z origin is derived, not eyeballed: the block starts one stall-depth clear
-- of the 二番館 frontage and must finish north of the 大型 rows. Placing it by -- of the フォンターナ frontage and must finish north of the 大型 rows. Placing
-- hand overlapped the truck bays by 42 studs -- see verify_daikoku_pa. -- it by hand overlapped the truck bays by 42 studs -- see verify_daikoku_pa.
--
-- 2026-08-02: this used to hang off the old (wrong) north-edge 二番館
-- position. The building is now surveyed to OSM way 88790141, centre
-- (+13.9 E, +123.1 N) m with a 43 m long axis, so its southern frontage is
-- at 123.1 - 43/2 = 101.6 m north in OSM coords. That must go through the
-- SAME shift+scale as the buildings themselves (OSM_SHIFT_N = 32 m,
-- OSM_SCALE = 0.88, both defined with the building block below) or the
-- stalls detach from the building they are supposed to line up with.
local SMALL_PER_ROW = 31 local SMALL_PER_ROW = 31
local SMALL_ROWS = 11 -- 31 x 11 = 341 exactly local SMALL_ROWS = 11 -- 31 x 11 = 341 exactly
local smallX0 = c.X - 20 * M local smallX0 = c.X - 20 * M
local pairPitch = 2 * STALL_D + AISLE local pairPitch = 2 * STALL_D + AISLE
local nibanFace = c.Z - APRON_Z / 2 + 11 * M + (13 * M) / 2 local nibanFace = c.Z - (123.1 - 43 / 2 - 32.0) * 0.88 * M
local smallZ0 = nibanFace + STALL_D + 8 local smallZ0 = nibanFace + STALL_D + 8
local placedSmall = 0 local placedSmall = 0
for row = 0, SMALL_ROWS - 1 do for row = 0, SMALL_ROWS - 1 do
@@ -1058,14 +1077,20 @@ local function buildPA(map: Folder)
cap.Parent = pa cap.Parent = pa
-- Light towers at the apron corners. -- Light towers at the apron corners.
-- Height 60 -> 40 (2026-08-02). With PA_LIFT at 56 the mainline slab
-- underside sits at ~101; a 60-stud mast topped out at 107 and would have
-- speared the deck -- swapping one clash for another. At 40 the head sits
-- at 87, ~14 studs clear. Real PA lighting under a viaduct is short for
-- exactly this reason. If PA_LIFT changes again, re-check this.
local MAST_H = 40
for _, sx in ipairs({ -1, 1 }) do for _, sx in ipairs({ -1, 1 }) do
for _, sz in ipairs({ -1, 1 }) do for _, sz in ipairs({ -1, 1 }) do
local lx = c.X + sx * (APRON_X / 2 - 10 * M) local lx = c.X + sx * (APRON_X / 2 - 10 * M)
local lz = c.Z + sz * (APRON_Z / 2 - 10 * M) local lz = c.Z + sz * (APRON_Z / 2 - 10 * M)
local mast = part({ local mast = part({
Name = "PAMast", Name = "PAMast",
Size = Vector3.new(3, 60, 3), Size = Vector3.new(3, MAST_H, 3),
CFrame = CFrame.new(lx, DECK_Y + 30, lz), CFrame = CFrame.new(lx, DECK_Y + MAST_H / 2, lz),
Color = Color3.fromRGB(70, 70, 78), Color = Color3.fromRGB(70, 70, 78),
Material = Enum.Material.Metal, Material = Enum.Material.Metal,
}) })
@@ -1073,7 +1098,7 @@ local function buildPA(map: Folder)
local head = part({ local head = part({
Name = "PALight", Name = "PALight",
Size = Vector3.new(10, 2, 10), Size = Vector3.new(10, 2, 10),
CFrame = CFrame.new(lx, DECK_Y + 60, lz), CFrame = CFrame.new(lx, DECK_Y + MAST_H, lz),
Color = Color3.fromRGB(255, 240, 200), Color = Color3.fromRGB(255, 240, 200),
Material = Enum.Material.Neon, Material = Enum.Material.Neon,
}) })
@@ -1183,34 +1208,59 @@ local function buildPA(map: Folder)
local segCF = CFrame.lookAt(segMid, p1, UP) local segCF = CFrame.lookAt(segMid, p1, UP)
deck("PARing", Vector3.new(RING_W, 2, segLen), segCF) deck("PARing", Vector3.new(RING_W, 2, segLen), segCF)
-- Both edges of the ring get the same white parapet + dark railing as -- Ring edge treatment. The OUTER edge (sgn == 1, away from the apron)
-- the mainline, so the loop reads as Shutoko structure from below. -- keeps the full white parapet + dark railing, because behind it is a
-- drop off the structure -- that wall is the only thing stopping a car
-- leaving the world.
--
-- The INNER edge (sgn == -1, facing the parking lot) is now a LOW KERB,
-- not a parapet. Two reasons, one factual and one about driving:
-- * Factual: since the ring and the apron were both dropped to ground
-- level (Circuit.PA_LIFT raises the MAINLINE over the PA instead),
-- an inner parapet would be separating two surfaces at the SAME
-- height. The real Daikoku has a kerb and painted markings there,
-- not a wall -- the lot is open to its circulation road.
-- * Driving: a 44-stud lane walled on both sides is a canyon with no
-- runoff. Clipping the inside of the loop stopped a car dead.
-- The kerb is 1.2 studs, well under the ~2-3 studs a car can climb, so
-- it reads as an edge without being a barrier.
local fwd = (p1 - p0).Unit local fwd = (p1 - p0).Unit
local outN = (segMid - Vector3.new(c.X, ringY, c.Z)).Unit local outN = (segMid - Vector3.new(c.X, ringY, c.Z)).Unit
for _, sgn in ipairs({ 1, -1 }) do for _, sgn in ipairs({ 1, -1 }) do
local ePos = segMid + outN * (sgn * RING_W / 2) local ePos = segMid + outN * (sgn * RING_W / 2)
-- sgn == 1 is the OUTER edge (away from the apron), sgn == -1 the -- Skip the edge entirely where a ramp tees in, or the ring seals
-- INNER edge facing the parking lot. Skip the parapet entirely -- the PA (see above).
-- where a ramp tees in, or the ring seals the PA (see above).
local gate = (sgn == 1) and GATE_OUT or GATE_IN local gate = (sgn == 1) and GATE_OUT or GATE_IN
if not nearJunction(ePos, gate) then if not nearJunction(ePos, gate) then
local eCF = CFrame.lookAt(ePos, ePos + fwd, UP) local eCF = CFrame.lookAt(ePos, ePos + fwd, UP)
local lip = part({ if sgn == 1 then
Name = "PARingWall", local lip = part({
Size = Vector3.new(2.2, BARRIER_H, segLen + 1), Name = "PARingWall",
CFrame = eCF * CFrame.new(0, 1 + BARRIER_H / 2, 0), Size = Vector3.new(2.2, BARRIER_H, segLen + 1),
Color = C_PARAPET, CFrame = eCF * CFrame.new(0, 1 + BARRIER_H / 2, 0),
Material = Enum.Material.Concrete, Color = C_PARAPET,
}) Material = Enum.Material.Concrete,
lip.Parent = pa })
local lr = part({ lip.Parent = pa
Name = "PARingRail", local lr = part({
Size = Vector3.new(0.6, RAIL_H, segLen + 1), Name = "PARingRail",
CFrame = eCF * CFrame.new(0, 1 + BARRIER_H + RAIL_H / 2, 0), Size = Vector3.new(0.6, RAIL_H, segLen + 1),
Color = C_RAIL, CFrame = eCF * CFrame.new(0, 1 + BARRIER_H + RAIL_H / 2, 0),
Material = Enum.Material.Metal, Color = C_RAIL,
}) Material = Enum.Material.Metal,
lr.Parent = pa })
lr.Parent = pa
else
local KERB_H = 1.2
local kerb = part({
Name = "PARingKerb",
Size = Vector3.new(1.6, KERB_H, segLen + 1),
CFrame = eCF * CFrame.new(0, 1 + KERB_H / 2, 0),
Color = C_PARAPET,
Material = Enum.Material.Concrete,
})
kerb.Parent = pa
end
end end
end end
@@ -1272,7 +1322,24 @@ local function buildPA(map: Folder)
pil.Parent = pa pil.Parent = pa
end end
-- Edge kerbs so a car can't slide off the side of a raised ramp. -- Edge kerbs so a car can't slide off the side of a raised ramp.
if h > 8 then --
-- BUG (2026-08-02, found by test drive + tools/diag_ramp.luau):
-- the guard used to be `h > 8`, which is a HEIGHT test on a ramp
-- that starts at apron level. The first 2-3 segments of every ramp
-- (heightAboveApron 0.5 .. 5.6) therefore got no kerb at all, so
-- the barrier visibly stopped part-way down the ramp -- the
-- "side barriers are not actually fully finished" report. The low
-- end is not a fall hazard, but it still needs an edge or the
-- structure just ends in mid-air visually.
--
-- The other half of that report -- "barriers extend too far into
-- the main highway" -- is the opposite end: the kerb ran right to
-- the road end of the ramp, so a kerb stood IN the mainline
-- driving lane at the merge (measured: a 2.4-stud step 14 studs
-- right of the centreline, hit as Road_526 -> PARampKerb). A merge
-- has to be open, so the last segment carries no kerb.
local isMergeSeg = (s >= STEPS - 1)
if h > 2 and not isMergeSeg then
local fwd2 = (b - a) local fwd2 = (b - a)
fwd2 = Vector3.new(fwd2.X, 0, fwd2.Z) fwd2 = Vector3.new(fwd2.X, 0, fwd2.Z)
if fwd2.Magnitude > 0.01 then if fwd2.Magnitude > 0.01 then
@@ -1318,10 +1385,23 @@ local function buildPA(map: Folder)
-- outer lip walls the whole thing -- so there is no exposed edge to fall -- 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. -- off and no need for the old four-sided perimeter box.
-- SPAWN PAD PLACEMENT. This used to sit exactly on PA_CENTER, which was
-- fine while the apron centre was empty. It is NOT fine after the survey
-- landed: the OSM stall block is centred near the apron middle, so the pad
-- punched a 120x120 dark hole straight through the painted 小型 rows
-- (visible as a black rectangle in the overhead capture).
--
-- The offset below is MEASURED, not chosen: a 15-stud grid search over the
-- whole apron scored every candidate by its clearance to all 804 built PA
-- obstacles (buildings, stalls, props) while staying inside the ring road.
-- (-165, -150) is the best cell, with 93 studs of clearance -- comfortably
-- more than the pad's 60-stud half-width. It lands on the open southwest
-- circulation area, which is empty tarmac on the real site too.
local SPAWN_OFF = Vector3.new(-165, 0.3, -150)
local pad = part({ local pad = part({
Name = "SpawnPad", Name = "SpawnPad",
Size = Vector3.new(120, 2.4, 120), Size = Vector3.new(120, 2.4, 120),
CFrame = CFrame.new(c + Vector3.new(0, 0.3, 0)), CFrame = CFrame.new(c + SPAWN_OFF),
Color = Color3.fromRGB(20, 20, 26), Color = Color3.fromRGB(20, 20, 26),
Material = Enum.Material.Neon, Material = Enum.Material.Neon,
Transparency = 0.25, Transparency = 0.25,
@@ -1332,10 +1412,14 @@ local function buildPA(map: Folder)
-- and CarManager spawns the car onto the character -- so without this the -- and CarManager spawns the car onto the character -- so without this the
-- player starts ~8,600 studs off-circuit regardless of where SpawnPad is. -- player starts ~8,600 studs off-circuit regardless of where SpawnPad is.
-- Neutral + non-collidable so it never becomes an obstacle on the pad. -- Neutral + non-collidable so it never becomes an obstacle on the pad.
-- Must sit ON the pad -- it is keyed to the same measured offset, because
-- CarManager spawns the car onto the character. Leaving this at PA_CENTER
-- while the pad moved would drop the player into the middle of the 小型
-- stall rows instead of onto clear tarmac.
local spawnLoc = Instance.new("SpawnLocation") local spawnLoc = Instance.new("SpawnLocation")
spawnLoc.Name = "PASpawn" spawnLoc.Name = "PASpawn"
spawnLoc.Size = Vector3.new(24, 1, 24) spawnLoc.Size = Vector3.new(24, 1, 24)
spawnLoc.CFrame = CFrame.new(c + Vector3.new(0, 2, 0)) spawnLoc.CFrame = CFrame.new(c + Vector3.new(SPAWN_OFF.X, 2, SPAWN_OFF.Z))
spawnLoc.Transparency = 1 spawnLoc.Transparency = 1
spawnLoc.CanCollide = false spawnLoc.CanCollide = false
spawnLoc.Anchored = true spawnLoc.Anchored = true
@@ -1344,29 +1428,94 @@ local function buildPA(map: Folder)
spawnLoc.Parent = pa spawnLoc.Parent = pa
-- BUILDINGS --------------------------------------------------------------- -- BUILDINGS ---------------------------------------------------------------
-- ja.wikipedia: 本館 sits on the WEST side of the area and ベイサイドテラス -- SURVEYED LAYOUT, not art direction. Every position below is read from
-- 二番館 on the NORTH, and both are reachable from every direction. The -- OpenStreetMap and converted to studs, so the arrangement matches the real
-- 本館 is genuinely three storeys (3F closed off, 2F a rest/meeting space -- 大黒ふ頭PA rather than approximating it.
-- reopened 2025-10-30, 1F the shop/toilet block). Footprint sizes here are --
-- eyeballed to the aerial -- unlike the apron extent and stall counts, they -- Sources (fetched 2026-08-02 via the OSM API, not Overpass -- Overpass
-- are NOT measured, so treat them as art direction. -- 504s repeatedly on this bbox):
-- * way 59608205 amenity=parking 大黒ふ頭パーキングエリア -- the apron
-- * way 405966005 highway=rest_area 大黒PA (wikidata Q10938704)
-- * building ways + amenity/shop nodes within 230 m of the apron centroid
--
-- Local frame: origin = apron polygon centroid (35.461455 N, 139.680201 E),
-- +xe = EAST metres, +zn = NORTH metres. Mapped into the game world as
-- East -> +X and North -> -Z, which is the convention the rest of the map
-- already uses.
--
-- THE BIG CORRECTION: the apron is NOT the axis-aligned square the previous
-- code assumed. Measured from the polygon, it is a PARALLELOGRAM lying on a
-- SW-NE diagonal -- principal axis bearing ~37 deg, longest edge 155.8 m at
-- bearing 45.4 deg, area 1.60 ha. The 181 x 174 m figure quoted before is
-- just the bounding box of that diagonal lot, which is why buildings placed
-- on N/S/E/W edges landed in the wrong places. The two named buildings sit
-- at OPPOSITE ENDS OF THE DIAGONAL, ~175 m apart:
-- 本館 (main building) at the SOUTH-WEST end, near the neck
-- フォンターナ・ダイコク at the NORTH-EAST tip
-- That single fact is most of what makes the lot read as Daikoku.
local C_BLDG = Color3.fromRGB(214, 210, 200) local C_BLDG = Color3.fromRGB(214, 210, 200)
local C_GLASS = Color3.fromRGB(120, 150, 165) local C_GLASS = Color3.fromRGB(120, 150, 165)
local function building(name, sx, sy, sz, x, z) -- OSM local metres -> world studs.
--
-- Two corrections are folded into this transform, both forced by MEASURED
-- constraints rather than taste:
--
-- 1. ORIGIN SHIFT (+32 m North). The OSM parking polygon's centroid is not
-- the centre of the SITE -- the buildings all sit north and east of the
-- stalls, so the full site bbox spans east -87..+111 and north -53..+137.
-- Centring on the parking centroid pushed 7 of 12 features off the apron.
-- Solving for the origin that minimises the enclosing radius gives
-- (+0.0 E, +32.0 N), which is the value used here.
--
-- 2. UNIFORM SCALE (0.88). The real site needs a 113.9 m radius after the
-- shift, but the PA ring road only offers 103.0 m of clear space inside
-- it (ring centreline PA_RADIUS+130 = 390 studs, ring 44 studs wide).
-- The ring CANNOT simply be grown: the nearest mainline sample is only
-- 330 studs from PA_CENTER (measured in-engine), so the ring is already
-- wider than its clearance to the expressway and enlarging it would push
-- the loop through the road deck and break the ramp junctions.
-- Scaling the survey down by 0.88 instead fits it in the existing
-- envelope. Because the scale is UNIFORM, every distance and angle keeps
-- its true proportion -- the relative arrangement of the buildings, which
-- is what makes the lot recognisable as Daikoku, is preserved exactly.
local OSM_SHIFT_N = 32.0
local OSM_SCALE = 0.88
local function osmX(xe: number): number
return c.X + xe * OSM_SCALE * M
end
local function osmZ(zn: number): number
return c.Z - (zn - OSM_SHIFT_N) * OSM_SCALE * M
end
-- OSM edge bearing (degrees, measured as atan2(east, north) the way the
-- survey reports it) -> Roblox Y rotation. Derivation: we want the part's
-- LookVector (-sin t, 0, -cos t) to lie along the OSM direction
-- (cos a East, sin a North) = (cos a, 0, -sin a) in world axes, which
-- solves to t = a - 90 deg.
local function osmYaw(angDeg: number): number
return math.rad(angDeg - 90)
end
-- `long` runs along the building's own long axis, `wide` across it.
-- Footprints are scaled by the same OSM_SCALE as the positions, so the lot
-- stays a faithful uniform reduction rather than a stretched one.
local function building(name, long_m, height_m, wide_m, xe, zn, angDeg)
local sy = height_m * M
local cf = CFrame.new(osmX(xe), DECK_Y + sy / 2, osmZ(zn)) * CFrame.Angles(0, osmYaw(angDeg), 0)
local b = part({ local b = part({
Name = name, Name = name,
Size = Vector3.new(sx, sy, sz), Size = Vector3.new(wide_m * OSM_SCALE * M, sy, long_m * OSM_SCALE * M),
CFrame = CFrame.new(x, DECK_Y + sy / 2, z), CFrame = cf,
Color = C_BLDG, Color = C_BLDG,
Material = Enum.Material.Concrete, Material = Enum.Material.Concrete,
}) })
b.Parent = pa b.Parent = pa
-- Ground-floor glazing so it reads as a rest stop, not a warehouse. -- Ground-floor glazing so it reads as a rest stop, not a warehouse.
local glassH = math.min(4 * M, sy * 0.7)
local glass = part({ local glass = part({
Name = name .. "Glazing", Name = name .. "Glazing",
Size = Vector3.new(sx + 0.6, 4 * M, sz + 0.6), Size = Vector3.new(wide_m * OSM_SCALE * M + 0.6, glassH, long_m * OSM_SCALE * M + 0.6),
CFrame = CFrame.new(x, DECK_Y + 2.6 * M, z), CFrame = cf * CFrame.new(0, -sy / 2 + glassH / 2 + 0.2 * M, 0),
Color = C_GLASS, Color = C_GLASS,
Material = Enum.Material.Glass, Material = Enum.Material.Glass,
Transparency = 0.35, Transparency = 0.35,
@@ -1375,26 +1524,60 @@ local function buildPA(map: Folder)
return b return b
end end
-- 本館 (main building), west side, three storeys. -- 本館 (main building) -- SW end of the lot. Three storeys (1F shop/toilet
local honkanX = c.X - APRON_X / 2 + 13 * M -- block, 2F rest area reopened 2025-10-30, 3F closed). Its footprint is not
building("PAHonkan", 22 * M, 13 * M, 46 * M, honkanX, c.Z) -- mapped as a building way in OSM, so the extent is reconstructed from the
makeSign( -- tight POI cluster that sits inside it: food_court (-22,-64), Bay Side
"本館 MAIN BUILDING", -- Restaurant (-25,-62), bakery Little Marmaid (-31,-62), toilets (-22,-59),
CFrame.new(honkanX + 12 * M, DECK_Y + 15 * M, c.Z) * CFrame.Angles(0, math.pi / 2, 0), -- elevator (-34,-51), telephone (-21,-49), Blue Seal (-42,-50), cafe
pa -- Cafe de Crie (-43,-49), convenience e-Mart (-40,-45). Those span roughly
) -- x -45..-18, z -66..-43, hence a ~46 x 34 m block centred (-32,-53)
-- squared up to the 45 deg lot grid.
building("PAHonkan", 46, 13, 34, -32, -53, 45)
makeSign("本館 MAIN BUILDING", CFrame.new(osmX(-32), DECK_Y + 15 * M, osmZ(-53) + 20 * M), pa)
-- ベイサイドテラス二番館 (Bayside Terrace No.2), north side, single storey. -- フォンターナ・ダイコク / ベイサイドテラス -- NE tip. OSM way 88790141,
local nibanZ = c.Z - APRON_Z / 2 + 11 * M -- oriented bbox 28 x 43 m at bearing +43 deg, centre (+13.9, +123.1).
building("PANibankan", 34 * M, 7 * M, 13 * M, c.X + 18 * M, nibanZ) building("PANibankan", 43, 7, 28, 13.9, 123.1, 43)
makeSign("ベイサイドテラス二番館", CFrame.new(c.X + 18 * M, DECK_Y + 9 * M, nibanZ + 7 * M), pa) makeSign("フォンターナ・ダイコク", CFrame.new(osmX(13.9), DECK_Y + 9 * M, osmZ(123.1) + 16 * M), pa)
-- Remaining mapped structures, each from its own OSM building way.
building("PAToiletNorth", 21, 5, 7, -17.5, 109.8, -44) -- way 88790136
building("PAKioskWest", 16, 5, 9, -87.2, 24.5, 46) -- way 88790109
building("PAHutWest", 14, 4.5, 10, -71.9, 42.2, -45) -- way 88790185
-- Free-standing amenity nodes. Small props, but they are what stops the
-- apron reading as an empty car park.
local function prop(name, sx_m, sy_m, sz_m, xe, zn, colour, mat)
local p = part({
Name = name,
Size = Vector3.new(sx_m * OSM_SCALE * M, sy_m * M, sz_m * OSM_SCALE * M),
CFrame = CFrame.new(osmX(xe), DECK_Y + (sy_m * M) / 2, osmZ(zn)),
Color = colour,
Material = mat or Enum.Material.Metal,
})
p.Parent = pa
return p
end
-- Lawson (+21,+136.7) and the north toilet block (-3.3,+124) flank Fontana.
prop("PALawson", 14, 4, 10, 21.0, 136.7, Color3.fromRGB(70, 130, 200), Enum.Material.Concrete)
prop("PAToiletBlockN", 12, 4, 8, -3.3, 124.0, Color3.fromRGB(205, 200, 190), Enum.Material.Concrete)
-- 噴水 fountain (+5.9,+122.5) -- the photo spot on the north terrace.
prop("PAFountain", 9, 0.8, 9, 5.9, 122.5, Color3.fromRGB(150, 170, 185), Enum.Material.Slate)
-- Smoking shelters: north terrace (+1.4,+103.9) and by the main building.
prop("PASmokingN", 7, 3, 5, 1.4, 103.9, Color3.fromRGB(150, 150, 155))
prop("PASmokingS", 7, 3, 5, -47.7, -31.5, Color3.fromRGB(150, 150, 155))
-- 二輪車駐車場: motorcycles park separately, right by the main building.
prop("PAMotorcycleBay", 20, 0.05, 12, 9.0, -44.9, Color3.fromRGB(70, 70, 78), Enum.Material.Asphalt)
-- EV rapid charger: first on any Japanese expressway, installed 2008-10-10. -- EV rapid charger: first on any Japanese expressway, installed 2008-10-10.
-- OSM charging_station node sits at (-58.3, +30.2), on the west flank.
for k = 0, 1 do for k = 0, 1 do
local ev = part({ local ev = part({
Name = "PAEVCharger", Name = "PAEVCharger",
Size = Vector3.new(1.6 * M, 2.2 * M, 0.9 * M), Size = Vector3.new(1.6 * M, 2.2 * M, 0.9 * M),
CFrame = CFrame.new(honkanX + 14 * M + k * 4 * M, DECK_Y + 1.1 * M, c.Z + 26 * M), CFrame = CFrame.new(osmX(-58.3) + k * 4 * M, DECK_Y + 1.1 * M, osmZ(30.2)),
Color = Color3.fromRGB(60, 130, 90), Color = Color3.fromRGB(60, 130, 90),
Material = Enum.Material.Metal, Material = Enum.Material.Metal,
}) })
@@ -1634,6 +1817,18 @@ local function buildSkyline(map: Folder)
folder.Name = "Skyline" folder.Name = "Skyline"
folder.Parent = map folder.Parent = map
-- PA keep-out. Returns true if a `w`-wide box centred at (x,z) clears the
-- Daikoku PA island entirely. RING_OUTER mirrors buildPA's own geometry:
-- ringRadius (PA_RADIUS + 130) + half the 44-stud ring width.
local PA_C = Circuit.PA_CENTER
local RING_OUTER = Circuit.PA_RADIUS + 130 + 22
local PA_MARGIN = 60
local function paKeepout(x: number, z: number, w: number): boolean
local dx, dz = x - PA_C.X, z - PA_C.Z
local r = math.sqrt(dx * dx + dz * dz)
return r - w / 2 > RING_OUTER + PA_MARGIN
end
local samples = Circuit.samples local samples = Circuit.samples
local n = #samples local n = #samples
local placed = 0 local placed = 0
@@ -1668,6 +1863,20 @@ local function buildSkyline(map: Folder)
continue continue
end end
-- ...but `samples` is the MAINLINE centreline ONLY. The PA ring road,
-- its two ramps and the parking apron are generated separately (see
-- buildPA) and appear nowhere in `samples`, so the test above is blind
-- to the entire PA island. A 186-wide tower landed at r=428 from
-- PA_CENTER, spanning radius 335->521 and punching straight through the
-- ring road band [368,412] -- a skyscraper standing in the road.
--
-- Exclude the whole PA disc, not just the ring band: nothing generated
-- belongs inside the ring at all. Test the building's INNER FACE
-- (r - w/2) against the ring's outer kerb + the same 60-stud margin.
if not paKeepout(x, z, w) then
continue
end
local b = part({ local b = part({
Name = "Building", Name = "Building",
Size = Vector3.new(w, h, w), Size = Vector3.new(w, h, w),
@@ -1690,7 +1899,7 @@ local function buildSkyline(map: Folder)
snear = d snear = d
end end
end end
if snear > CLEAR then if snear > CLEAR and paKeepout(sx, z, 12) then
local sign = part({ local sign = part({
Name = "NeonSign", Name = "NeonSign",
Size = Vector3.new(12, h * 0.42, 5), Size = Vector3.new(12, h * 0.42, 5),
+13 -2
View File
@@ -893,9 +893,20 @@ end
-- heights. Everything downstream (deck, median, barrier, kill wall, lane -- heights. Everything downstream (deck, median, barrier, kill wall, lane
-- paint, ramps) is built from sample.pos, so lifting the samples lifts the -- paint, ramps) is built from sample.pos, so lifting the samples lifts the
-- whole structure coherently. -- whole structure coherently.
Circuit.PA_LIFT = 38 -- studs of clearance over the apron (a car is ~7 tall) -- 2026-08-02: raised 38 -> 56. 38 studs (~10.6 m) was less than the height of
-- the structures standing on the apron, so the mainline deck cut straight
-- through them -- PAHonkan (13 m / 46.4 studs) surfaced through Road_536..539,
-- which is the "building sitting on the road" artefact. Clearance must exceed
-- the TALLEST thing under the deck, not just a car. Honkan top sits 46.4 studs
-- above the apron, so the slab underside (46 + PA_LIFT - 1) needs PA_LIFT > 48.
-- 56 gives underside 101 -- 7.6 studs of daylight over the roof. This is also
-- closer to the real Bayshore Route viaduct, which clears the PA by ~15-20 m,
-- not 10. Verify with tools/diag_clearance.luau after changing.
Circuit.PA_LIFT = 56 -- studs of clearance over the apron (tallest = Honkan 46.4)
local LIFT_FLAT = 20 -- samples held at full lift, centred on the PA local LIFT_FLAT = 20 -- samples held at full lift, centred on the PA
local LIFT_RAMP = 18 -- samples of smooth transition on each side -- Widened 18 -> 26 alongside the lift increase: the climb is spread over more
-- samples so the grade stays where it was instead of getting 47% steeper.
local LIFT_RAMP = 26 -- samples of smooth transition on each side
do do
local c = Circuit.PA_CENTER local c = Circuit.PA_CENTER
local n = #samples local n = #samples