Daikoku PA: lift mainline over the apron, rebuild both ramps

The PA ring road and the expressway mainline were both at ground level, so
the ring parapet stood INSIDE the carriageway (measured -10.0 studs of
vertical overlap: a wall in the middle of the road).

Reference photos of the real Daikoku Futo PA show the expressway flying
OVER the parking apron on pillars, with the PA at ground level and ramps
climbing up to meet it. Rebuilt to match:

- mainline lifted 38 studs through the junction, eased in/out (max 5.26%
  grade, within real expressway limits)
- PA ring + apron stay at ground level; legacy PARingPillar props removed
- both on/off ramps regraded over the full rise with smoothstep easing,
  segments overlapped 3 studs so wheels can't catch a seam, plus support
  pillars and edge kerbs where the deck is high

Verified in-engine against built parts (tools/diag_daikoku.luau), not just
source: clearance now +28.0 studs, both ramps continuous (max step 5.62,
no gaps). tools/plot_daikoku.py renders the plan/section for comparison.
This commit is contained in:
2026-08-02 15:56:13 -04:00
parent 8808b919c3
commit c8dabea53a
51 changed files with 6558 additions and 65 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""Push MapBuilder.luau source into a LIVE (Play-mode) Studio via the MCP bridge.
Rojo cannot reach the running server datamodel in Play mode, so the source is
shipped as ~9000-char chunks into named StringValue children (p0, p1, ...) under
ReplicatedStorage, then concatenated and assigned to the ModuleScript's .Source.
Named children matter: a retried chunk overwrites its slot instead of appending
a duplicate. Bridge calls fail intermittently ("Unable to find an active Studio
instance") even while Studio is up, so every call is retried.
"""
import sys, time
sys.path.insert(0, "/tmp")
import rbx
SRC_PATH = sys.argv[1]
MODULE_NAME = sys.argv[2]
# Which datamodel to write into. "Server" is the PLAY-mode sandbox: Studio
# DISCARDS it when Play stops, so pushes there are silently lost on the next
# stop/start (this burned an entire debugging session -- pushes verified at the
# correct byte length, then vanished). "Edit" is the persistent datamodel and
# is what you almost always want; it survives into the next Play session.
# Edit is only reachable while Studio is actually in Edit mode.
DATAMODEL = sys.argv[3] if len(sys.argv) > 3 else "Edit"
MODULE = 'game:GetService("ServerScriptService"):FindFirstChild("Server", true)'
CHUNK = 9000
def call(code, tries=4):
last = None
for a in range(tries):
try:
r = rbx.call(
"execute_luau",
{"code": code, "datamodel_type": DATAMODEL},
warmup=6,
wait=45,
timeout=300,
)
if r and "Unable to find an active Studio" not in str(r):
return r
last = r
except Exception as e: # noqa: BLE001
last = e
time.sleep(5)
raise RuntimeError(f"bridge failed after {tries} tries: {last}")
src = open(SRC_PATH, encoding="utf-8").read()
nbytes = len(src.encode())
print(f"source: {len(src)} chars, {nbytes} utf-8 bytes")
# Pick a long-bracket level that does not occur in the source.
lvl = 0
while ("]" + "=" * lvl + "]") in src or ("[" + "=" * lvl + "[") in src:
lvl += 1
eq = "=" * lvl
print(f"long-bracket level: {lvl}")
chunks = [src[i : i + CHUNK] for i in range(0, len(src), CHUNK)]
print(f"chunks: {len(chunks)}")
print(call('local rs=game:GetService("ReplicatedStorage")\n'
'local b=rs:FindFirstChild("MBBuf") if b then b:Destroy() end\n'
'b=Instance.new("Folder") b.Name="MBBuf" b.Parent=rs\n'
'return "buffer reset"'))
for i, c in enumerate(chunks):
# Lua long-bracket literals SWALLOW a newline that immediately follows the
# opening bracket. When a chunk happened to start with "\n" that byte was
# silently lost, giving assembled=65158 vs expected=65159. Emit a deliberate
# leading newline for Lua to eat so the payload always survives intact.
code = (
'local rs=game:GetService("ReplicatedStorage")\n'
'local b=rs:WaitForChild("MBBuf")\n'
f'local n="p{i}"\n'
"local sv=b:FindFirstChild(n) or Instance.new(\"StringValue\")\n"
"sv.Name=n sv.Parent=b\n"
f"sv.Value=[{eq}[\n{c}]{eq}]\n"
f'return "p{i} "..#sv.Value'
)
print(f" chunk {i+1}/{len(chunks)}: {call(code)}")
assemble = f"""
local rs=game:GetService("ReplicatedStorage")
local b=rs:WaitForChild("MBBuf")
local parts={{}}
for i=0,{len(chunks)-1} do
local sv=b:FindFirstChild("p"..i)
if not sv then return "MISSING CHUNK p"..i end
parts[#parts+1]=sv.Value
end
local s=table.concat(parts)
-- Search the WHOLE game, not just ServerScriptService: shared modules live in
-- ReplicatedStorage and client ones in StarterPlayerScripts. Searching only
-- SSS silently reported "NOT FOUND" for Chassis while the wrapper still
-- exited 0, so a half-applied push looked like a success.
local roots={{
game:GetService("ServerScriptService"),
game:GetService("ReplicatedStorage"),
game:GetService("StarterPlayer"),
game:GetService("Workspace"),
}}
local m=nil
for _,r in ipairs(roots) do
local hit=r:FindFirstChild("{MODULE_NAME}", true)
if hit and hit:IsA("ModuleScript") then m=hit break end
end
if not m then return "{MODULE_NAME} ModuleScript NOT FOUND" end
m.Source=s
return ("assembled=%d expected={nbytes} match=%s class=%s path=%s"):format(
#s, tostring(#s=={nbytes}), m.ClassName, m:GetFullName())
"""
result = call(assemble)
print(result)
# Exit non-zero on any failure. Previously this always exited 0, so a
# "NOT FOUND" or byte-count mismatch was reported as success by the caller.
if "match=true" not in str(result):
print("PUSH FAILED -- module not updated", file=sys.stderr)
sys.exit(1)