currently in my roller coaster sinulation, going upside down is broken. the spline works but the visualized track as soon as it gets to past 90 degrees it flips. can you make it work. also can m=you make it be able to handle any roller coaster element like inversions and everything. ehre is the code: 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()


Module1:-- 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 2: -- 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