Weird Jittery Physics Despite Correct NetworkOwner

I’m unsure whether this is a bug or not but here goes.

I have a ball which when kicked, sets the network owner to the player and applies the force. Occasionally, the ball acts as if the networkowner isn’t the player and acts like this:
https://medal.tv/games/roblox-studio/clips/xBSsR8GxrwfWR/d1337e6zDsqW?invite=cr-MSxOWE0sMjQxNTAxMTEs

As you can tell from the clip, the NetworkOwner IS me, but the physics are still jittery. I’m directly setting the ball’s velocity to move it and do not use any bodymovers etc.

Here’s how it’s normally supposed to work:
https://medal.tv/games/roblox-studio/clips/xBTxfuaS8ZOO4/d1337f0NYYz3?invite=cr-MSxFZW4sMjQxNTAxMTEs

From this clip, the ball reacts accordingly and moves smoothly.

Code which applies the force:

local function ApplyForce(ball: BasePart, limb: BasePart, shottype, camera: CFrame) -- applies all forces (spin, linearvelocity) to the ball
	kicked = true
	local isjumping = humanoid.Jump
	local ballpos = ball.Position
	local limbpos = limb.Position
	local playerpos = humanoidrootpart.Position
	local direction = humanoidrootpart.CFrame.LookVector * Vector3.new(1, 0, 1)
	local lookvector = camera.LookVector
	local rightvector = humanoidrootpart.CFrame.RightVector
	local totalspeed = math.clamp(ball.AssemblyLinearVelocity.Magnitude, 0, 120)
	local curveforce = GetCurveForce(ball, limb) -- done before delay to ensure that curve isn't affected by latency
	local topspinforce
	task.spawn(function()
		topspinforce = GetTopSpinForce(ball, ballpos, limb, limbpos, isjumping) -- make sure that directional info are above this because they're delayed.
	end)
	UpdateGuide(false)
	direction += Vector3.new(0, yvector, 0)
	if playerpos.Y - ballpos.Y > 1.5 and shottype == "Chest" then return end
	if ball:GetAttribute("Owner") ~= player.Name then -- waits until the server confirms the touch and transfers network ownership.
		ball.TouchOnClient:FireServer(ticks.Value, limb, ball.CFrame, kickaction) -- remotefunction was too slow, so using remoteevent instead.
		local start = tick()
		repeat task.wait() until ball:GetAttribute("Owner") == player.Name or tick() - start > 2
		if ball:GetAttribute("Owner") ~= player.Name then return end
	else
		ball.TouchOnClient:FireServer(ticks.Value, limb, ball.CFrame, kickaction)
		if shottype == "LDribble" or shottype == "RDribble" then
			task.wait(0.1) -- delay for more dribbling opportunities
		end
	end
	if ball:GetAttribute("Owner") ~= player.Name then return end
	if player:GetAttribute("Thrower") and player:GetAttribute("HasBall") then
		local start = tick()
		repeat task.wait() until not player:GetAttribute("HasBall")
	end
	if shottype == "LDribble" or shottype == "RDribble" then
		local isright = shottype == "RDribble"
		if sprinting then
			direction = humanoid.MoveDirection
		end
		if lookvector.Y <= -0.8 then
			direction *= Vector3.new(1, 0, 1)
			direction += Vector3.new(0, 1, 0)
			power = humanoid.WalkSpeed + 5
			local ObjectSpace = humanoidrootpart.CFrame:PointToObjectSpace(ball.Position)
			if ObjectSpace.Z >= 0 then
				GetCorrectAnimation("Dribble", isright):Stop()
				GetCorrectAnimation("RainbowFlick", isright):Play()
			end	
		else
			direction *= Vector3.new(1, 0, 1)
			if sprinting then
				power = humanoid.WalkSpeed + 8
				if (humanoid.MoveDirection:Dot(humanoidrootpart.CFrame.RightVector) > 0.5) then
					GetCorrectAnimation("SharpTurn", true):Play()
				elseif (humanoid.MoveDirection:Dot(humanoidrootpart.CFrame.RightVector) < -0.5) then
					GetCorrectAnimation("SharpTurn", false):Play()
				elseif humanoid.MoveDirection:Dot(humanoidrootpart.CFrame.LookVector) < -0.5 then
					GetCorrectAnimation("DragBack", isright):Play()
				else
					GetCorrectAnimation("Dribble", isright):Play()
				end	
			else
				power = humanoid.WalkSpeed + 12
				GetCorrectAnimation("Dribble", isright):Play()
			end
		end
		local dribbleforce = direction * power
		ball.AssemblyLinearVelocity = dribbleforce
	elseif shottype == "Dive" then
		local direction = CFrame.new(ballpos, playerpos).LookVector
		local direction2 = humanoidrootpart.AssemblyLinearVelocity.Unit
		local reboundpower = math.max(50, totalspeed / 2)
		ball.AssemblyLinearVelocity = -direction * reboundpower
	elseif shottype == "Header" then
		direction -= Vector3.new(0, 0.1, 0)
		if direction.Y <= 0.1 then
			direction = Vector3.new(direction.X, 0, direction.Z)
		end
		ball.AssemblyLinearVelocity = totalspeed * direction
	elseif shottype == "Chest" then
		direction = humanoidrootpart.CFrame.LookVector
		local dribbleforce = direction * (humanoid.WalkSpeed + 5)
		ball.AssemblyLinearVelocity = dribbleforce
	elseif shottype == "Tackle" then
		if linearvelocity.Enabled then
			direction = linearvelocity.VectorVelocity.Unit
		end
		local spin = direction:Cross(Vector3.new(0, 1, 0))
		ball.AssemblyLinearVelocity = direction * (power * 0.5)
		ball.AssemblyAngularVelocity = -(spin * (power * 0.5) * Vector3.new(1, 0, 1))
	elseif shottype == "Throw" then
		ball.AssemblyLinearVelocity = direction * (power / 3)
	elseif shottype == "RShoot" or shottype == "LShoot" then
		ball.AssemblyLinearVelocity = direction * power * 0.7
		if not topspinforce then 
			repeat task.wait() until topspinforce
		end
		if yvector == 0 then
			topspinforce = 0.8 * power
		end
		ball.AssemblyAngularVelocity = Vector3.new(0, curveforce, 0) + -(rightvector * topspinforce * Vector3.new(1, 0, 1))
	end
end

On the server, it sets the ball’s network ownership to the player and you can see from the first clip, it does it successfully.

If this IS a bug, I don’t have permission to post yet and would appreciate if someone would do it for me.

Thanks for reading.