Ball physics for my experience

I’ve been trying for days to get realistic bounce behavior for my game but all I get is this:

and what i want to get to is this behavior:

Any suggestions or anything I might be doing wrong? My code currently looks like this:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CameraQuery = ReplicatedStorage:WaitForChild("CameraQuery")

local ballSpeed = 0
local maxBallSpeed = 150
local currentTarget = nil
local deflectDirection = nil
local lerpAlpha = 0
local bouncePoints = nil
local bounceT = 0

local function bezierCurve(p0, p1, p2, t)
	local mt = 1 - t
	return mt * mt * p0 + 2 * mt * t * p1 + t * t * p2
end

local function generateBouncePoints(startPos, normal, targetPos)
	local bounceForce = Vector3.new(
		math.random(-25, 25),
		math.random(10, 30),
		math.random(-25, 25)
	)

	local rotatedNormal = normal * CFrame.Angles(
		math.rad(math.random(-45, 45)),
		math.rad(math.random(-45, 45)),
		math.rad(math.random(-45, 45))
	).LookVector

	local midPoint = startPos + rotatedNormal * math.random(25, 40) + bounceForce

	local randomTargetOffset = Vector3.new(
		math.random(-10, 10),
		math.random(-10, 10),
		math.random(-10, 10)
	)

	return {
		startPos,
		midPoint,
		targetPos + randomTargetOffset
	}
end

local function getNextTarget(currentCharacter)
	if not currentCharacter then
		return workspace:FindFirstChild("NPC")
	end
	if currentCharacter.Name == "NPC" then
		local player = Players:GetPlayers()[1]
		return player and player.Character
	else
		return workspace:FindFirstChild("NPC")
	end
end

local function findPlayerWithPlaying()
	if not currentTarget or not currentTarget.Parent then
		currentTarget = workspace:FindFirstChild("NPC")
	end
	return currentTarget
end

local function lerpVector(start, target, alpha)
	return start + (target - start) * alpha
end

local function handleCollision(ball, hit)
	if hit:IsA("BasePart") and hit.Name ~= "HumanoidRootPart" then
		local raycastResult = workspace:Raycast(ball.Position, hit.Position - ball.Position)
		if not raycastResult then return end

		local targetCharacter = findPlayerWithPlaying()
		if not targetCharacter then return end

		local targetHRP = targetCharacter:FindFirstChild("HumanoidRootPart")
		if not targetHRP then return end

		bouncePoints = generateBouncePoints(ball.Position, raycastResult.Normal, targetHRP.Position)
		bounceT = 0
	end
end

local function checkDeflections()
	if not currentTarget or not currentTarget.Parent then return end
	local ball = workspace:FindFirstChild("Ball")
	if not ball then return end
	local humanoidRootPart = currentTarget:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return end

	local distance = (humanoidRootPart.Position - ball.Position).Magnitude
	if distance <= 10 and not deflectDirection then
		local deflect = currentTarget:FindFirstChild("Deflect")
		if deflect and deflect.Value then
			local nextTarget = getNextTarget(currentTarget)
			if not nextTarget then return end

			if currentTarget.Name == "NPC" then
				local nextHRP = nextTarget:FindFirstChild("HumanoidRootPart")
				if not nextHRP then return end
				deflectDirection = (nextHRP.Position - ball.Position).Unit
			else
				local player = Players:GetPlayers()[1]
				if not player then return end
				local cameraCFrame = CameraQuery:InvokeClient(player)
				if not cameraCFrame then return end
				deflectDirection = cameraCFrame.LookVector
			end

			lerpAlpha = 0
			currentTarget = nextTarget
		end
	end
end

local function updateBall()
	local ball = workspace:FindFirstChild("Ball")
	if not ball then return end

	if not ball:GetAttribute("CollisionSetup") then
		ball:SetAttribute("CollisionSetup", true)
		ball.Touched:Connect(function(hit)
			handleCollision(ball, hit)
		end)
	end

	if bouncePoints then
		bounceT = bounceT + 0.05
		if bounceT >= 1 then
			bouncePoints = nil
			bounceT = 0
		else
			local newPosition = bezierCurve(
				bouncePoints[1],
				bouncePoints[2],
				bouncePoints[3],
				bounceT
			)
			ball.AssemblyLinearVelocity = (newPosition - ball.Position) * 8
			return
		end
	end

	local targetCharacter = findPlayerWithPlaying()
	if not targetCharacter then return end
	local humanoidRootPart = targetCharacter:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return end

	checkDeflections()

	local targetPosition = humanoidRootPart.Position
	local ballPosition = ball.Position

	local finalDirection
	if deflectDirection then
		local toTarget = (targetPosition - ballPosition).Unit
		local lerpedDirection = lerpVector(deflectDirection, toTarget, lerpAlpha)
		finalDirection = lerpedDirection.Unit

		lerpAlpha = math.min(1, lerpAlpha + 0.01)
		if lerpAlpha >= 1 then
			deflectDirection = nil
			lerpAlpha = 0
		end
	else
		finalDirection = (targetPosition - ballPosition).Unit
	end

	if ballSpeed < maxBallSpeed then
		ballSpeed = ballSpeed + 0.2
	end

	ball.AssemblyLinearVelocity = finalDirection * ballSpeed
end

RunService.Heartbeat:Connect(updateBall)

intentionally script it to sometimes hit into walls, using the math random to determine how sharp the angle is