Three iterations of the Daikoku PA ramps never read as a real
interchange. Measured why, rather than tuning further:
* the entry and exit decks INTERSECTED -- 2547 sample pairs within
30 studs horizontally at <12 studs dY, worst case 0.5 studs apart
at dY=0.0. On screen that is the bowtie tangle of white rails.
* both real Daikoku ramps turn the SAME way (clockwise; the guide
video says "right, right"). This build mirrored them into each
other.
* the real arc wraps OUTSIDE the lot and forms its north/east
boundary. This build cut across the apron interior.
Gated behind BUILD_PA_RAMPS=false rather than deleted -- the radius
was already correct (470 studs vs an OSM-measured mean of 469) and
the merge lands tangent at 0.1-0.2 deg divergence. Both worth keeping.
Also removed two leftovers that would have outlived the ramps:
* rampOpen punched holes in the mainline's outer parapet, railing
and kill wall for ramps that no longer exist -- a barrier gap with
a drop behind it. Sealed via RAMP_EDGE_OPENINGS=false. Verified:
Barrier/EdgeRail/KillWall = 1646 each = 823 samples x 2 edges,
0 road samples without a guard within 90 studs.
* gore zebra hatching painted 1024 bars + 64 boundary lines marking
a junction that is not there. Sealed via RAMP_GORE_MARKINGS=false.
Verified after a cache-busting fresh rebuild (Studio's require() cache
otherwise serves the previous map and reports a false pass):
parts 22628 -> 15988
all ramp part classes = 0
Road = 823 (unchanged), spawn pad + PASpawn intact
clip probe TRUSTWORTHY (control +11/-0), CLIPPING=0
Research and rebuild spec kept for when this is revisited:
reference/ramps/BUILD_SPEC.md, INDEX.md (10 CC-licensed Commons
photos), docs/daikoku-reference-spec.md (OSM circle fits).
146 lines
3.4 KiB
Luau
146 lines
3.4 KiB
Luau
-- Diagnose the two reported defects:
|
|
-- (1) ramps not accessible in either direction (connectivity)
|
|
-- (2) ramps pass through buildings (clipping)
|
|
-- Uses native GetPartBoundsInBox so this stays fast on ~22k parts.
|
|
local ws = game:GetService("Workspace")
|
|
local map = ws:FindFirstChild("Map")
|
|
if not map then
|
|
return "NO MAP"
|
|
end
|
|
|
|
local BUILDING = {
|
|
Building = true,
|
|
TokyoTower = true,
|
|
TowerDeck = true,
|
|
TowerAntenna = true,
|
|
ShiodomeTower = true,
|
|
TowerCrown = true,
|
|
DietTower = true,
|
|
RoppongiHills = true,
|
|
TokyoStation = true,
|
|
StationDome = true,
|
|
Skytree = true,
|
|
GinzaBlock = true,
|
|
NationalDiet = true,
|
|
BridgeTower = true,
|
|
TowerCrossbeam = true,
|
|
Structure = true,
|
|
PalaceWall = true,
|
|
}
|
|
|
|
local ramp, drivable = {}, {}
|
|
for _, d in ipairs(map:GetDescendants()) do
|
|
if d:IsA("BasePart") then
|
|
local n = d.Name
|
|
if n:find("PAEntryRamp") or n:find("PAExitRamp") then
|
|
table.insert(ramp, d)
|
|
elseif n:match("^Road_%d+$") or n == "PAFloor" then
|
|
table.insert(drivable, d)
|
|
end
|
|
end
|
|
end
|
|
if #ramp == 0 then
|
|
return "no ramp parts"
|
|
end
|
|
|
|
-- ---------- (2) BUILDING CLIP CHECK ----------
|
|
local op = OverlapParams.new()
|
|
op.MaxParts = 40
|
|
local clipHits, clipNames, worst = 0, {}, {}
|
|
for _, r in ipairs(ramp) do
|
|
local hits = ws:GetPartBoundsInBox(r.CFrame, r.Size, op)
|
|
for _, h in ipairs(hits) do
|
|
if h ~= r and BUILDING[h.Name] then
|
|
clipHits += 1
|
|
clipNames[h.Name] = (clipNames[h.Name] or 0) + 1
|
|
if #worst < 5 then
|
|
table.insert(worst, ("%s@%.0f,%.0f,%.0f"):format(r.Name, r.Position.X, r.Position.Y, r.Position.Z))
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
-- CONTROL: same probe against mainline Road_ parts (known-good, should be ~0)
|
|
local ctrlHits, ctrlN = 0, 0
|
|
for i = 1, #drivable, 9 do
|
|
local d = drivable[i]
|
|
ctrlN += 1
|
|
local hits = ws:GetPartBoundsInBox(d.CFrame, d.Size, op)
|
|
for _, h in ipairs(hits) do
|
|
if h ~= d and BUILDING[h.Name] then
|
|
ctrlHits += 1
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
local cn = {}
|
|
for k, v in pairs(clipNames) do
|
|
table.insert(cn, k .. "=" .. v)
|
|
end
|
|
|
|
-- ---------- (1) CONNECTIVITY ----------
|
|
-- Ramp basename groups, then endpoints of each group by trailing index.
|
|
local groups = {}
|
|
for _, r in ipairs(ramp) do
|
|
local base, idx = r.Name:match("^(.-)_?(%d+)$")
|
|
base = base or r.Name
|
|
idx = tonumber(idx) or 0
|
|
local g = groups[base]
|
|
if not g then
|
|
g = { lo = math.huge, hi = -math.huge, loP = nil, hiP = nil, n = 0 }
|
|
groups[base] = g
|
|
end
|
|
g.n += 1
|
|
if idx < g.lo then
|
|
g.lo, g.loP = idx, r
|
|
end
|
|
if idx > g.hi then
|
|
g.hi, g.hiP = idx, r
|
|
end
|
|
end
|
|
|
|
-- For an endpoint, find nearest drivable surface and report gap + height delta.
|
|
local function nearestDrivable(p)
|
|
local bd, bp = math.huge, nil
|
|
for _, d in ipairs(drivable) do
|
|
local dd = (d.Position - p).Magnitude
|
|
if dd < bd then
|
|
bd, bp = dd, d
|
|
end
|
|
end
|
|
return bd, bp
|
|
end
|
|
|
|
local lines = {}
|
|
for base, g in pairs(groups) do
|
|
if g.loP and g.hiP then
|
|
local d1, p1 = nearestDrivable(g.loP.Position)
|
|
local d2, p2 = nearestDrivable(g.hiP.Position)
|
|
table.insert(
|
|
lines,
|
|
("%s n=%d | START gap=%.0f dY=%.1f to %s | END gap=%.0f dY=%.1f to %s"):format(
|
|
base,
|
|
g.n,
|
|
d1,
|
|
p1 and (g.loP.Position.Y - p1.Position.Y) or -999,
|
|
p1 and p1.Name or "nil",
|
|
d2,
|
|
p2 and (g.hiP.Position.Y - p2.Position.Y) or -999,
|
|
p2 and p2.Name or "nil"
|
|
)
|
|
)
|
|
end
|
|
end
|
|
|
|
return ("CLIP ramp=%d/%d parts hit buildings [%s] samples=[%s] || CONTROL road %d/%d hit\n%s"):format(
|
|
clipHits,
|
|
#ramp,
|
|
table.concat(cn, ", "),
|
|
table.concat(worst, " ; "),
|
|
ctrlHits,
|
|
ctrlN,
|
|
table.concat(lines, "\n")
|
|
)
|