Rebuild Daikoku Futo PA from measured real-world data

The PA was a placeholder: 9 painted stripes and 4 lights on a slab.
Rebuilt it against sourced figures rather than recollection.

Sources:
  * OpenStreetMap way 59608205 (大黒ふ頭パーキングエリア) -- the apron is
    181 m x 174 m / 1.6 ha, i.e. essentially SQUARE, not circular.
  * ja.wikipedia 大黒パーキングエリア -- 首都高速で最大のPA, opened
    1989-09-27. Capacity 小型 341 + 大型 59 + 身障者用 4 = 404, now
    reproduced EXACTLY (11 rows x 31, 20/20/19, 1 row x 4).
  * 本館 on the west side, ベイサイドテラス二番館 on the north, per the
    same article. EV rapid charger (first on a Japanese expressway,
    2008-10-10) and the 段差 bumps retrofitted to deter lowered cars.

Stall dimensions (小型 2.5x5.0 m, 大型 3.3x12 m, 6 m aisles) are
conventional JP parking-design values and are commented as NOT
primary-sourced, unlike the 全標協 road markings. Building footprints
are art-directed to the aerial and flagged as such.

tools/verify_daikoku_pa.luau asserts capacity, containment, block
separation and building placement in-engine: 20 passed, 0 failed.

Two bugs found and fixed by that harness:
  * The hand-placed 小型 Z origin overlapped the 大型 bays by 42 studs.
    The origin is now DERIVED from the 二番館 frontage; clearance is
    61.4 studs. The first version of the overlap test compared
    consecutive head lines (always 0 for back-to-back rows), which was
    meaningless and hid this.
  * push_mapbuilder.py lost a byte whenever a chunk began with a
    newline -- Lua long-bracket literals swallow a newline directly
    after the opening bracket (assembled=65158 vs 65159). It now emits
    a deliberate leading newline; the push verifies match=true.
This commit is contained in:
2026-08-01 19:26:48 -04:00
parent 7d368d5444
commit 8808b919c3
3 changed files with 349 additions and 12 deletions
+168 -11
View File
@@ -923,25 +923,110 @@ local function buildPA(map: Folder)
})
slab.Parent = pa
-- painted bays
for i = -4, 4 do
local bay = part({
Name = "Bay",
Size = Vector3.new(1, 2.2, 90),
CFrame = CFrame.new(c + Vector3.new(i * 34, 0.1, 0)),
Color = Color3.fromRGB(235, 235, 240),
-- REAL DAIKOKU FUTO PA LAYOUT ---------------------------------------------
-- Footprint measured from OpenStreetMap way 59608205 (大黒ふ頭パーキングエリア):
-- the apron is 181 m x 174 m (1.6 ha) -- a big, essentially SQUARE lot, not
-- a circular one. The spiral ramps sweep around and over it, which is the
-- "螺旋状の道路" the PA is famous for as a photo backdrop.
--
-- Capacity from ja.wikipedia 大黒パーキングエリア (首都高速で最大のPA, opened
-- 1989-09-27): 小型 341 + 大型 59 + 身障者用 4 = 404 spaces. Those three
-- counts are reproduced EXACTLY below rather than approximated, because the
-- lot's character is the sheer amount of parking.
--
-- Stall dimensions (小型 2.5 x 5.0 m, 大型 3.3 x 12 m, 身障者用 3.5 x 5.0 m,
-- 6 m aisles) are conventional Japanese parking-design values -- NOT read
-- from a primary standard, unlike the road markings. Paint is 15 cm, which
-- does match the 全標協 line width used on the mainline.
local APRON_X = 181 * M
local APRON_Z = 174 * M
local STALL_W = 2.5 * M
local STALL_D = 5.0 * M
local BIG_W = 3.3 * M
local BIG_D = 12.0 * M
local ACC_W = 3.5 * M
local AISLE = 6.0 * M
local LINE_W = 0.15 * M
local DECK_Y = c.Y + 1 -- top face of the apron slab
local C_PAINT = Color3.fromRGB(235, 235, 240)
local function paint(name, size, x, z, colour)
local m = part({
Name = name,
Size = size,
CFrame = CFrame.new(x, DECK_Y + 0.1, z),
Color = colour or C_PAINT,
Material = Enum.Material.SmoothPlastic,
})
bay.Parent = pa
m.Parent = pa
return m
end
-- light towers
-- One row of stalls: n+1 divider lines perpendicular to the aisle, plus the
-- head line the cars nose up to. `sign` is the direction the stalls extend
-- away from the aisle edge.
local function markRow(name, x0, zEdge, n, w, d, sign)
local zMid = zEdge + sign * d / 2
for k = 0, n do
paint(name .. "Line", Vector3.new(LINE_W, 2.2, d), x0 + k * w, zMid)
end
paint(name .. "Head", Vector3.new(n * w, 2.2, LINE_W), x0 + n * w / 2, zEdge)
end
-- 小型 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
-- of the 二番館 frontage and must finish north of the 大型 rows. Placing it by
-- hand overlapped the truck bays by 42 studs -- see verify_daikoku_pa.
local SMALL_PER_ROW = 31
local SMALL_ROWS = 11 -- 31 x 11 = 341 exactly
local smallX0 = c.X - 20 * M
local pairPitch = 2 * STALL_D + AISLE
local nibanFace = c.Z - APRON_Z / 2 + 11 * M + (13 * M) / 2
local smallZ0 = nibanFace + STALL_D + 8
local placedSmall = 0
for row = 0, SMALL_ROWS - 1 do
local pairIdx = math.floor(row / 2)
local zc = smallZ0 + pairIdx * pairPitch
local sign = (row % 2 == 0) and -1 or 1
markRow("Stall", smallX0, zc, SMALL_PER_ROW, STALL_W, STALL_D, sign)
placedSmall += SMALL_PER_ROW
end
-- 大型 59: trucks and buses park along the south edge where the bays are
-- long enough -- three rows of 20/20/19.
local bigZ = c.Z + APRON_Z / 2 - BIG_D - 14
local bigCounts = { 20, 20, 19 } -- = 59
local placedBig = 0
for r, n in ipairs(bigCounts) do
markRow("BigStall", c.X - APRON_X / 2 + 18 * M, bigZ - (r - 1) * (BIG_D + AISLE), n, BIG_W, BIG_D, 1)
placedBig += n
end
-- 身障者用 4: hard against the 本館 entrance, as they are in reality.
local accX = c.X - APRON_X / 2 + 34 * M
markRow("AccStall", accX, c.Z - 2 * ACC_W, 4, ACC_W, STALL_D, 1)
local placedAcc = 4
-- Recorded so the headless verifier can assert the real capacity.
local cap = Instance.new("StringValue")
cap.Name = "PACapacity"
cap.Value = ("small=%d,large=%d,accessible=%d,total=%d"):format(
placedSmall,
placedBig,
placedAcc,
placedSmall + placedBig + placedAcc
)
cap.Parent = pa
-- Light towers at the apron corners.
for _, sx in ipairs({ -1, 1 }) do
for _, sz in ipairs({ -1, 1 }) do
local lx = c.X + sx * (APRON_X / 2 - 10 * M)
local lz = c.Z + sz * (APRON_Z / 2 - 10 * M)
local mast = part({
Name = "PAMast",
Size = Vector3.new(3, 60, 3),
CFrame = CFrame.new(c + Vector3.new(sx * (R - 30), 30, sz * (R - 30))),
CFrame = CFrame.new(lx, DECK_Y + 30, lz),
Color = Color3.fromRGB(70, 70, 78),
Material = Enum.Material.Metal,
})
@@ -949,7 +1034,7 @@ local function buildPA(map: Folder)
local head = part({
Name = "PALight",
Size = Vector3.new(10, 2, 10),
CFrame = CFrame.new(c + Vector3.new(sx * (R - 30), 60, sz * (R - 30))),
CFrame = CFrame.new(lx, DECK_Y + 60, lz),
Color = Color3.fromRGB(255, 240, 200),
Material = Enum.Material.Neon,
})
@@ -1088,6 +1173,78 @@ local function buildPA(map: Folder)
spawnLoc.Duration = 0
spawnLoc.Parent = pa
-- BUILDINGS ---------------------------------------------------------------
-- ja.wikipedia: 本館 sits on the WEST side of the area and ベイサイドテラス
-- 二番館 on the NORTH, and both are reachable from every direction. The
-- 本館 is genuinely three storeys (3F closed off, 2F a rest/meeting space
-- 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
-- are NOT measured, so treat them as art direction.
local C_BLDG = Color3.fromRGB(214, 210, 200)
local C_GLASS = Color3.fromRGB(120, 150, 165)
local function building(name, sx, sy, sz, x, z)
local b = part({
Name = name,
Size = Vector3.new(sx, sy, sz),
CFrame = CFrame.new(x, DECK_Y + sy / 2, z),
Color = C_BLDG,
Material = Enum.Material.Concrete,
})
b.Parent = pa
-- Ground-floor glazing so it reads as a rest stop, not a warehouse.
local glass = part({
Name = name .. "Glazing",
Size = Vector3.new(sx + 0.6, 4 * M, sz + 0.6),
CFrame = CFrame.new(x, DECK_Y + 2.6 * M, z),
Color = C_GLASS,
Material = Enum.Material.Glass,
Transparency = 0.35,
})
glass.Parent = pa
return b
end
-- 本館 (main building), west side, three storeys.
local honkanX = c.X - APRON_X / 2 + 13 * M
building("PAHonkan", 22 * M, 13 * M, 46 * M, honkanX, c.Z)
makeSign(
"本館 MAIN BUILDING",
CFrame.new(honkanX + 12 * M, DECK_Y + 15 * M, c.Z) * CFrame.Angles(0, math.pi / 2, 0),
pa
)
-- ベイサイドテラス二番館 (Bayside Terrace No.2), north side, single storey.
local nibanZ = c.Z - APRON_Z / 2 + 11 * M
building("PANibankan", 34 * M, 7 * M, 13 * M, c.X + 18 * M, nibanZ)
makeSign("ベイサイドテラス二番館", CFrame.new(c.X + 18 * M, DECK_Y + 9 * M, nibanZ + 7 * M), pa)
-- EV rapid charger: first on any Japanese expressway, installed 2008-10-10.
for k = 0, 1 do
local ev = part({
Name = "PAEVCharger",
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),
Color = Color3.fromRGB(60, 130, 90),
Material = Enum.Material.Metal,
})
ev.Parent = pa
end
-- 段差: the real PA had bumps retrofitted specifically to keep illegally
-- lowered 車高短 cars out. Deliberately shallow here (~0.1 m) -- enough to
-- be visible and to unsettle a stiff car, not enough to beach anything.
for k = -1, 1 do
local bump = part({
Name = "PALowCarBump",
Size = Vector3.new(26 * M, 0.1 * M, 0.8 * M),
CFrame = CFrame.new(c.X + k * 28 * M, DECK_Y + 0.05 * M, c.Z + APRON_Z / 2 - 4 * M),
Color = Color3.fromRGB(200, 190, 90),
Material = Enum.Material.Concrete,
})
bump.Parent = pa
end
makeSign("大黒PA DAIKOKU PA", CFrame.new(c + Vector3.new(0, 26, -R + 10)), pa)
end