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

126 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Capture the PA slip ramps from the user's own viewpoint, LIVE-ANCHORED.
The user's screenshot looks along the mainline from above the deck with the
ramp descending on the left. Reproduce that framing from the actual ramp
geometry so the shot cannot drift onto empty air after an edit.
"""
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"
# Anchors: for each slip -> mouth (first deck seg), mid-span, landing (last seg).
ANCHOR_CODE = """
local map = workspace:FindFirstChild("Map")
if not map then return "NOMAP" end
local segs = {}
for _, d in ipairs(map:GetDescendants()) do
if d:IsA("BasePart") then
for _, s in ipairs({"PAWestSlip", "PAEastSlip"}) do
local k = d.Name:match("^" .. s .. "_Deck_(%d+)$")
if k then
segs[s] = segs[s] or {}
table.insert(segs[s], {i = tonumber(k), p = d.Position})
end
end
end
end
local out = {}
for _, s in ipairs({"PAWestSlip", "PAEastSlip"}) do
local g = segs[s]
if g and #g > 2 then
table.sort(g, function(a, b) return a.i < b.i end)
local mouth = g[1].p
local mid = g[math.floor(#g / 2)].p
local land = g[#g].p
table.insert(out, ("%.1f %.1f %.1f"):format(mouth.X, mouth.Y, mouth.Z))
table.insert(out, ("%.1f %.1f %.1f"):format(mid.X, mid.Y, mid.Z))
table.insert(out, ("%.1f %.1f %.1f"):format(land.X, land.Y, land.Z))
end
end
if #out == 0 then return "NOSLIP" 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 "NOSLIP" in txt:
print("ANCHOR FAIL: " + txt[:200])
return
trip = re.findall(r"(-?\d+\.\d+) (-?\d+\.\d+) (-?\d+\.\d+)", txt)
if len(trip) < 6:
print("ANCHOR PARSE FAIL: " + txt[:300])
return
pts = [tuple(float(v) for v in t) for t in trip]
print("anchors: " + str(pts))
made = []
for n, base in enumerate((0, 3), start=1):
mouth, mid, land = pts[base], pts[base + 1], pts[base + 2]
tag = ["west", "east"][n - 1]
# 1. The user's view: high, behind the mouth, looking down the ramp.
made.append(grab(
f"ramp_{tag}_userview",
mouth[0] - 300, mouth[1] + 120, mouth[2] - 300,
land[0], land[1], land[2], fov=60))
# 2. Side elevation: shows the descent profile against the deck.
made.append(grab(
f"ramp_{tag}_side",
mid[0] + 420, mid[1] + 90, mid[2],
mid[0], mid[1], mid[2], fov=48))
# 3. Top-down over mid-span: is it parallel to the deck or cutting away?
made.append(grab(
f"ramp_{tag}_topdown",
mid[0], mid[1] + 560, mid[2],
mid[0], mid[1], mid[2], fov=55))
made = [m for m in made if m]
print("\nMADE " + str(len(made)) + " shots")
for m in made:
print(m)
if __name__ == "__main__":
main()