Aux-lane suppression + PA geometry fixes; probe/verify tooling
CI / quality (push) Failing after 1m9s
CI / quality (push) Failing after 1m9s
MapBuilder.luau
- Suppress the aux lane across samples 525-550 (AUX_SUPPRESS_FROM/TO),
honoring the user's hand deletion in Studio so a rebuild no longer
regenerates it.
- Smoothstep the width ramp over AUX_BLEND=5 samples on each side. A hard
cut produced a 26-stud lateral fence jog; the blend removes it.
- DO NOT remove this suppression. Re-enabling it resurrects geometry the
user deleted by hand.
rokit.toml
- rojo 7.5.1 -> 7.7.0 to match the auto-updating Studio plugin.
Server older than plugin fails the handshake with
"attempt to index number with 'protocolVersion'".
tools/
- 34 read-only probe/verify/capture scripts used to measure the datamodel
instead of reasoning from source.
Verified-good baseline at this commit:
total parts 16788
PAWestSlip 60 segs, 34.4 deg total bend, worst 1.4, 0 kinks >5
PAEastSlip 60 segs, 30.7 deg total bend, worst 1.3, 0 kinks >5
AuxLane parts 30 (samples 525-550 deliberately absent)
barrier coverage 823 samples, 0 unguarded
fence jogs >10st 0
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
-- Two questions, measured rather than eyeballed:
|
||||
-- (1) WHERE exactly is the mainline missing barriers? Walk the deck edge and
|
||||
-- report contiguous runs with no Barrier/EdgeRail within reach.
|
||||
-- (User deleted ~106 parts by hand, so gaps are wherever they cut.)
|
||||
-- (2) HOW does the south on-ramp differ from the north off-ramp? Compare the
|
||||
-- two slips feature-by-feature so "make it look like the other" is a
|
||||
-- concrete list of deltas, not a guess.
|
||||
local out = {}
|
||||
local function say(s) table.insert(out, s) end
|
||||
|
||||
local map = workspace:FindFirstChild("Map")
|
||||
if not map then return "Map MISSING" end
|
||||
|
||||
-- Collect barrier-ish parts into a coarse spatial hash for fast lookup.
|
||||
local CELL = 60
|
||||
local grid = {}
|
||||
local function key(x, z) return math.floor(x / CELL) .. ":" .. math.floor(z / CELL) end
|
||||
local barrierCount = 0
|
||||
for _, d in ipairs(map:GetDescendants()) do
|
||||
if d:IsA("BasePart") and (d.Name == "Barrier" or d.Name == "EdgeRail") then
|
||||
barrierCount += 1
|
||||
local k = key(d.Position.X, d.Position.Z)
|
||||
grid[k] = grid[k] or {}
|
||||
table.insert(grid[k], d)
|
||||
end
|
||||
end
|
||||
say(("indexed %d Barrier/EdgeRail parts"):format(barrierCount))
|
||||
|
||||
local function nearestBarrier(p)
|
||||
local best = math.huge
|
||||
local cx, cz = math.floor(p.X / CELL), math.floor(p.Z / CELL)
|
||||
for dx = -1, 1 do
|
||||
for dz = -1, 1 do
|
||||
local c = grid[(cx + dx) .. ":" .. (cz + dz)]
|
||||
if c then
|
||||
for _, b in ipairs(c) do
|
||||
local d2 = (Vector3.new(b.Position.X, 0, b.Position.Z) - Vector3.new(p.X, 0, p.Z)).Magnitude
|
||||
-- only count barriers at roughly this height
|
||||
if math.abs(b.Position.Y - p.Y) < 14 and d2 < best then best = d2 end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return best
|
||||
end
|
||||
|
||||
-- Walk the mainline deck parts to build an ordered centreline.
|
||||
local deck = {}
|
||||
for _, d in ipairs(map:GetDescendants()) do
|
||||
if d:IsA("BasePart") then
|
||||
local k = d.Name:match("^Road_(%d+)$") or d.Name:match("^Deck_(%d+)$")
|
||||
or d.Name:match("^RoadDeck_(%d+)$")
|
||||
if k then table.insert(deck, { i = tonumber(k), p = d.Position, cf = d.CFrame, sz = d.Size }) end
|
||||
end
|
||||
end
|
||||
table.sort(deck, function(a, b) return a.i < b.i end)
|
||||
say(("mainline deck parts found: %d"):format(#deck))
|
||||
|
||||
if #deck > 0 then
|
||||
local UP = Vector3.new(0, 1, 0)
|
||||
local runs = {}
|
||||
local cur = nil
|
||||
for j, s in ipairs(deck) do
|
||||
local fwd = s.cf.LookVector
|
||||
local right = fwd:Cross(UP).Unit
|
||||
local half = s.sz.X / 2
|
||||
-- probe just outboard of each edge
|
||||
local gapL = nearestBarrier(s.p - right * (half + 2)) > 12
|
||||
local gapR = nearestBarrier(s.p + right * (half + 2)) > 12
|
||||
local bad = gapL or gapR
|
||||
if bad then
|
||||
local side = (gapL and gapR) and "BOTH" or (gapL and "L" or "R")
|
||||
if cur and cur.side == side and s.i - cur.last <= 2 then
|
||||
cur.last = s.i; cur.n += 1
|
||||
else
|
||||
if cur then table.insert(runs, cur) end
|
||||
cur = { first = s.i, last = s.i, n = 1, side = side, pos = s.p }
|
||||
end
|
||||
else
|
||||
if cur then table.insert(runs, cur); cur = nil end
|
||||
end
|
||||
end
|
||||
if cur then table.insert(runs, cur) end
|
||||
|
||||
table.sort(runs, function(a, b) return a.n > b.n end)
|
||||
say(("UNBARRIERED RUNS: %d total"):format(#runs))
|
||||
for j = 1, math.min(#runs, 14) do
|
||||
local r = runs[j]
|
||||
say((" samples %d..%d (%d) side=%s at (%.0f,%.0f,%.0f)"):format(
|
||||
r.first, r.last, r.n, r.side, r.pos.X, r.pos.Y, r.pos.Z))
|
||||
end
|
||||
end
|
||||
|
||||
-- (2) Compare the two slips.
|
||||
say("--- SLIP COMPARISON ---")
|
||||
for _, s in ipairs({ "PAWestSlip", "PAEastSlip" }) do
|
||||
local segs, walls, lines, other = {}, 0, 0, {}
|
||||
for _, d in ipairs(map:GetDescendants()) do
|
||||
if d:IsA("BasePart") and d.Name:match("^" .. s) then
|
||||
local k = d.Name:match("^" .. s .. "_Deck_(%d+)$")
|
||||
if k then
|
||||
table.insert(segs, { i = tonumber(k), p = d.Position, sz = d.Size, cf = d.CFrame })
|
||||
elseif d.Name:match("_Wall_") then walls += 1
|
||||
elseif d.Name:match("[Ll]ine") then lines += 1
|
||||
else
|
||||
local nm = d.Name:gsub("^" .. s .. "_", ""):gsub("_%d+$", "")
|
||||
other[nm] = (other[nm] or 0) + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(segs, function(a, b) return a.i < b.i end)
|
||||
local ol = {}
|
||||
for k, v in pairs(other) do table.insert(ol, k .. "=" .. v) end
|
||||
table.sort(ol)
|
||||
if #segs > 0 then
|
||||
-- heading of first and last segment: which way does it run?
|
||||
local h1 = segs[1].cf.LookVector
|
||||
local hN = segs[#segs].cf.LookVector
|
||||
-- curvature: total heading change
|
||||
local turn = 0
|
||||
for j = 2, #segs do
|
||||
local a = segs[j - 1].cf.LookVector
|
||||
local b = segs[j].cf.LookVector
|
||||
turn += math.deg(math.acos(math.clamp(a:Dot(b), -1, 1)))
|
||||
end
|
||||
say(("%s: segs=%d walls=%d lines=%d startY=%.1f endY=%.1f"):format(s, #segs, walls, lines, segs[1].p.Y, segs[#segs].p.Y))
|
||||
say((" heading first=(%.2f,%.2f) last=(%.2f,%.2f) totalTurn=%.1fdeg"):format(h1.X, h1.Z, hN.X, hN.Z, turn))
|
||||
say((" start=(%.0f,%.0f,%.0f) end=(%.0f,%.0f,%.0f)"):format(
|
||||
segs[1].p.X, segs[1].p.Y, segs[1].p.Z, segs[#segs].p.X, segs[#segs].p.Y, segs[#segs].p.Z))
|
||||
say(" other parts: " .. (#ol > 0 and table.concat(ol, " ") or "none"))
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat(out, "\n")
|
||||
Reference in New Issue
Block a user