Can you make this roller coaster simulation handle being able to go upside down? Currenly the track breaks. Module: -- RollerCoasterManager (ModuleScript)
-- Place this script in ServerScriptService

local RollerCoasterModule = require(script:WaitForChild("RollerCoasterModule"))

-- List of Roller Coaster Folder Names
local coasterFolderNames = {
	"Coaster",    -- Existing coaster
	"Coaster2",   -- Additional coaster
	"Coaster3",   -- Add more as needed
	-- Add more coaster folder names here
}

-- Table to hold RollerCoasterController instances
local RollerCoasterControllers = {}

-- Initialize All Roller Coasters
local function InitializeRollerCoasters()
	for _, folderName in ipairs(coasterFolderNames) do
		local coasterFolder = workspace:FindFirstChild(folderName)
		if coasterFolder then
			-- Ensure each folder has a "Cart" part
			local cart = coasterFolder:FindFirstChild("Cart")
			if cart and cart:IsA("BasePart") then
				-- Instantiate the RollerCoasterController
				local controller = RollerCoasterModule.new(folderName)
				RollerCoasterControllers[folderName] = controller

				-- Start Physics Automatically
				controller:StartPhysics()

				print("[RollerCoasterManager] Initialized and started physics for:", folderName)
			else
				warn("[RollerCoasterManager] Folder '" .. folderName .. "' is missing a 'Cart' part or it's not a BasePart.")
			end
		else
			warn("[RollerCoasterManager] Folder '" .. folderName .. "' not found in Workspace.")
		end
	end
end

-- Initialize coasters on script start
InitializeRollerCoasters()

-- Exposed Functions to Manage Coasters

local RollerCoasterManager = {}

-- Start Physics for a Specific Coaster
function RollerCoasterManager.StartPhysics(folderName)
	local controller = RollerCoasterControllers[folderName]
	if controller then
		controller:StartPhysics()
		print("[RollerCoasterManager] Physics started for:", folderName)
	else
		warn("[RollerCoasterManager] No controller found for '" .. folderName .. "'.")
	end
end

-- Stop Physics for a Specific Coaster
function RollerCoasterManager.StopPhysics(folderName)
	local controller = RollerCoasterControllers[folderName]
	if controller then
		controller:StopPhysics()
		print("[RollerCoasterManager] Physics stopped for:", folderName)
	else
		warn("[RollerCoasterManager] No controller found for '" .. folderName .. "'.")
	end
end

-- Set Velocity for a Specific Coaster
function RollerCoasterManager.SetVelocity(folderName, newVelocity)
	local controller = RollerCoasterControllers[folderName]
	if controller then
		controller:SetVelocityExternal(newVelocity)
		print("[RollerCoasterManager] Velocity set to", newVelocity, "m/s for:", folderName)
	else
		warn("[RollerCoasterManager] No controller found for '" .. folderName .. "'.")
	end
end

-- Activate Booster for a Specific Coaster
function RollerCoasterManager.ActivateBooster(folderName, boostAmount)
	local controller = RollerCoasterControllers[folderName]
	if controller then
		controller:ActivateBooster(boostAmount)
		print("[RollerCoasterManager] Booster activated by", boostAmount, "m/s for:", folderName)
	else
		warn("[RollerCoasterManager] No controller found for '" .. folderName .. "'.")
	end
end

-- Restart a Specific Coaster
function RollerCoasterManager.RestartCoaster(folderName)
	local controller = RollerCoasterControllers[folderName]
	if controller then
		controller:RestartCoaster()
		print("[RollerCoasterManager] Coaster restarted for:", folderName)
	else
		warn("[RollerCoasterManager] No controller found for '" .. folderName .. "'.")
	end
end

-- Function to Get a Controller (for attaching custom event listeners)
function RollerCoasterManager.GetController(folderName)
	return RollerCoasterControllers[folderName]
end

-- Return the Manager table
return RollerCoasterManager

Module:
-- RollerCoasterModule (ModuleScript)
-- Place this in ServerScriptService

local RollerCoasterController = {}
RollerCoasterController.__index = RollerCoasterController

-- Services
local RunService = game:GetService("RunService")
local Workspace = workspace
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Constants (Adjust as needed)
local GRAVITY = 9.8 -- Gravity acceleration (m/s²)
local FRICTION_COEFFICIENT = 0.2 -- Kinetic friction coefficient (0 < μ < 1)
local SAMPLE_STEP = 0.001 -- Arc-length sampling step (smaller for higher accuracy)
local VISUALIZATION_STEP = 0.02 -- Step size for visual track generation
local INITIAL_SPEED = 0 -- Starting speed of the cart (m/s)

-- Constructor
function RollerCoasterController.new(coasterFolderName)
	local self = setmetatable({}, RollerCoasterController)

	-- References
	self.coasterFolder = Workspace:WaitForChild(coasterFolderName)
	self.cart = self.coasterFolder:WaitForChild("Cart") -- Each coaster has its own Cart

	-- Verify Cart Properties
	self.cart.Anchored = false -- Ensure cart is not anchored
	self.cart.Velocity = Vector3.new(0, 0, 0) -- Reset initial velocity

	-- Track Data
	self.trackPoints = {} -- Sorted list of track point positions
	self.arcLengthMap = {} -- Mapping of cumulative arc-length to spline parameters
	self.totalTrackLength = 0 -- Total length of the track
	self.touchableSList = {} -- List of {s, waypointNumber} positions where touchable points are located
	self.touchedTouchableS = {} -- Table to keep track of already touched touchable points

	-- Template for visual track parts (for debugging and aesthetics)
	-- Not needed since we're cloning pre-made segments

	-- BindableEvent for Touches
	self.TouchEvent = Instance.new("BindableEvent")
	self.TouchEvent.Name = "TouchEvent"
	self.TouchEvent.Parent = self.coasterFolder

	-- Load and sort track points
	self:loadTrackParts()

	-- Precompute arc-length mapping
	self:precomputeArcLengthMap()

	-- Calculate total track length
	self:calculateTotalTrackLength()

	-- Identify and record touchable points
	self:identifyTouchablePoints()

	-- Generate visual track by cloning TrackSegments
	self:generateVisualTrack()

	-- Initialize movement variables
	self.s = 0 -- Current arc-length position
	self.v = INITIAL_SPEED -- Current velocity (m/s)
	self.isPhysicsRunning = false
	self.moveConnection = nil

	print("[RollerCoasterController] Initialized for folder:", coasterFolderName)
	return self
end

-- Load and Sort Track Parts
function RollerCoasterController:loadTrackParts()
	local unsortedParts = {}

	for _, part in ipairs(self.coasterFolder:GetChildren()) do
		if part:IsA("Part") then
			local orderValue = part:FindFirstChild("Value")
			if orderValue and orderValue:IsA("IntValue") then
				table.insert(unsortedParts, { part = part, order = orderValue.Value })
			else
				warn("[RollerCoasterController] Part missing 'Value' IntValue: ", part.Name)
			end
		end
	end

	-- Sort parts based on the IntValue in ascending order
	table.sort(unsortedParts, function(a, b)
		return a.order < b.order
	end)

	-- Populate trackPoints with sorted positions
	for _, entry in ipairs(unsortedParts) do
		table.insert(self.trackPoints, entry.part.Position)
	end

	-- Ensure there are enough points to form a track
	if #self.trackPoints < 2 then
		error("[RollerCoasterController] Not enough track points loaded in '" .. self.coasterFolder.Name .. "'. At least two points are required.")
	end

	print("[RollerCoasterController] Loaded " .. #self.trackPoints .. " track points for:", self.coasterFolder.Name)
end

-- Catmull-Rom Spline Interpolation
function RollerCoasterController:catmullRomSpline(p0, p1, p2, p3, t)
	local t2 = t * t
	local t3 = t2 * t

	return 0.5 * (
		(2 * p1) +
			(-p0 + p2) * t +
			(2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
			(-p0 + 3 * p1 - 3 * p2 + p3) * t3
	)
end

-- Get Spline Point
function RollerCoasterController:getSplinePoint(t, segment)
	local p0 = self.trackPoints[math.max(segment - 1, 1)]
	local p1 = self.trackPoints[segment]
	local p2 = self.trackPoints[math.min(segment + 1, #self.trackPoints)]
	local p3 = self.trackPoints[math.min(segment + 2, #self.trackPoints)]

	return self:catmullRomSpline(p0, p1, p2, p3, t)
end

-- Get Spline Tangent
function RollerCoasterController:getSplineTangent(t, segment)
	local epsilon = 0.0001 -- Small step for derivative approximation
	local t1 = math.max(t - epsilon, 0)
	local t2 = math.min(t + epsilon, 1)
	local point1 = self:getSplinePoint(t1, segment)
	local point2 = self:getSplinePoint(t2, segment)
	local tangent = (point2 - point1).Unit
	return tangent
end

-- Precompute Arc-Length Mapping
function RollerCoasterController:precomputeArcLengthMap()
	self.arcLengthMap = {}
	local cumulativeLength = 0

	for segment = 1, #self.trackPoints - 1 do
		local lastPos = self:getSplinePoint(0, segment)
		for t = SAMPLE_STEP, 1, SAMPLE_STEP do
			local currentPos = self:getSplinePoint(t, segment)
			local delta = (currentPos - lastPos).Magnitude
			cumulativeLength = cumulativeLength + delta
			table.insert(self.arcLengthMap, { s = cumulativeLength, segment = segment, t = t })
			lastPos = currentPos
		end
	end

	print("[RollerCoasterController] Precomputed Arc Length Map with " .. #self.arcLengthMap .. " entries for:", self.coasterFolder.Name)
end

-- Find Spline Parameters Using Binary Search
function RollerCoasterController:findSplineParameters(s)
	if #self.arcLengthMap == 0 then
		return nil, nil
	end

	if s <= 0 then
		return self.arcLengthMap[1].segment, self.arcLengthMap[1].t
	end

	if s >= self.arcLengthMap[#self.arcLengthMap].s then
		return self.arcLengthMap[#self.arcLengthMap].segment, self.arcLengthMap[#self.arcLengthMap].t
	end

	local left = 1
	local right = #self.arcLengthMap
	local mid = 0

	-- Binary search for efficiency
	while left <= right do
		mid = math.floor((left + right) / 2)
		if self.arcLengthMap[mid].s < s then
			left = mid + 1
		elseif self.arcLengthMap[mid].s > s then
			right = mid - 1
		else
			return self.arcLengthMap[mid].segment, self.arcLengthMap[mid].t
		end
	end

	-- Safeguard to ensure 'right' is within bounds
	if right < 1 then right = 1 end
	if right > #self.arcLengthMap then right = #self.arcLengthMap end

	local s1 = self.arcLengthMap[right].s
	local s2 = self.arcLengthMap[left].s

	if s2 == s1 then
		-- Prevent division by zero
		return self.arcLengthMap[right].segment, self.arcLengthMap[right].t
	end

	local ratio = (s - s1) / (s2 - s1)
	local segment = self.arcLengthMap[right].segment
	local t = self.arcLengthMap[right].t + ratio * (self.arcLengthMap[left].t - self.arcLengthMap[right].t)
	return segment, t
end

-- Generate Visual Track by Cloning Pre-made TrackSegments
function RollerCoasterController:generateVisualTrack()
	-- Create a Folder to hold visual rails and ties for better organization
	local railsFolder = Instance.new("Folder")
	railsFolder.Name = "VisualRails"
	railsFolder.Parent = self.coasterFolder

	local tiesFolder = Instance.new("Folder")
	tiesFolder.Name = "TrackTies"
	tiesFolder.Parent = self.coasterFolder

	-- Load the TrackSegment template from ReplicatedStorage
	local replicatedStorage = game:GetService("ReplicatedStorage")
	local trackTemplates = replicatedStorage:FindFirstChild("TrackTemplates")
	if not trackTemplates then
		error("TrackTemplates folder not found in ReplicatedStorage.")
	end

	local masterTrackSegment = trackTemplates:FindFirstChild("TrackSegment")
	if not masterTrackSegment then
		error("TrackSegment model not found in TrackTemplates.")
	end

	-- Iterate through TrackPoints and clone TrackSegments
	for segment = 1, #self.trackPoints - 1 do
		for t = 0, 1, VISUALIZATION_STEP do
			local currentPos = self:getSplinePoint(t, segment)
			local nextT = t + VISUALIZATION_STEP
			if nextT > 1 then nextT = 1 end
			local nextPos = self:getSplinePoint(nextT, segment)

			-- Calculate direction for orientation
			local direction = (nextPos - currentPos).Unit

			-- Calculate midpoint between currentPos and nextPos
			local midpoint = (currentPos + nextPos) / 2

			-- Clone the TrackSegment
			local clonedSegment = masterTrackSegment:Clone()

			-- Position and orient the cloned segment
			clonedSegment:SetPrimaryPartCFrame(
				CFrame.lookAt(midpoint, midpoint + direction)
			)

			-- Parent the cloned segment to railsFolder
			clonedSegment.Parent = railsFolder
		end
	end

	print("[RollerCoasterController] Visual rails generated by cloning TrackSegments for:", self.coasterFolder.Name)
end

-- Calculate Total Track Length
function RollerCoasterController:calculateTotalTrackLength()
	if #self.arcLengthMap > 0 then
		self.totalTrackLength = self.arcLengthMap[#self.arcLengthMap].s
		print("[RollerCoasterController] Total Track Length:", self.totalTrackLength, "meters for:", self.coasterFolder.Name)
	else
		self.totalTrackLength = 0
		error("[RollerCoasterController] Arc Length Map is empty for:" .. self.coasterFolder.Name)
	end
end

-- Identify Touchable Points and Setup Touch Detection
function RollerCoasterController:identifyTouchablePoints()
	for _, part in ipairs(self.coasterFolder:GetChildren()) do
		if part:IsA("Part") then
			local touchableValue = part:FindFirstChild("Touchable")
			if touchableValue and touchableValue:IsA("BoolValue") and touchableValue.Value == true then
				-- Find the arc-length position corresponding to this track point
				local orderValue = part:FindFirstChild("Value")
				if orderValue and orderValue:IsA("IntValue") then
					local waypointNumber = orderValue.Value
					if waypointNumber >= 1 and waypointNumber <= #self.trackPoints then
						-- Calculate approximate s for this track point
						local pos = self.trackPoints[waypointNumber]
						local minDistance = math.huge
						local targetS = 0
						for _, entry in ipairs(self.arcLengthMap) do
							local currentPos = self:getSplinePoint(entry.t, entry.segment)
							local distance = (currentPos - pos).Magnitude
							if distance < minDistance then
								minDistance = distance
								targetS = entry.s
							end
						end
						table.insert(self.touchableSList, { s = targetS, waypointNumber = waypointNumber })
						print("[RollerCoasterController] Touchable waypoint #" .. waypointNumber .. " added at s =", targetS, "meters for:", self.coasterFolder.Name)
					end
				end
			end
		end
	end
end

-- Calculate Net Acceleration
function RollerCoasterController:calculateAcceleration(tangent, velocity)
	-- Compute the slope angle theta
	-- sin(theta) = tangent.Y
	local sinTheta = tangent.Y
	local cosTheta = math.sqrt(1 - sinTheta ^ 2) or 0.0001 -- Prevent division by zero

	-- Net acceleration along the track:
	-- a = -g * sin(theta) - μ * g * cos(theta) * sign(v)
	local a_gravity = -GRAVITY * sinTheta

	-- Determine friction direction based on velocity
	local frictionDirection = 0
	if velocity > 0 then
		frictionDirection = -1
	elseif velocity < 0 then
		frictionDirection = 1
	else
		frictionDirection = 0
	end

	local a_friction = FRICTION_COEFFICIENT * GRAVITY * cosTheta * frictionDirection

	-- Total acceleration
	local a_total = a_gravity + a_friction

	return a_total
end

-- Start Physics Simulation
function RollerCoasterController:StartPhysics()
	if self.isPhysicsRunning then
		warn("[RollerCoasterController] Physics simulation is already running for:", self.coasterFolder.Name)
		return
	end

	self.isPhysicsRunning = true

	-- Initialize cart's state
	self.s = 0 -- Current arc-length position
	self.v = INITIAL_SPEED -- Current velocity (m/s)

	-- Position the cart at the starting point
	local startPos = self.trackPoints[1]
	self.cart.Position = startPos
	local initialTangent = self:getSplineTangent(0, 1)
	self.cart.CFrame = CFrame.lookAt(startPos, startPos + initialTangent)
	self.cart.Velocity = Vector3.new(0, 0, 0) -- Reset any existing velocity

	-- Connect to Heartbeat for frame-synced updates
	self.moveConnection = RunService.Heartbeat:Connect(function(deltaTime)
		if not self.isPhysicsRunning then
			return -- Skip physics updates if simulation is stopped
		end

		-- Prevent division by zero
		if self.totalTrackLength <= 0 then
			warn("[RollerCoasterController] Total track length is zero or undefined for:", self.coasterFolder.Name)
			self.moveConnection:Disconnect()
			return
		end

		-- Calculate acceleration based on current position
		local segment, t = self:findSplineParameters(self.s)
		if not segment or not t then
			self.moveConnection:Disconnect()
			print("[RollerCoasterController] Cart has reached the end of the track for:", self.coasterFolder.Name)
			return
		end

		local tangent = self:getSplineTangent(t, segment)
		local a = self:calculateAcceleration(tangent, self.v)

		-- Update velocity: v = v + a * dt
		self.v = self.v + a * deltaTime

		-- Update position: s = s + v * dt
		self.s = self.s + self.v * deltaTime

		-- Clamp s to track limits with tolerance
		local tolerance = 0.0001
		if self.s < 0 then
			self.s = 0
			self.v = 0
		elseif self.s > self.totalTrackLength + tolerance then
			self.s = self.totalTrackLength
			self.v = 0
			self.moveConnection:Disconnect()
			self.isPhysicsRunning = false
			print("[RollerCoasterController] Cart has reached the end of the track for:", self.coasterFolder.Name)
			return
		end

		-- Find new spline parameters based on updated s
		local newSegment, newT = self:findSplineParameters(self.s)
		if newSegment and newT then
			local newPos = self:getSplinePoint(newT, newSegment)
			self.cart.Position = newPos

			-- Update cart orientation based on new tangent
			local newTangent = self:getSplineTangent(newT, newSegment)
			if newTangent and newTangent.Magnitude > 0 then
				self.cart.CFrame = CFrame.lookAt(newPos, newPos + newTangent)
			end
		else
			warn("[RollerCoasterController] Failed to find spline parameters for s =", self.s, "in:", self.coasterFolder.Name)
		end

		-- Detect and handle touchable points
		for _, touchPoint in ipairs(self.touchableSList) do
			local touchS = touchPoint.s
			local waypointNumber = touchPoint.waypointNumber
			if not self.touchedTouchableS[touchS] and self.s >= touchS then
				self.touchedTouchableS[touchS] = true -- Mark as touched to prevent re-triggering
				-- Trigger the touchable event
				print("[RollerCoasterController] Touchable waypoint #" .. waypointNumber .. " reached at s =", touchS, "meters in:", self.coasterFolder.Name)
				-- Fire the TouchEvent with touch information
				self.TouchEvent:Fire(waypointNumber, self.coasterFolder.Name)
			end
		end

		-- Optional: Debugging Information
		-- Uncomment the following line to print the current position and velocity each frame
		-- print("[RollerCoasterController] Coaster:", self.coasterFolder.Name, "s =", self.s, "m, v =", self.v, "m/s")
	end)

	print("[RollerCoasterController] Physics simulation started for:", self.coasterFolder.Name)
end

-- Stop Physics Simulation
function RollerCoasterController:StopPhysics()
	if not self.isPhysicsRunning then
		warn("[RollerCoasterController] Physics simulation is not running for:", self.coasterFolder.Name)
		return
	end

	self.isPhysicsRunning = false

	if self.moveConnection then
		self.moveConnection:Disconnect()
		self.moveConnection = nil
	end

	print("[RollerCoasterController] Physics simulation stopped for:", self.coasterFolder.Name)
end

-- Set Velocity Manually
function RollerCoasterController:SetVelocityExternal(newVelocity)
	self.v = newVelocity
	print("[RollerCoasterController] Cart velocity set to:", newVelocity, "m/s for:", self.coasterFolder.Name)
end

-- Activate Booster (Interactive Feature)
function RollerCoasterController:ActivateBooster(boostAmount)
	self.v = self.v + boostAmount
	print("[RollerCoasterController] Booster activated! Velocity increased by", boostAmount, "m/s. New velocity:", self.v, "m/s for:", self.coasterFolder.Name)

	-- Play booster sound if available
	local boosterSound = self.coasterFolder:FindFirstChild("BoosterSound")
	if boosterSound and boosterSound:IsA("Sound") then
		boosterSound:Play()
	else
		warn("[RollerCoasterController] BoosterSound not found or not a Sound instance for:", self.coasterFolder.Name)
	end

	-- Optional: Add visual effects such as particle emitters here
end

-- Restart Coaster
function RollerCoasterController:RestartCoaster()
	print("[RollerCoasterController] Restarting coaster:", self.coasterFolder.Name)
	self:StopPhysics()
	self.s = 0
	self.v = INITIAL_SPEED
	for _, touchPoint in ipairs(self.touchableSList) do
		self.touchedTouchableS[touchPoint.s] = false
	end
	self:StartPhysics()
	print("[RollerCoasterController] Coaster restarted:", self.coasterFolder.Name)
end

-- Attach Callback to Touchable Points
function RollerCoasterController:OnTouched(callback)
	if typeof(callback) ~= "function" then
		error("[RollerCoasterController] OnTouched requires a function.")
	end
	self.TouchEvent.Event:Connect(callback)
end

-- Return the RollerCoasterController class
return RollerCoasterController

Script:
-- TemplateScript (ServerScript)
-- Place this script in ServerScriptService or an appropriate location

local RollerCoasterManager = require(game.ServerScriptService:WaitForChild("RollerCoasterManager"))

-- Define the list of coasters you want to interact with
local coasterNames = {"Coaster", "Coaster2", "Coaster3"} -- Add more as needed

-- Function to handle touch events with waypoint numbers
local function HandleCoasterTouch(waypointNumber, coasterName)
	print("[TemplateScript] Cart passed waypoint #" .. waypointNumber .. " on coaster:", coasterName)

	-- Example Action: Activate a booster when a waypoint is touched
	local boosterAmount = 10 -- Adjust as needed
	RollerCoasterManager.ActivateBooster(coasterName, boosterAmount)

	-- Additional Custom Logic Here
	-- For example, spawn effects, update UI, etc.
end

-- Attach the touch handler to each coaster's touch event
local function SetupTouchHandlers()
	for _, coasterName in ipairs(coasterNames) do
		local controller = RollerCoasterManager.GetController(coasterName)
		if controller then
			controller:OnTouched(function(waypointNumber, coasterName)
				HandleCoasterTouch(waypointNumber, coasterName)
			end)
		else
			warn("[TemplateScript] No controller found for '" .. coasterName .. "'.")
		end
	end
end

-- Example Interactions
local function ExampleInteractions()
	-- Start Physics for Coaster2 after 5 seconds
	wait(5)
	RollerCoasterManager.StartPhysics("Coaster2")

	-- Set Velocity for Coaster3 to 20 m/s after 10 seconds
	wait(5)
	RollerCoasterManager.SetVelocity("Coaster3", 20)

	-- Restart Coaster1 after 15 seconds
	wait(5)
	RollerCoasterManager.RestartCoaster("Coaster")

	-- Stop Physics for Coaster2 after 20 seconds
	wait(5)
	RollerCoasterManager.StopPhysics("Coaster2")
end

-- Initialize the template script
local function Initialize()
	SetupTouchHandlers()
	ExampleInteractions()
end

-- Run the Initialize function on script start
Initialize()