Position of Character.PrimaryPart updates very strangely

I am trying to make some sort of anti-cheat to prevent players from teleporting. What it does is takes their previous position of PrimaryPart and the current one and compares them. If the difference between them is too big, then they get kicked. And don’t worry about this system being bad, it can deal with intended player teleports.
Now what problem I encountered is how weirdly the position is being updated. I have ping of 50, and I do take in count player’s ping when calculating max possible difference.
It’s kind of hard to explain, so here is the screenshot:

Not sure if it’s right category to post in, so please correct me if it is not.

Could you try printing out (CurrentPosition - PreviousPosition).Magnitude for more accurate and comparable numbers, thanks.

The problem is position freezing, not calculation. So printing the Magnitude would be have same problem - it will remain at the same value.

Could you provide a sample of your code?

...

local config = require(script:WaitForChild("Config")) --no clue why it's blue, but it doesnt seem to affect anything

local last_info = {}
local players_kicked = {}

local function setPlayerInfo(plr:Player, last:{}, deltaTime:number)
	local char:Model = plr.Character
	
	if char ~= nil and last ~= nil and char.PrimaryPart ~= nil then
		local hum = char:FindFirstChildWhichIsA("Humanoid")
		
		local maxPlayerDelta = ((10 * plr:GetNetworkPing() + hum.WalkSpeed)/1000)*(deltaTime*2)
		if maxPlayerDelta < (2+(hum.WalkSpeed)/30) then
			maxPlayerDelta = (2+(hum.WalkSpeed)/30)
		end

		local delta = (char.PrimaryPart.Position - last['position']).Magnitude
		print(last['position'], "\n", char.PrimaryPart.Position)
		if char.PrimaryPart then
			if delta > maxPlayerDelta then
				--there are some double checks
			end
		end

		last_info[plr.Name] = {
			['position'] = char.PrimaryPart.Position
		}
	end
end

local function refreshKickedPlayersList()
	for plrName:string, kickTick:number in players_kicked do
		if tick() - kickTick > 1 then
			players_kicked[plrName] = nil
		end
	end
end

RunService.Stepped:Connect(function(t, dt)
	for i, plr:Player in ipairs(Players:GetChildren()) do
		local last_plr_info = last_info[plr.Name]
		if last_plr_info ~= nil then
			setPlayerInfo(plr, last_plr_info, dt)
		else
			local char = plr.Character or plr.CharacterAdded:Wait()
			last_info[plr.Name] = {['position'] = char.PrimaryPart.Position}
		end
	end
	refreshKickedPlayersList()
end)

Players.PlayerAdded:Connect(function(player)
	local antiCheatProtection = Instance.new("StringValue")
	antiCheatProtection.Name = "NO ANTICHEAT"
	antiCheatProtection.Parent = player

	Debris:AddItem(antiCheatProtection, 3)
	
	player.CharacterAdded:Connect(function(char)
		local antiCheatProtection = Instance.new("StringValue")
		antiCheatProtection.Name = "NO ANTICHEAT"
		antiCheatProtection.Parent = player

		Debris:AddItem(antiCheatProtection, 1)
	end)
end)

Players.PlayerRemoving:Connect(function(player)
	last_info[player.Name] = nil
end)

There is no problem with the code itself, it is just how weirdly the position updates.

I might have a suggestion on why this happens. I forgot to tell another thing - this happens when I apply LinearVelocity to the player. So, I guess the problem is that maybe LinearVelocity somehow glitches the position and that’s why we get big delta.
I don’t know if I should post this in #bug-reports.

Why do you check this here? You have already accessed it on this line…

local delta = (char.PrimaryPart.Position - last['position']).Magnitude

If you need character why do you only wait for it in the else branch? setPlayerInfo() just assumes Character is valid?

local char = plr.Character or plr.CharacterAdded:Wait()

This is all redundant, no reference is made to antiCheatProtection except here?

Players.PlayerAdded:Connect(function(player)
	local antiCheatProtection = Instance.new("StringValue")
	antiCheatProtection.Name = "NO ANTICHEAT"
	antiCheatProtection.Parent = player

	Debris:AddItem(antiCheatProtection, 3)
	
	player.CharacterAdded:Connect(function(char)
		local antiCheatProtection = Instance.new("StringValue")
		antiCheatProtection.Name = "NO ANTICHEAT"
		antiCheatProtection.Parent = player

		Debris:AddItem(antiCheatProtection, 1)
	end)
end)

antiCheatProtection is being counted in double checks.

I haven’t encountered this issue, but thanks anyways.

You can tidy this up with a simple check like this:

	if last == nil then return end;

	local char:Model = plr.Character;
	local hum = char and char:FindFirstChildWhichIsA("Humanoid");
	if not hum then return end;
	
	local maxPlayerDelta = ((10 * plr:GetNetworkPing() + hum.WalkSpeed)/1000)*(deltaTime*2)
	if maxPlayerDelta < (2+(hum.WalkSpeed)/30) then
		maxPlayerDelta = (2+(hum.WalkSpeed)/30)
	end

	local delta = (char.PrimaryPart.Position - last['position']).Magnitude
	print(last['position'], "\n", char.PrimaryPart.Position)
	if delta > maxPlayerDelta then
		--there are some double checks
	end

	last_info[plr.Name] = {
		['position'] = char.PrimaryPart.Position
	}