Spherecast goes through walls

I have a ball shooting system where the ball goes through several raycasts to reflect its velocity. There is a problem where when a ball hits one of the walls, it sometimes doesn’t reflect the velocity and instead goes through the wall.

https://gyazo.com/0faef75cf36ac167b98b0738cfd4bd2f

Code here:

local Players = game:GetService('Players')
local Player = Players.LocalPlayer
local Character = Player.Character or Player.CharacterAdded:Wait()
local CollectionService = game:GetService('CollectionService')
local PlayerBalls = workspace:WaitForChild('PlayerBalls')
local RunService = game:GetService('RunService')
local Cursor = workspace:WaitForChild('Cursor')
local Line = Cursor:WaitForChild('Line')
local Line2 = Line:WaitForChild('Line2')
local LineLine = Line:WaitForChild('Line')
local Arrow = Cursor:WaitForChild('Arrow')
local Circle = Cursor:WaitForChild('Circle')

local Button = script.Parent
local TextLabel = Button:WaitForChild('ShootTextLabel')
local Gradient = Button:WaitForChild('UIGradient')
local OGColor = Gradient.Color

function update(ball, SPEED) -- this function is called every frame prior to render
	RunService.Heartbeat:Connect(function(delta_time)
		if not ball or not ball.PrimaryPart then return end
		-- MOVE
		ball.PrimaryPart.CFrame = ball.PrimaryPart.CFrame * CFrame.new(0, 0, -SPEED * delta_time)
		ball.PrimaryPart.Orientation = Vector3.new(0, ball.PrimaryPart.Orientation.Y, ball.PrimaryPart.Orientation.Z)

		-- check for collisions
		local params = RaycastParams.new()
		params.FilterType = Enum.RaycastFilterType.Exclude
		params.FilterDescendantsInstances = {Arrow, Circle, Line2, LineLine, CollectionService:GetTagged('ShootingZone'), CollectionService:GetTagged('Barrier'), Character}

		local raycast = workspace:Spherecast(ball.PrimaryPart.Position, 1, ball.PrimaryPart.CFrame.LookVector, params)

		-- if collided, then reflect
		if raycast and raycast.Normal and raycast.Instance.Name ~= 'Color' and not CollectionService:HasTag(raycast.Instance.Parent, 'Ball') and raycast.Instance.Name ~= 'AreaZone' then
			local normal = raycast.Normal
			local instance = raycast.Instance
			if instance.Parent:HasTag('Block') then
				instance.Parent:SetAttribute('HP', instance.Parent:GetAttribute('HP') - 1)
			end
			local lookVector = ball.PrimaryPart.CFrame.lookVector
			local reflectionVector = lookVector - 2 * lookVector:Dot(normal) * normal

			-- after we obtain the reflection vector, we need to use cframes to rotate the ball
			ball.PrimaryPart.CFrame = CFrame.lookAt(ball.PrimaryPart.Position, ball.PrimaryPart.Position + reflectionVector)
			-- keep in mind im not the best with cframes, may be a better way to do this ^^^
		end

		if ball.PrimaryPart.CFrame.Z > -396 or ball.PrimaryPart.CFrame.Z < -500 then 
			ball:Destroy()
			return 
		end
	end)
end

script.Parent.MouseButton1Click:Connect(function()
	if TextLabel.Text == 'SHOOT!' then
		TextLabel.Text = 'STOP'
		Gradient.Color = ColorSequence.new({ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 0, 0)), ColorSequenceKeypoint.new(1, Color3.fromRGB(139, 0, 0))})
		for i = 1, 3 do
			if TextLabel.Text == 'STOP' then
				local ball = workspace:WaitForChild('Balls'):WaitForChild('Big Ball'):Clone()
				ball.Parent = PlayerBalls
				ball.PrimaryPart.CFrame = CFrame.lookAt(Line2.Position, Circle.Position)
				ball.PrimaryPart.Orientation = Vector3.new(0, ball.PrimaryPart.Orientation.Y, ball.PrimaryPart.Orientation.Z)
				update(ball, 100)
				task.wait(.4)
			end
		end
		
		repeat task.wait() until #PlayerBalls:GetChildren() == 0
		TextLabel.Text = 'SHOOT!'
		Gradient.Color = OGColor
	else
		for i, v in PlayerBalls:GetChildren() do
			v:Destroy()
		end
		
		TextLabel.Text = 'SHOOT!'
		Gradient.Color = OGColor
	end
end)

Why not cast an initial ray from where the ball is being shot from/where when it intersects a wall, then interpolate to where the ray intersects the part instead of casting a ray every frame?