--!strict -- Verify the rebuilt Daikoku PA junction IN-ENGINE, after MapBuilder has run. -- -- MEASURE FIRST (project rule): ray tests and code reasoning both mislead; -- actual part positions are ground truth. This reads the built parts back out -- of Workspace rather than trusting the source that generated them. -- -- Checks: -- 1. No PA ring/parapet part intrudes into the mainline carriageway. -- 2. Mainline deck clears the ring/apron by a usable height. -- 3. Both ramps form a continuous, climbable chain (no cliff, no gap). -- 4. The old PARingPillar parts are gone. local ws = workspace local function findFirst(name: string): Instance? return ws:FindFirstChild(name, true) end local out = {} local function say(s: string) table.insert(out, s) end -- Locate the PA + road containers ------------------------------------------- local pa = findFirst("ParkingArea") or findFirst("PA") or findFirst("DaikokuPA") local road = findFirst("Road") local struct = findFirst("Structure") or findFirst("Struct") say("== CONTAINERS ==") say(("pa=%s road=%s struct=%s"):format( pa and pa:GetFullName() or "NIL", road and road:GetFullName() or "NIL", struct and struct:GetFullName() or "NIL" )) -- Gather parts by name pattern anywhere under Workspace ---------------------- local buckets: { [string]: { BasePart } } = {} local function bucket(key: string, p: BasePart) buckets[key] = buckets[key] or {} table.insert(buckets[key], p) end for _, d in ipairs(ws:GetDescendants()) do if d:IsA("BasePart") then local n = d.Name if n:match("^PARingWall") or n:match("^PARingRail") then bucket("ringwall", d) elseif n:match("^PARingPillar") then bucket("ringpillar", d) elseif n:match("^PARamp") and n:match("Pillar") then bucket("ramppillar", d) elseif n:match("^PARampKerb") then bucket("rampkerb", d) elseif n:match("^PAOnRamp") then bucket("entryramp", d) elseif n:match("^PAOffRamp") then bucket("exitramp", d) elseif n:match("^Road_") then bucket("roaddeck", d) end end end say("") say("== PART COUNTS ==") for _, k in ipairs({ "roaddeck", "ringwall", "ringpillar", "entryramp", "exitramp", "ramppillar", "rampkerb" }) do say(("%-12s %d"):format(k, buckets[k] and #buckets[k] or 0)) end -- 4. Old ring pillars must be gone ------------------------------------------ say("") say("== CHECK: legacy ring pillars removed ==") local rp = buckets["ringpillar"] if rp and #rp > 0 then say(("FAIL: %d PARingPillar parts still present (ring should be ground-level)"):format(#rp)) else say("PASS: no PARingPillar parts (ring is ground-level)") end -- 1 + 2. Ring structures vs mainline carriageway ----------------------------- say("") say("== CHECK: ring walls do not intrude into the carriageway ==") local decks = buckets["roaddeck"] or {} local walls = buckets["ringwall"] or {} local worstGap, worstDesc = math.huge, "n/a" local intrusions = 0 for _, w in ipairs(walls) do local wTop = w.Position.Y + w.Size.Y / 2 for _, dk in ipairs(decks) do local dxz = (Vector3.new(dk.Position.X, 0, dk.Position.Z) - Vector3.new(w.Position.X, 0, w.Position.Z)).Magnitude -- Only decks whose footprint actually overlaps this wall matter. if dxz < (dk.Size.X / 2 + w.Size.X / 2 + 30) then local deckBottom = dk.Position.Y - dk.Size.Y / 2 local gap = deckBottom - wTop if gap < worstGap then worstGap = gap worstDesc = ("%s vs %s dxz=%.1f"):format(dk.Name, w.Name, dxz) end if gap < 0 then intrusions += 1 end end end end if worstGap == math.huge then say("PASS: no ring wall shares a footprint with any mainline deck") else say(("closest ring-wall/deck pair: %s"):format(worstDesc)) say(("vertical gap = %.1f studs"):format(worstGap)) if intrusions > 0 then say(("FAIL: %d wall/deck pairs overlap vertically -- wall in the road"):format(intrusions)) else say("PASS: every overlapping ring wall passes UNDER the deck") end end -- 3. Ramp continuity --------------------------------------------------------- local function checkRamp(key: string, label: string) say("") say(("== CHECK: %s continuity =="):format(label)) local segs = buckets[key] if not segs or #segs == 0 then say("FAIL: no segments found") return end table.sort(segs, function(a, b) local ai = tonumber(a.Name:match("_(%d+)$")) or 0 local bi = tonumber(b.Name:match("_(%d+)$")) or 0 return ai < bi end) local loY = segs[1].Position.Y local hiY = segs[#segs].Position.Y say(("%d segments, Y from %.1f to %.1f (rise %.1f)"):format(#segs, loY, hiY, hiY - loY)) local maxStep, maxGap = 0, 0 for i = 2, #segs do local a, b = segs[i - 1], segs[i] local step = math.abs(b.Position.Y - a.Position.Y) local horiz = (Vector3.new(a.Position.X, 0, a.Position.Z) - Vector3.new(b.Position.X, 0, b.Position.Z)).Magnitude -- gap = horizontal distance minus the two half-lengths that face -- each other; negative means the segments overlap (good). local gap = horiz - (a.Size.Z / 2 + b.Size.Z / 2) maxStep = math.max(maxStep, step) maxGap = math.max(maxGap, gap) end say(("max vertical step between segments = %.2f studs"):format(maxStep)) say(("max horizontal gap between segments = %.2f studs (negative = overlap)"):format(maxGap)) if maxStep > 6 then say("FAIL: a step that large is a cliff a car cannot climb") elseif maxGap > 0.5 then say("FAIL: segments do not meet -- wheels will drop into the seam") else say("PASS: ramp is continuous and climbable") end end checkRamp("entryramp", "entry ramp") checkRamp("exitramp", "exit ramp") return table.concat(out, "\n")