Server NPC's movement wobblying when using alignPosition combining with pathFinding - (Client/Server NPC Architecture)

Watch 13-07-2026 10-51-25 | Streamable – The clip desmonstrates the error

Currently Im using alignPosition and alignOrientation to move the npcs between pathfinding nodes. So they can actively glide through those nodes to reach the target.

local PathfindingService = game:GetService("PathfindingService")

local pathProfile = {
	AgentRadius = 2,
	AgentHeight = 5,
	AgentCanJump = false,
	WaypointSpacing = 5
}

local ChaseState = {}

function ChaseState.OnEnter(bot, data)
	local hrp = bot.PrimaryPart
	data.LastTargetPos = nil
	data.Waypoints = nil
	data.CurrentNodeIndex = 1
	data.LastNodePos = nil
	data.LastLookUpdate = 0

	if hrp then
		
		if not hrp:FindFirstChild("BotPathModifier") then
			local modifier = Instance.new("PathfindingModifier")
			modifier.Name = "BotPathModifier"
			modifier.PassThrough = true
			modifier.Parent = hrp
		end

		if not hrp:FindFirstChild("MoveAttachment") then
			local attachment = Instance.new("Attachment")
			attachment.Name = "MoveAttachment"
			attachment.Parent = hrp

			local alignPos = Instance.new("AlignPosition")
			alignPos.Name = "BotAlignPosition"
			alignPos.Attachment0 = attachment
			alignPos.Mode = Enum.PositionAlignmentMode.OneAttachment
			alignPos.Position = hrp.Position
			alignPos.ForceLimitMode = Enum.ForceLimitMode.PerAxis
			alignPos.MaxAxesForce = Vector3.new(100000, 0, 100000)
			alignPos.MaxVelocity = data.Config.Speed or 16
			alignPos.Responsiveness = 40
			alignPos.RigidityEnabled = false
			alignPos.Parent = hrp

			local alignOri = Instance.new("AlignOrientation")
			alignOri.Name = "BotAlignOrientation"
			alignOri.Attachment0 = attachment
			alignOri.Mode = Enum.OrientationAlignmentMode.OneAttachment
			alignOri.MaxTorque = 100000
			alignOri.Responsiveness = 30
			alignOri.Parent = hrp
		end
	end
end

function ChaseState.OnUpdate(bot, data, deltaTime)
	local targetHRP = data.Target and data.Target:FindFirstChild("HumanoidRootPart")
	local targetHumanoid = data.Target and data.Target:FindFirstChild("Humanoid")

	if not targetHRP or not targetHumanoid or targetHumanoid.Health <= 0 then
		return "Idle"
	end

	local hrp = bot.PrimaryPart
	local currentPos = hrp.Position
	local targetPos = targetHRP.Position

	local alignPos = hrp:FindFirstChild("BotAlignPosition")
	local alignOri = hrp:FindFirstChild("BotAlignOrientation")

	local dx = targetPos.X - currentPos.X
	local dz = targetPos.Z - currentPos.Z
	local distanceFlat = math.sqrt(dx * dx + dz * dz)

	local now = os.clock()

	if distanceFlat <= 2 then
		if alignPos and alignPos.MaxVelocity ~= 0 then
			alignPos.MaxVelocity = 0
		end

		if alignOri and (now - data.LastLookUpdate) > 0.2 then
			data.LastLookUpdate = now
			local lookTarget = Vector3.new(targetPos.X, currentPos.Y, targetPos.Z)
			if (lookTarget - currentPos).Magnitude > 1 then
				alignOri.CFrame = CFrame.lookAt(currentPos, lookTarget)
			end
		end
		return nil
	end

	local speed = data.Config.Speed or 16
	if alignPos and alignPos.MaxVelocity ~= speed then
		alignPos.MaxVelocity = speed
	end

	local targetDx = targetPos.X - (data.LastTargetPos and data.LastTargetPos.X or 0)
	local targetDz = targetPos.Z - (data.LastTargetPos and data.LastTargetPos.Z or 0)
	local targetMovedDist = math.sqrt(targetDx * targetDx + targetDz * targetDz)

	if not data.LastTargetPos or targetMovedDist > 4 then
		data.LastTargetPos = targetPos

		task.spawn(function()
			local path = PathfindingService:CreatePath(pathProfile)
			local success, _ = pcall(function()
				path:ComputeAsync(currentPos, targetPos)
			end)

			if success and path.Status == Enum.PathStatus.Success then
				data.Waypoints = path:GetWaypoints()
				data.CurrentNodeIndex = 2
				data.LastNodePos = nil
			end
		end)
	end

	if data.Waypoints and data.CurrentNodeIndex <= #data.Waypoints then
		local nodePos = data.Waypoints[data.CurrentNodeIndex].Position
		local nx = nodePos.X - currentPos.X
		local nz = nodePos.Z - currentPos.Z
		local nodeDistanceFlat = math.sqrt(nx * nx + nz * nz)

		if nodeDistanceFlat < 4 then
			data.CurrentNodeIndex += 1
		end

		if data.Waypoints[data.CurrentNodeIndex] then
			local nextNodePos = data.Waypoints[data.CurrentNodeIndex].Position

			if alignPos and data.LastNodePos ~= nextNodePos then
				data.LastNodePos = nextNodePos
				alignPos.Position = nextNodePos
			end

			if alignOri and (now - data.LastLookUpdate) > 0.2 then
				data.LastLookUpdate = now
				local lookTarget = Vector3.new(nextNodePos.X, currentPos.Y, nextNodePos.Z)
				if (lookTarget - currentPos).Magnitude > 2 then
					alignOri.CFrame = CFrame.lookAt(currentPos, lookTarget)
				end
			end
		end
	else
		if alignPos and data.LastNodePos ~= targetPos then
			data.LastNodePos = targetPos
			alignPos.Position = targetPos
		end

		if alignOri and (now - data.LastLookUpdate) > 0.2 then
			data.LastLookUpdate = now
			local lookTarget = Vector3.new(targetPos.X, currentPos.Y, targetPos.Z)
			if (lookTarget - currentPos).Magnitude > 2 then
				alignOri.CFrame = CFrame.lookAt(currentPos, lookTarget)
			end
		end
	end

	return nil
end

function ChaseState.OnExit(bot, data)
	if bot.PrimaryPart then
		local alignPos = bot.PrimaryPart:FindFirstChild("BotAlignPosition")
		if alignPos then
			alignPos.MaxVelocity = 0
		end
	end

	data.Waypoints = nil
	data.LastTargetPos = nil
	data.LastNodePos = nil
end

return ChaseState

This is the total chunk of code that is used for moving the npc. Finite state machine approach.
I believe there could be some alternatives that can be used to move the server npc(non-humanoid one, only has a root part). But I havent figured out any!

EDIT: Sorry, I should be more specific about the error… In the video, it might be hard to actually see the issue, but the red box that is moving, keeps on stuttering and trembling whenever it is traversing on its path, it could be some collision issue but Im not sure at all..