Problem with Rail Grind Mechanic

I’ve been working on a competitive obby race game that utilizes several platforming mechanics, one of which includes rail grinding. The mechanic works by using Blockcasts to see if a valid part can be found and aligning the character’s position accordingly via an AlignPosition instance. It works well for the most part, but some issues come up when handling certain character rotations.


Due to the way the engine handles rotations, I made it so the character’s orientation on the Z axis is either 0 or 180 depending on the Normal vector of the Blockcast result (better explained below). As seen above, this approach works fine on the circular rail, but the other one produces very inconsistent results. Even the rotation is successful, there is still a noticeable jitter. Other times, it does not properly set the rotation, so the Blockcast is always unsuccessful, leaving the character floating in place.

Here’s the relevant code:

LocalScript:

function action.perform(controller, last_action)
	heartbeat = run_s.Heartbeat:Connect(function(dt)
		local root_cframe = root.CFrame --root is a HumanoidRootPart of the character
		
		local result = controller:rail_blockcast(Vector3.zero, Vector3.new(3, 3, 1))
		if result and result.Instance:HasTag("GrindRail") then
			local new_cframe = CFrame.lookAlong(root.Position,
				result.Instance.CFrame.RightVector * 100)
			local x, y, z = new_cframe:ToOrientation()
			--Below is the real important part (and what I'm having trouble fixing)
			if result.Normal.Y < 0 then
				z = math.rad(180)
			end

			new_cframe *= CFrame.fromOrientation(0, 0, z)
			root.CFrame = new_cframe
			local part = result.Instance

			--align_p referenced below is an AlignPosition
			align_p.Position = ((part.Position + part.CFrame.RightVector)
				+ part.CFrame:VectorToWorldSpace(Vector3.new(0, 2.5, 0)))
				+ root.CFrame.LookVector
		end
	end)
end

ModuleScript function that the LocalScript references:

function module:rail_blockcast(offset, size)
	local root = self.root
	local cframe = root.CFrame
	
	local result = workspace:Blockcast(cframe + offset,
		size,
		-root.CFrame.UpVector * (2.6 + offset.Y),
		raycast_p2)
	return result
end

I have tried also using various unit vectors of the character’s HumanoidRootPart to fix this, but no implementations have worked. Feel free to ask for clarification if need be. Any help is appreciated!

checking for just below zero would not work because a rail could be -0.01 without you knowing causing the flip
use print(result.Normal.Y) for debugging, it would show a negative value when the bug happens if I’m correct which in this case would flip the character on the z axis like in the video.
if it’s a small value like -0.01 then check for below -0.05 instead for example