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).
147 lines
4.0 KiB
Luau
147 lines
4.0 KiB
Luau
-- CLIP PROBE v3 -- INVERTED SCOPE.
|
|
--
|
|
-- v2 tested drivable parts against a HARDCODED WHITELIST of 16 building
|
|
-- names (Building, TokyoTower, GinzaBlock, ...). It reported CLIPPING=0 with
|
|
-- a passing control, and I reported that as fact. The user can see ramps
|
|
-- going through buildings, so the zero was false.
|
|
--
|
|
-- The bug is the whitelist: any obstacle whose name isn't one of those 16 is
|
|
-- invisible to the test. The PA structures (shops, kiosks, canopies) are the
|
|
-- prime suspect -- they sit exactly where the ramp ends land.
|
|
--
|
|
-- v3 inverts the scope: an obstacle is ANY part that is not drivable and not
|
|
-- infrastructure (pier/rail/sign/ground). No name list to go stale.
|
|
-- It also reports offenders GROUPED BY OBSTACLE NAME, so the answer names
|
|
-- the thing being speared instead of just counting.
|
|
|
|
local ws = game:GetService("Workspace")
|
|
local map = ws:FindFirstChild("Map")
|
|
if not map then
|
|
return "NO MAP"
|
|
end
|
|
|
|
local function isDrivable(n)
|
|
return n:match("^Road_")
|
|
or n:find("Ramp")
|
|
or n == "PAFloor"
|
|
or n:match("^Apron")
|
|
or n:match("^Slip")
|
|
or n:match("^Merge")
|
|
end
|
|
|
|
-- things that are ALLOWED to intersect a road (structure, decor, markings)
|
|
local function isInfra(n)
|
|
return n:find("Pier")
|
|
or n:find("Pillar")
|
|
or n:find("Column")
|
|
or n:find("Support")
|
|
or n:find("Beam")
|
|
or n:find("Rail")
|
|
or n:find("Guard")
|
|
or n:find("Barrier")
|
|
or n:find("Sign")
|
|
or n:find("Light")
|
|
or n:find("Lamp")
|
|
or n:find("Line")
|
|
or n:find("Mark")
|
|
or n:find("Stripe")
|
|
or n:find("Deck")
|
|
or n:find("Water")
|
|
or n == "Ground"
|
|
or n:find("Grounds")
|
|
or n:find("Median")
|
|
or n:find("Curb")
|
|
or n:find("Wall")
|
|
end
|
|
|
|
local drive, obst = {}, {}
|
|
for _, d in ipairs(map:GetDescendants()) do
|
|
if d:IsA("BasePart") then
|
|
local n = d.Name
|
|
if isDrivable(n) then
|
|
table.insert(drive, d)
|
|
elseif not isInfra(n) then
|
|
table.insert(obst, d)
|
|
end
|
|
end
|
|
end
|
|
|
|
if #obst == 0 or #drive == 0 then
|
|
return ("SCOPE FAIL drive=%d obst=%d"):format(#drive, #obst)
|
|
end
|
|
|
|
local op = OverlapParams.new()
|
|
op.FilterType = Enum.RaycastFilterType.Include
|
|
op.FilterDescendantsInstances = obst
|
|
op.MaxParts = 12
|
|
|
|
-- POSITIVE CONTROL: probe dead-centre of a known obstacle must report a hit.
|
|
local target = obst[math.floor(#obst / 2)]
|
|
local ctrl = ws:GetPartBoundsInBox(CFrame.new(target.Position), Vector3.new(4, 4, 4), op)
|
|
local controlOK = #ctrl > 0
|
|
|
|
-- SECOND CONTROL: a box far out in empty sky must report ZERO. Guards the
|
|
-- opposite failure -- a filter so broad everything "hits".
|
|
local negCtrl = ws:GetPartBoundsInBox(CFrame.new(0, 40000, 0), Vector3.new(8, 8, 8), op)
|
|
local negOK = #negCtrl == 0
|
|
|
|
local hits = 0
|
|
local byObst, byRamp = {}, {}
|
|
local samples = {}
|
|
for _, p in ipairs(drive) do
|
|
local probe = p.Size - Vector3.new(1.5, 1.5, 1.5)
|
|
if probe.X > 0.5 and probe.Y > 0.5 and probe.Z > 0.5 then
|
|
local h = ws:GetPartBoundsInBox(p.CFrame, probe, op)
|
|
if #h > 0 then
|
|
hits += 1
|
|
local on = h[1].Name
|
|
byObst[on] = (byObst[on] or 0) + 1
|
|
local rn = p.Name:gsub("_%d+$", "")
|
|
byRamp[rn] = (byRamp[rn] or 0) + 1
|
|
if #samples < 10 then
|
|
table.insert(
|
|
samples,
|
|
("%s@%.0f,%.0f,%.0f IN %s"):format(p.Name, p.Position.X, p.Position.Y, p.Position.Z, on)
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local out = {}
|
|
table.insert(
|
|
out,
|
|
("CONTROL+=%s(%d) CONTROL-=%s(%d) -> probe %s"):format(
|
|
controlOK and "PASS" or "FAIL",
|
|
#ctrl,
|
|
negOK and "PASS" or "FAIL",
|
|
#negCtrl,
|
|
(controlOK and negOK) and "TRUSTWORTHY" or "BROKEN-IGNORE-RESULTS"
|
|
)
|
|
)
|
|
table.insert(out, ("drivable=%d obstacles=%d CLIPPING=%d"):format(#drive, #obst, hits))
|
|
|
|
local function dump(t, label)
|
|
local l = {}
|
|
for k, v in pairs(t) do
|
|
table.insert(l, ("%s=%d"):format(k, v))
|
|
end
|
|
table.sort(l, function(a, b)
|
|
return a > b
|
|
end)
|
|
local keep = {}
|
|
for i = 1, math.min(#l, 12) do
|
|
table.insert(keep, l[i])
|
|
end
|
|
table.insert(out, label .. ": " .. table.concat(keep, " "))
|
|
end
|
|
if hits > 0 then
|
|
dump(byObst, "speared obstacles")
|
|
dump(byRamp, "offending road groups")
|
|
for _, s in ipairs(samples) do
|
|
table.insert(out, " " .. s)
|
|
end
|
|
end
|
|
|
|
return table.concat(out, "\n")
|