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:
+168
-11
@@ -923,25 +923,110 @@ local function buildPA(map: Folder)
|
|||||||
})
|
})
|
||||||
slab.Parent = pa
|
slab.Parent = pa
|
||||||
|
|
||||||
-- painted bays
|
-- REAL DAIKOKU FUTO PA LAYOUT ---------------------------------------------
|
||||||
for i = -4, 4 do
|
-- Footprint measured from OpenStreetMap way 59608205 (大黒ふ頭パーキングエリア):
|
||||||
local bay = part({
|
-- the apron is 181 m x 174 m (1.6 ha) -- a big, essentially SQUARE lot, not
|
||||||
Name = "Bay",
|
-- a circular one. The spiral ramps sweep around and over it, which is the
|
||||||
Size = Vector3.new(1, 2.2, 90),
|
-- "螺旋状の道路" the PA is famous for as a photo backdrop.
|
||||||
CFrame = CFrame.new(c + Vector3.new(i * 34, 0.1, 0)),
|
--
|
||||||
Color = Color3.fromRGB(235, 235, 240),
|
-- 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,
|
Material = Enum.Material.SmoothPlastic,
|
||||||
})
|
})
|
||||||
bay.Parent = pa
|
m.Parent = pa
|
||||||
|
return m
|
||||||
end
|
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 _, 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 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, 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),
|
Color = Color3.fromRGB(70, 70, 78),
|
||||||
Material = Enum.Material.Metal,
|
Material = Enum.Material.Metal,
|
||||||
})
|
})
|
||||||
@@ -949,7 +1034,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(c + Vector3.new(sx * (R - 30), 60, sz * (R - 30))),
|
CFrame = CFrame.new(lx, DECK_Y + 60, lz),
|
||||||
Color = Color3.fromRGB(255, 240, 200),
|
Color = Color3.fromRGB(255, 240, 200),
|
||||||
Material = Enum.Material.Neon,
|
Material = Enum.Material.Neon,
|
||||||
})
|
})
|
||||||
@@ -1088,6 +1173,78 @@ local function buildPA(map: Folder)
|
|||||||
spawnLoc.Duration = 0
|
spawnLoc.Duration = 0
|
||||||
spawnLoc.Parent = pa
|
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)
|
makeSign("大黒PA DAIKOKU PA", CFrame.new(c + Vector3.new(0, 26, -R + 10)), pa)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -59,13 +59,17 @@ print(call('local rs=game:GetService("ReplicatedStorage")\n'
|
|||||||
'return "buffer reset"'))
|
'return "buffer reset"'))
|
||||||
|
|
||||||
for i, c in enumerate(chunks):
|
for i, c in enumerate(chunks):
|
||||||
|
# Lua long-bracket literals SWALLOW a newline that immediately follows the
|
||||||
|
# opening bracket. When a chunk happened to start with "\n" that byte was
|
||||||
|
# silently lost, giving assembled=65158 vs expected=65159. Emit a deliberate
|
||||||
|
# leading newline for Lua to eat so the payload always survives intact.
|
||||||
code = (
|
code = (
|
||||||
'local rs=game:GetService("ReplicatedStorage")\n'
|
'local rs=game:GetService("ReplicatedStorage")\n'
|
||||||
'local b=rs:WaitForChild("MBBuf")\n'
|
'local b=rs:WaitForChild("MBBuf")\n'
|
||||||
f'local n="p{i}"\n'
|
f'local n="p{i}"\n'
|
||||||
"local sv=b:FindFirstChild(n) or Instance.new(\"StringValue\")\n"
|
"local sv=b:FindFirstChild(n) or Instance.new(\"StringValue\")\n"
|
||||||
"sv.Name=n sv.Parent=b\n"
|
"sv.Name=n sv.Parent=b\n"
|
||||||
f"sv.Value=[{eq}[{c}]{eq}]\n"
|
f"sv.Value=[{eq}[\n{c}]{eq}]\n"
|
||||||
f'return "p{i} "..#sv.Value'
|
f'return "p{i} "..#sv.Value'
|
||||||
)
|
)
|
||||||
print(f" chunk {i+1}/{len(chunks)}: {call(code)}")
|
print(f" chunk {i+1}/{len(chunks)}: {call(code)}")
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
-- Verify the Daikoku Futo PA rebuild against SOURCED real-world figures.
|
||||||
|
--
|
||||||
|
-- Sources:
|
||||||
|
-- * ja.wikipedia 大黒パーキングエリア -- 小型 341 / 大型 59 / 身障者用 4 = 404
|
||||||
|
-- * OpenStreetMap way 59608205 -- apron 181 m x 174 m
|
||||||
|
--
|
||||||
|
-- Runs on a CLONE of MapBuilder so it never disturbs the live map, matching
|
||||||
|
-- the pattern used by verify_c1_fixes3.
|
||||||
|
local ServerScriptService = game:GetService("ServerScriptService")
|
||||||
|
local out = {}
|
||||||
|
local function log(...)
|
||||||
|
table.insert(out, table.concat({ ... }, " "))
|
||||||
|
end
|
||||||
|
local pass, fail = 0, 0
|
||||||
|
local function check(name, ok, detail)
|
||||||
|
if ok then
|
||||||
|
pass += 1
|
||||||
|
log("PASS", name, detail or "")
|
||||||
|
else
|
||||||
|
fail += 1
|
||||||
|
log("FAIL", name, detail or "")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local src = ServerScriptService:FindFirstChild("Server")
|
||||||
|
src = src and src:FindFirstChild("MapBuilder") or ServerScriptService:FindFirstChild("MapBuilder", true)
|
||||||
|
if not src then
|
||||||
|
return "FATAL: MapBuilder module not found"
|
||||||
|
end
|
||||||
|
local MapBuilder = require(src:Clone())
|
||||||
|
local map = MapBuilder.build and MapBuilder.build() or MapBuilder.Build()
|
||||||
|
local pa = map:FindFirstChild("ParkingArea", true)
|
||||||
|
if not pa then
|
||||||
|
return "FATAL: ParkingArea folder not built"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 1. Capacity must be EXACTLY the published numbers -------------------------
|
||||||
|
local cap = pa:FindFirstChild("PACapacity")
|
||||||
|
check("capacity value present", cap ~= nil, cap and cap.Value or "missing")
|
||||||
|
if cap then
|
||||||
|
check("capacity == 341/59/4/404", cap.Value == "small=341,large=59,accessible=4,total=404", cap.Value)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 2. Stall line counts must agree with those numbers ------------------------
|
||||||
|
-- markRow paints n+1 divider lines per row of n stalls.
|
||||||
|
local counts = {}
|
||||||
|
for _, d in ipairs(pa:GetDescendants()) do
|
||||||
|
counts[d.Name] = (counts[d.Name] or 0) + 1
|
||||||
|
end
|
||||||
|
-- 小型: 11 rows x (31+1) = 352 dividers, 11 head lines
|
||||||
|
check("small stall dividers = 352", counts.StallLine == 352, tostring(counts.StallLine))
|
||||||
|
check("small stall head lines = 11", counts.StallHead == 11, tostring(counts.StallHead))
|
||||||
|
-- 大型: rows of 20,20,19 -> 21+21+20 = 62 dividers, 3 heads
|
||||||
|
check("large stall dividers = 62", counts.BigStallLine == 62, tostring(counts.BigStallLine))
|
||||||
|
check("large stall head lines = 3", counts.BigStallHead == 3, tostring(counts.BigStallHead))
|
||||||
|
-- 身障者用: 1 row of 4 -> 5 dividers
|
||||||
|
check("accessible dividers = 5", counts.AccStallLine == 5, tostring(counts.AccStallLine))
|
||||||
|
|
||||||
|
-- 3. Parking geometry must sit ON the apron slab ---------------------------
|
||||||
|
-- NOTE: scoped to parking furniture only. The first version of this test swept
|
||||||
|
-- every descendant and flagged PARingWall/PARingRail -- the ELEVATED ring road,
|
||||||
|
-- which is supposed to sit at/over the perimeter. That was a bad test, not a
|
||||||
|
-- bad build.
|
||||||
|
local floor = pa:FindFirstChild("PAFloor")
|
||||||
|
check("PAFloor exists", floor ~= nil)
|
||||||
|
local function isParkingFurniture(d)
|
||||||
|
return d.Name:find("Stall")
|
||||||
|
or d.Name:find("PAHonkan")
|
||||||
|
or d.Name:find("PANibankan")
|
||||||
|
or d.Name == "PAEVCharger"
|
||||||
|
or d.Name == "PALowCarBump"
|
||||||
|
or d.Name == "PAMast"
|
||||||
|
end
|
||||||
|
local offSlab, worst = 0, 0
|
||||||
|
if floor then
|
||||||
|
local hx, hz = floor.Size.X / 2, floor.Size.Z / 2
|
||||||
|
local fc = floor.Position
|
||||||
|
for _, d in ipairs(pa:GetDescendants()) do
|
||||||
|
if d:IsA("BasePart") and d ~= floor and isParkingFurniture(d) then
|
||||||
|
local dx = math.abs(d.Position.X - fc.X) + d.Size.X / 2
|
||||||
|
local dz = math.abs(d.Position.Z - fc.Z) + d.Size.Z / 2
|
||||||
|
local over = math.max(dx - hx, dz - hz)
|
||||||
|
if over > 0.5 then
|
||||||
|
offSlab += 1
|
||||||
|
worst = math.max(worst, over)
|
||||||
|
if offSlab <= 5 then
|
||||||
|
log(" off-slab:", d.Name, string.format("by %.1f studs", over))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
check(
|
||||||
|
"no parking geometry overhangs the apron",
|
||||||
|
offSlab == 0,
|
||||||
|
string.format("%d parts, worst %.1f studs", offSlab, worst)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- 4. The 小型 block must not collide with the 大型 block --------------------
|
||||||
|
-- The original test compared consecutive head-line gaps, which is always 0 for
|
||||||
|
-- back-to-back rows (they share a nose line) -- meaningless. It also hid a REAL
|
||||||
|
-- 42-stud overlap between the small rows and the truck bays. Compare Z spans.
|
||||||
|
local function zSpan(prefix)
|
||||||
|
local lo, hi = math.huge, -math.huge
|
||||||
|
for _, d in ipairs(pa:GetDescendants()) do
|
||||||
|
if d:IsA("BasePart") and d.Name:sub(1, #prefix) == prefix then
|
||||||
|
lo = math.min(lo, d.Position.Z - d.Size.Z / 2)
|
||||||
|
hi = math.max(hi, d.Position.Z + d.Size.Z / 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return lo, hi
|
||||||
|
end
|
||||||
|
local _, sHi = zSpan("Stall")
|
||||||
|
local bLo, bHi = zSpan("BigStall")
|
||||||
|
check(
|
||||||
|
"小型 block clears 大型 block",
|
||||||
|
sHi < bLo,
|
||||||
|
string.format("small ends %.1f, large starts %.1f (gap %.1f)", sHi, bLo, bLo - sHi)
|
||||||
|
)
|
||||||
|
check(
|
||||||
|
"大型 block within apron",
|
||||||
|
floor and bHi < floor.Position.Z + floor.Size.Z / 2,
|
||||||
|
string.format("large ends %.1f", bHi)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- 5. Buildings on the correct sides -----------------------------------------
|
||||||
|
local honkan = pa:FindFirstChild("PAHonkan")
|
||||||
|
local niban = pa:FindFirstChild("PANibankan")
|
||||||
|
check("本館 exists", honkan ~= nil)
|
||||||
|
check("二番館 exists", niban ~= nil)
|
||||||
|
if honkan and floor then
|
||||||
|
check(
|
||||||
|
"本館 on the WEST side",
|
||||||
|
honkan.Position.X < floor.Position.X,
|
||||||
|
string.format("dx %.0f", honkan.Position.X - floor.Position.X)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
if niban and floor then
|
||||||
|
check(
|
||||||
|
"二番館 on the NORTH side",
|
||||||
|
niban.Position.Z < floor.Position.Z,
|
||||||
|
string.format("dz %.0f", niban.Position.Z - floor.Position.Z)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 6. Nothing may intersect the buildings ------------------------------------
|
||||||
|
local clash = 0
|
||||||
|
for _, b in ipairs({ honkan, niban }) do
|
||||||
|
if b then
|
||||||
|
local hx, hz = b.Size.X / 2, b.Size.Z / 2
|
||||||
|
for _, d in ipairs(pa:GetDescendants()) do
|
||||||
|
if d:IsA("BasePart") and d ~= b and d.Name:find("Stall") then
|
||||||
|
local dx = math.abs(d.Position.X - b.Position.X) - (hx + d.Size.X / 2)
|
||||||
|
local dz = math.abs(d.Position.Z - b.Position.Z) - (hz + d.Size.Z / 2)
|
||||||
|
if dx < 0 and dz < 0 then
|
||||||
|
clash += 1
|
||||||
|
if clash <= 5 then
|
||||||
|
log(" clash:", d.Name, b.Name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
check("no stalls inside a building", clash == 0, tostring(clash) .. " overlaps")
|
||||||
|
|
||||||
|
-- 7. Documented amenities ---------------------------------------------------
|
||||||
|
check("EV chargers present (2008 first-in-Japan)", (counts.PAEVCharger or 0) >= 1, tostring(counts.PAEVCharger))
|
||||||
|
check("low-car bumps present (段差)", (counts.PALowCarBump or 0) >= 1, tostring(counts.PALowCarBump))
|
||||||
|
check("light masts = 4", counts.PAMast == 4, tostring(counts.PAMast))
|
||||||
|
check("PASpawn survived", pa:FindFirstChildOfClass("SpawnLocation") ~= nil)
|
||||||
|
|
||||||
|
map:Destroy()
|
||||||
|
log("")
|
||||||
|
log(string.format("RESULT %d passed, %d failed", pass, fail))
|
||||||
|
return table.concat(out, "\n")
|
||||||
Reference in New Issue
Block a user