Files
jesse 6e50f9718d
CI / quality (push) Failing after 1m9s
Aux-lane suppression + PA geometry fixes; probe/verify tooling
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
2026-08-06 08:27:47 -04:00

135 lines
4.8 KiB
Luau

-- 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")