Files
tokyo-wangan-racing/tools/diag_daikoku.luau
T
jesse 1d23c187ad Daikoku PA: two perimeter ramps, far side served by the median opening
Rework the PA junction to match the reference drawing: yellow = PA -> highway
(entry), red = highway -> PA (exit). Entry and exit swap sides (entry now
EAST, exit WEST) and both trunks hug the PA perimeter instead of running
concentric spirals.

The simplification: both carriageways sit at the same deck level, adjacent,
so the median opening IS the far-side access path. The under-deck crossing
and merge runs are deleted outright -- 8 runs collapse to 4, and the
girder-soffit ceiling logic that constrained the crossing goes with them.
Part count 24,210 -> 21,691.

The median opening is now load-bearing rather than cosmetic (it is the only
route from the far carriageway to the ramps), so it widens from 6 to 9
samples per junction.

Orientation is a single flip-able constant (ORIENT) so a mirrored result is
a one-line correction, not a rewrite.

Measured on the built map (not asserted):
  - 600 entry + 600 exit ramp parts
  - max grade 8.00% / 8.27%, under the 9% limit
  - 0 collisions with piers/masts/girders (558 structural parts)
  - segment continuity <= 2.5 studs; trunk->merge handoff <= 1.7 studs
  - merge lands at dY 0.0 (exact deck level), 33 studs lateral
  - median open at junctions: 0 parts vs. control ~7 on plain mainline

Two traps worth recording, both caught by controls:

1. Studio's require() cache silently rebuilt STALE code. The first "passing"
   build reported 17,575 parts and zero ramps, and the map contained
   PAOffRamp/PAOnRamp parts whose names exist nowhere in the current source.
   Every measurement taken before this was describing an old map. Added
   tools/rebuild_fresh.luau, which clones the module to bust the cache; use
   it rather than requiring MapBuilder directly.

2. A reported "median never opens" bug was a bad probe, not a defect. It
   counted `Barrier` (the outer edge parapet, which must NEVER open) instead
   of `Median_<i>`. Re-run against the right name: control 7, junctions 0.

Both were only visible because the probes carry controls -- a 0-hit result
with no control is vacuous. The verification harnesses are kept in tools/
with their controls built in.
2026-08-03 11:53:40 -04:00

162 lines
5.4 KiB
Luau

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