Aux-lane suppression + PA geometry fixes; probe/verify tooling
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:
2026-08-06 08:27:47 -04:00
parent c297b20aed
commit 6e50f9718d
36 changed files with 3723 additions and 1 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Capture the two spots the user circled in their screenshot.
Their view looks along the mainline with the ramp descending on the left; the
two marks sit at the suppression boundary (mainline samples ~525 and ~551),
where the aux lane ends and the parapet transitions back to the deck edge.
Anchor the camera to those SAMPLES, live, so the shot cannot drift.
left oval -> sample 525 area: was a 4-sample hole with no barrier
right circle -> sample 551 area: was a 26-stud protruding fence stub
"""
import re
import shutil
import sys
import time
sys.path.insert(0, "/tmp")
import rbx # noqa: E402
OUT = "/home/jesse/roblox/tokyo-wangan-racing/tools/shots"
ANCHOR_CODE = """
local map = workspace:FindFirstChild("Map")
if not map then return "NOMAP" end
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+)$")
if k then deck[tonumber(k)] = d end
end
end
local out = {}
for _, i in ipairs({520, 525, 538, 551, 556}) do
local d = deck[i]
if d then
table.insert(out, ("%.1f %.1f %.1f"):format(d.Position.X, d.Position.Y, d.Position.Z))
end
end
if #out < 5 then return "NOSAMP" end
return table.concat(out, " | ")
"""
CAM_TPL = """
local cam = workspace.CurrentCamera
cam.CameraType = Enum.CameraType.Scriptable
cam.FieldOfView = {fov}
cam.CFrame = CFrame.lookAt(
Vector3.new({px}, {py}, {pz}),
Vector3.new({tx}, {ty}, {tz})
)
return "cam ok"
"""
def grab(name, px, py, pz, tx, ty, tz, fov=55):
code = CAM_TPL.format(fov=fov, px=px, py=py, pz=pz, tx=tx, ty=ty, tz=tz)
r = rbx.call("execute_luau", {"datamodel_type": "Edit", "code": code})
if isinstance(r, dict) and r.get("isError"):
print(f"{name}: CAM FAIL {r}")
return None
time.sleep(1.5)
cap = rbx.call("screen_capture", {"capture_id": "ScreenCapture_1"}, wait=22, timeout=300)
imgs = cap.get("images") if isinstance(cap, dict) else None
if not imgs:
print(f"{name}: CAPTURE FAIL {str(cap)[:160]}")
return None
dest = f"{OUT}/{name}.jpg"
shutil.copy(imgs[0], dest)
print(f"{name}: OK -> {dest}")
return dest
def main():
a = rbx.call("execute_luau", {"datamodel_type": "Edit", "code": ANCHOR_CODE},
wait=40, timeout=300)
txt = a if isinstance(a, str) else str(a)
if "NOMAP" in txt or "NOSAMP" in txt:
print("ANCHOR FAIL: " + txt[:200])
return
trip = re.findall(r"(-?\d+\.\d+) (-?\d+\.\d+) (-?\d+\.\d+)", txt)
if len(trip) < 5:
print("ANCHOR PARSE FAIL: " + txt[:300])
return
p = [tuple(float(v) for v in t) for t in trip]
s520, s525, s538, s551, s556 = p
print("anchors: " + str(p))
made = []
# LEFT OVAL: the formerly-bare run. Look along the road from upstream so the
# barrier line is seen edge-on -- a gap would read as an obvious break.
made.append(grab("fix_left_oval",
s520[0] - 90, s520[1] + 55, s520[2] - 90,
s538[0], s538[1], s538[2], fov=55))
# RIGHT CIRCLE: the transition where the fence used to jut out 26 studs.
made.append(grab("fix_right_circle",
s556[0] + 120, s556[1] + 50, s556[2] + 120,
s551[0], s551[1], s551[2], fov=50))
# Top-down over each boundary: a jut shows instantly as a step in the line.
made.append(grab("fix_boundary_525_top",
s525[0], s525[1] + 300, s525[2],
s525[0], s525[1], s525[2], fov=55))
made.append(grab("fix_boundary_551_top",
s551[0], s551[1] + 300, s551[2],
s551[0], s551[1], s551[2], fov=55))
# Wide overview matching the user's framing.
made.append(grab("fix_overview",
s538[0] - 420, s538[1] + 240, s538[2] - 420,
s538[0], s538[1], s538[2], fov=60))
made = [m for m in made if m]
print("\nMADE " + str(len(made)) + " shots")
for m in made:
print(m)
if __name__ == "__main__":
main()