From 0dbd7420d41dd63133ce4b6ec4dd049abbf71525 Mon Sep 17 00:00:00 2001 From: "R3AL.CH1CK3N" Date: Fri, 31 Jul 2026 02:13:55 -0400 Subject: [PATCH] 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. --- src/server/MapBuilder.luau | 317 +++++++++++++++++++++++++++++++++++-- 1 file changed, 304 insertions(+), 13 deletions(-) diff --git a/src/server/MapBuilder.luau b/src/server/MapBuilder.luau index bd2cde5..bb9c393 100644 --- a/src/server/MapBuilder.luau +++ b/src/server/MapBuilder.luau @@ -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,15 +595,27 @@ 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 sign = part({ - Name = "NeonSign", - Size = Vector3.new(12, h * 0.42, 5), - CFrame = CFrame.new(x + w / 2, h * 0.62, z), - Color = ({ NEON_PINK, NEON_BLUE, NEON_TEAL, NEON_AMBER })[(i % 4) + 1], - Material = Enum.Material.Neon, - }) - sign.Parent = folder + 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(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 @@ -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]