map: signature crossings + fix skyline setback clipping the road

buildBridges() adds the two landmark structures the generated road was
missing, driven off the existing per-sample tunnel/bridge flags in
Circuit.luau (200 tunnel / 474 bridge samples, recorded at trace time):

  * NihonbashiViaduct  - box girder carried on piers over the historic
    stone bridge, the C1's most recognisable stretch.
  * Shiodome cable-stayed span - twin H-towers with fanned stays.

Also fixes buildSkyline() placing geometry inside the racing surface:

  * the setback test compared the building CENTRE against 220 studs, so a
    220-wide tower still reached 110 studs inward - through the tunnel
    bore walls. Now tests the outer face (near - w/2) against
    ROAD_WIDTH/2 + 60.
  * it strided samples by 3, skipping the closest approach on tight
    corners. Now tests every sample.
  * NeonSign hangs off the +X face and was never clearance-checked at
    all; it now gets its own test.

Verified in Studio against a full rebuild (823 samples -> 2468 road
parts): 400 tunnel walls / 200 ceilings for the 200 flagged samples,
621 buildings placed, and raycasts over every road part report 0
headroom and 0 lateral obstructions. Minimum building clearance 47.8
studs.
This commit is contained in:
2026-07-31 02:13:55 -04:00
parent 1f888b3693
commit 0dbd7420d4
+297 -6
View File
@@ -562,21 +562,28 @@ local function buildSkyline(map: Folder)
local x = math.sin(a) * r - 477
local z = math.cos(a) * r - 473
-- keep buildings off the racing surface
local h = 120 + det(i * 5.1) ^ 2.2 * 900
local w = 90 + det(i * 8.3) * 130
-- keep buildings off the racing surface.
-- The test must use the building's OUTER FACE, not its centre: a 220-stud
-- centre setback still lets a 220-wide tower reach 110 studs inward, which
-- put it through the tunnel bore (walls sit at ROAD_WIDTH/2 + 1.5 + 3).
-- Sample every point too -- striding by 3 skipped the closest approach on
-- tight corners.
local CLEAR = ROAD_WIDTH / 2 + 60
local near = math.huge
for k = 1, n, 3 do
for k = 1, n do
local s = samples[k].pos
local d = (Vector3.new(s.X, 0, s.Z) - Vector3.new(x, 0, z)).Magnitude
if d < near then
near = d
end
end
if near < 220 then
if near - w / 2 < CLEAR then
continue
end
local h = 120 + det(i * 5.1) ^ 2.2 * 900
local w = 90 + det(i * 8.3) * 130
local b = part({
Name = "Building",
Size = Vector3.new(w, h, w),
@@ -588,16 +595,28 @@ local function buildSkyline(map: Folder)
b.Parent = folder
placed += 1
-- signs hang off the +X face, so they need their own clearance check
if det(i * 13.7) > 0.72 then
local sx = x + w / 2 + 6
local snear = math.huge
for k = 1, n do
local s = samples[k].pos
local d = (Vector3.new(s.X, 0, s.Z) - Vector3.new(sx, 0, z)).Magnitude
if d < snear then
snear = d
end
end
if snear > CLEAR then
local sign = part({
Name = "NeonSign",
Size = Vector3.new(12, h * 0.42, 5),
CFrame = CFrame.new(x + w / 2, h * 0.62, z),
CFrame = CFrame.new(sx, h * 0.62, z),
Color = ({ NEON_PINK, NEON_BLUE, NEON_TEAL, NEON_AMBER })[(i % 4) + 1],
Material = Enum.Material.Neon,
})
sign.Parent = folder
end
end
if placed > 620 then
break
end
@@ -614,6 +633,277 @@ local function buildSkyline(map: Folder)
ground.Parent = folder
end
--[[
Signature crossings.
Two hand-placed structures on top of the generated road:
* NihonbashiViaduct -- the C1's most famous stretch. The expressway runs
on a box girder directly OVER the historic Nihonbashi stone bridge and
the Nihonbashi-gawa. The stone bridge underneath is the actual landmark,
so it is modelled too (deck, balustrades, ornamental posts, arches).
* CableStayed -- a red twin-tower cable-stayed span on the straightest
bay-side run, with fanned stay cables and aviation warning lights.
Both index into Circuit.samples, so they follow the road wherever it moves.
Span indices were chosen from the georeferenced OSM trace: Nihonbashi maps
to the Edobashi-junction samples, the cable-stayed span to the Shiodome
straight (lowest cumulative heading change of any elevated, non-tunnel run).
]]
local function buildBridges(map: Folder)
local folder = Instance.new("Folder")
folder.Name = "Bridges"
folder.Parent = map
local samples = Circuit.samples
local n = Circuit.SAMPLE_COUNT
local function mid(i: number): Vector3
local a = samples[i]
local b = samples[(i % n) + 1]
return (a.pos + b.pos) / 2
end
----------------------------------------------------------------- Nihonbashi
do
local NB = Instance.new("Folder")
NB.Name = "NihonbashiViaduct"
NB.Parent = folder
local A, B = 46, 62
local pa, pb = samples[A].pos, samples[B].pos
local centre = (pa + pb) / 2
local axis = pb - pa
local flat = Vector3.new(axis.X, 0, axis.Z).Unit
local right = flat:Cross(UP)
local span = axis.Magnitude
-- Nihonbashi-gawa flowing under the viaduct
local waterCF = CFrame.lookAt(Vector3.new(centre.X, 2, centre.Z), Vector3.new(centre.X, 2, centre.Z) + flat, UP)
local water = part({
Name = "RiverWater",
Size = Vector3.new(150, 3, span + 300),
CFrame = waterCF,
Color = Color3.fromRGB(18, 32, 42),
Material = Enum.Material.Glass,
})
water.Reflectance = 0.35
water.Parent = NB
for _, side in ipairs({ -1, 1 }) do
local q = part({
Name = "RiverWall",
Size = Vector3.new(10, 26, span + 300),
CFrame = waterCF * CFrame.new(side * 80, 11, 0),
Color = Color3.fromRGB(58, 56, 52),
Material = Enum.Material.Concrete,
})
q.Parent = NB
end
-- the historic stone bridge, crossing UNDER the expressway
local bpos = Vector3.new(centre.X, 16, centre.Z)
local bcf = CFrame.lookAt(bpos, bpos + right, UP)
local deck = part({
Name = "NihonbashiStoneDeck",
Size = Vector3.new(46, 4, 180),
CFrame = bcf,
Color = Color3.fromRGB(150, 142, 128),
Material = Enum.Material.Slate,
})
deck.Parent = NB
for _, s in ipairs({ -1, 1 }) do
local bal = part({
Name = "Balustrade",
Size = Vector3.new(2.5, 5, 180),
CFrame = bcf * CFrame.new(s * 22, 4, 0),
Color = Color3.fromRGB(168, 160, 146),
Material = Enum.Material.Slate,
})
bal.Parent = NB
for k = -2, 2 do
local post = part({
Name = "BridgePost",
Size = Vector3.new(6, 14, 6),
CFrame = bcf * CFrame.new(s * 22, 8, k * 44),
Color = Color3.fromRGB(120, 112, 100),
Material = Enum.Material.Slate,
})
post.Parent = NB
if math.abs(k) == 2 then
local lamp = part({
Name = "BridgeLamp",
Size = Vector3.new(4, 6, 4),
CFrame = bcf * CFrame.new(s * 22, 17, k * 44),
Color = Color3.fromRGB(255, 214, 150),
Material = Enum.Material.Neon,
})
local pl = Instance.new("PointLight")
pl.Brightness = 2
pl.Range = 60
pl.Color = Color3.fromRGB(255, 214, 150)
pl.Parent = lamp
lamp.Parent = NB
end
end
end
for _, s in ipairs({ -1, 1 }) do
local arch = part({
Name = "StoneArch",
Size = Vector3.new(40, 22, 70),
CFrame = bcf * CFrame.new(0, -10, s * 46),
Color = Color3.fromRGB(138, 130, 118),
Material = Enum.Material.Slate,
})
arch.Parent = NB
end
-- box girder + pier bents carrying the expressway deck
for i = A, B do
local m = mid(i)
local fwd = Circuit.forwardAt(i)
local cf = CFrame.lookAt(m, m + fwd, UP)
local g = part({
Name = "BoxGirder",
Size = Vector3.new(ROAD_WIDTH + 10, 9, 64),
CFrame = cf * CFrame.new(0, -6, 0),
Color = Color3.fromRGB(64, 64, 72),
Material = Enum.Material.Metal,
})
g.Parent = NB
if i % 3 == 0 then
local y = samples[i].pos.Y
for _, side in ipairs({ -1, 1 }) do
local leg = part({
Name = "ViaductPier",
Size = Vector3.new(9, math.max(y - 14, 4), 9),
CFrame = CFrame.new(Vector3.new(m.X, math.max(y - 14, 4) / 2 + 14, m.Z) + right * (side * 34)),
Color = Color3.fromRGB(72, 70, 76),
Material = Enum.Material.Concrete,
})
leg.Parent = NB
end
local cap = part({
Name = "PierCap",
Size = Vector3.new(88, 6, 12),
CFrame = cf * CFrame.new(0, -13, 0),
Color = Color3.fromRGB(72, 70, 76),
Material = Enum.Material.Concrete,
})
cap.Parent = NB
end
end
end
--------------------------------------------------------------- cable-stayed
do
local CS = Instance.new("Folder")
CS.Name = "CableStayed"
CS.Parent = folder
local A, B = 646, 659
local pa, pb = samples[A].pos, samples[B].pos
local axis = pb - pa
local flat = Vector3.new(axis.X, 0, axis.Z).Unit
local right = flat:Cross(UP)
local deckY = pa.Y
local TOWER_H = 210
local RED = Color3.fromRGB(196, 72, 72)
local tops = {}
for _, idx in ipairs({ A + 3, B - 3 }) do
local c = mid(idx)
for _, side in ipairs({ -1, 1 }) do
local top = deckY + TOWER_H
local leg = part({
Name = "BridgeTower",
Size = Vector3.new(12, top, 12),
CFrame = CFrame.new(Vector3.new(c.X, top / 2, c.Z) + right * (side * 56)),
Color = RED,
Material = Enum.Material.Metal,
})
leg.Parent = CS
table.insert(tops, { pos = Vector3.new(c.X, top, c.Z) + right * (side * 56), idx = idx, side = side })
end
for _, h in ipairs({ 0.55, 0.85 }) do
local y = deckY + TOWER_H * h
local beam = part({
Name = "TowerCrossbeam",
Size = Vector3.new(124, 8, 8),
CFrame = CFrame.lookAt(Vector3.new(c.X, y, c.Z), Vector3.new(c.X, y, c.Z) + flat, UP)
* CFrame.Angles(0, math.pi / 2, 0),
Color = RED,
Material = Enum.Material.Metal,
})
beam.Parent = CS
end
local al = part({
Name = "AviationLight",
Size = Vector3.new(6, 3, 6),
CFrame = CFrame.new(c.X, deckY + TOWER_H + 3, c.Z),
Color = Color3.fromRGB(255, 60, 60),
Material = Enum.Material.Neon,
})
local pl = Instance.new("PointLight")
pl.Brightness = 3
pl.Range = 140
pl.Color = Color3.fromRGB(255, 60, 60)
pl.Parent = al
al.Parent = CS
end
-- fanned stay cables
for _, t in ipairs(tops) do
for k = 1, 6 do
local target = math.clamp(t.idx + (k * 2 - 7), A, B)
local anchor = mid(target) + right * (t.side * 46) + Vector3.new(0, 2, 0)
local delta = anchor - t.pos
local dist = delta.Magnitude
if dist > 1 then
local cable = part({
Name = "Cable",
Size = Vector3.new(0.9, 0.9, dist),
CFrame = CFrame.lookAt(t.pos + delta / 2, anchor, UP),
Color = Color3.fromRGB(225, 225, 232),
Material = Enum.Material.Metal,
})
cable.Parent = CS
end
end
end
for i = A, B do
local m = mid(i)
local fwd = Circuit.forwardAt(i)
local g = part({
Name = "BridgeGirder",
Size = Vector3.new(ROAD_WIDTH + 14, 7, 64),
CFrame = CFrame.lookAt(m, m + fwd, UP) * CFrame.new(0, -5, 0),
Color = RED,
Material = Enum.Material.Metal,
})
g.Parent = CS
end
local centre = (pa + pb) / 2
local bay = part({
Name = "BayWater",
Size = Vector3.new(900, 3, axis.Magnitude + 500),
CFrame = CFrame.lookAt(Vector3.new(centre.X, 1, centre.Z), Vector3.new(centre.X, 1, centre.Z) + flat, UP),
Color = Color3.fromRGB(14, 26, 38),
Material = Enum.Material.Glass,
})
bay.Reflectance = 0.4
bay.Parent = CS
end
end
function MapBuilder.build(): Folder
local existing = Workspace:FindFirstChild("Map")
if existing then
@@ -628,6 +918,7 @@ function MapBuilder.build(): Folder
buildPA(map)
buildLandmarks(map)
buildSkyline(map)
buildBridges(map)
-- Start/finish across the road at sample 1
local s1 = Circuit.samples[1]