Files
tokyo-wangan-racing/tools/probe_ground_truth.luau
jesse c297b20aed Disable PA on/off ramps; restore mainline to pre-ramp state
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).
2026-08-03 21:15:25 -04:00

120 lines
3.3 KiB
Luau

-- GROUND-TRUTH PROBE for the floating-scenery finding.
--
-- probe_floating reported 664 parts with "nothing below". That number alone
-- is ambiguous: it reads identically whether (a) the buildings genuinely
-- hang in the air, or (b) the map has no ground/terrain plane at all, so
-- NOTHING can ever return a hit. Distinguish before calling it a defect.
--
-- Measure:
-- 1. Does the map contain terrain / a baseplate / a water or ground slab?
-- 2. Bottom-face Y of each scenery part vs the road deck Y and vs 0.
-- A building whose bottom is at/below ~0 is "planted" even with no
-- ground mesh; one whose bottom is hundreds of studs up is FLOATING.
local ws = game:GetService("Workspace")
local map = ws:FindFirstChild("Map")
if not map then
return "NO MAP"
end
local out = {}
-- 1. terrain / ground candidates
local terr = ws:FindFirstChildOfClass("Terrain")
local terrHas = false
if terr then
local ok, reg = pcall(function()
return terr.MaxExtents
end)
-- MaxExtents is a Region3int16; its Min/Max are Vector3int16 which have
-- no .Magnitude. Compare components instead.
if ok and reg then
local mn, mx = reg.Min, reg.Max
terrHas = (mx.X - mn.X) > 1 or (mx.Y - mn.Y) > 1 or (mx.Z - mn.Z) > 1
end
end
table.insert(out, ("Terrain present=%s hasVoxels=%s"):format(tostring(terr ~= nil), tostring(terrHas)))
local groundish = {}
for _, d in ipairs(ws:GetDescendants()) do
if d:IsA("BasePart") then
local n = d.Name:lower()
if
n:find("ground")
or n:find("base")
or n:find("water")
or n:find("sea")
or n:find("bay")
or n:find("floor")
then
local sz = d.Size
if sz.X > 200 or sz.Z > 200 then
table.insert(
groundish,
("%s size=%.0fx%.0f topY=%.1f"):format(d.Name, sz.X, sz.Z, d.Position.Y + sz.Y / 2)
)
end
end
end
end
table.insert(out, ("large ground-like slabs=%d"):format(#groundish))
for i = 1, math.min(#groundish, 6) do
table.insert(out, " " .. groundish[i])
end
-- 2. bottom-face Y of scenery vs deck
local roadY, roadN = {}, 0
local scen = {}
for _, d in ipairs(map:GetDescendants()) do
if d:IsA("BasePart") then
local nm = d.Name
if nm:sub(1, 5) == "Road_" then
roadN += 1
table.insert(roadY, d.Position.Y)
elseif
nm:find("Building")
or nm:find("Tower")
or nm:find("Ginza")
or nm:find("Shiodome")
or nm:find("Diet")
then
table.insert(scen, d)
end
end
end
table.sort(roadY)
local medRoad = roadY[math.max(1, math.floor(#roadY / 2))] or 0
table.insert(out, ("median road Y=%.1f (n=%d)"):format(medRoad, roadN))
local planted, floatHigh = 0, 0
local hist = {}
local worst, worstNm, worstP = -1e9, "none", nil
for _, p in ipairs(scen) do
local btm = p.Position.Y - p.Size.Y / 2
if btm <= 5 then
planted += 1
else
floatHigh += 1
local base = p.Name:gsub("_%d+$", "")
hist[base] = (hist[base] or 0) + 1
if btm > worst then
worst, worstNm, worstP = btm, p.Name, p.Position
end
end
end
table.insert(out, ("scenery=%d | bottom<=5 (planted)=%d | bottom>5 (airborne)=%d"):format(#scen, planted, floatHigh))
if worstP then
table.insert(
out,
("HIGHEST bottom: %s btmY=%.0f at %.0f,%.0f,%.0f"):format(worstNm, worst, worstP.X, worstP.Y, worstP.Z)
)
end
local hl = {}
for k, v in pairs(hist) do
table.insert(hl, ("%s=%d"):format(k, v))
end
table.sort(hl)
table.insert(out, "airborne byName: " .. (#hl > 0 and table.concat(hl, " ") or "(none)"))
return table.concat(out, "\n")