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
+5 -1
View File
@@ -59,13 +59,17 @@ print(call('local rs=game:GetService("ReplicatedStorage")\n'
'return "buffer reset"'))
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 = (
'local rs=game:GetService("ReplicatedStorage")\n'
'local b=rs:WaitForChild("MBBuf")\n'
f'local n="p{i}"\n'
"local sv=b:FindFirstChild(n) or Instance.new(\"StringValue\")\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'
)
print(f" chunk {i+1}/{len(chunks)}: {call(code)}")
+176
View File
@@ -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")