Thoughts on improving character validation performance

I’m starting to go down the anti-exploiter rabbit hole. I started out syncing the server-client time (for cooldowns, with a small margin of error). Now I’m capturing the player’s character/positions and tick(), verifying that the player did not teleport by using those positions, capturing the player’s ping, capturing the current FPS of the game, checking/removing old table entries, capturing each player’s velocity and doing a check on that velocity. I have most of this done every frame inside Stepped and fully plan to add more. I then read this amazing answer by @Corecii to an exploitation question and now I’m worried about the performance.

If I include everything in that post, will I take a performance hit since I’m using Stepped? I noticed the thresholds in their post are for a few seconds+, meaning that for a few seconds the exploiter did exploit successfully. Since the punishment (rubberbanding, etc…) is done after the fact, should I just capture in Stepped and run the check in Heartbeat to save on performance because Heartbeat always fires after Stepped/Physics?

Something like this:

  • …Thread does other things first
  • Hits Stepped and just captures positions very quickly
  • …Thread does the physics
  • Hits Heartbeat and does all the calculations/fires raycasts/validates everything captured in Stepped

Would this be the ideal way to handle character exploits since it minimally affects physics (keeping in mind the punishment is done later)? Or should I use a combination of the two?

P.S. I kinda wish we had some form of access to NetworkReplication on the Server. For example:

  • Instead of Exploiter Teleports—>Replicated to all Clients—>Server Rubberbands
  • Have this: Exploiter Teleports—>Server Rubberbands—>Replicated to all Clients

You can do the checks on the server. This means there will be no remotes that they can use to bypass your anti-exploit. This would also remove all the delay caused by remotes.

One of the best ways to do this is with a coroutine for each player. This also means you can use datastores to save some data when the anti-exploit takes action.

Using a coroutine means you can change the data as it is running. For example when a player is teleporting through a legitimate means you created you can update a value in the coroutine so that the anti-exploit knows what’s going on at all times.

In terms of performance using a coroutine does mean that you have multiple instances running but you would have that anyway if you were checking all players simultaneously.

As for the time, checking the change in position every second seems good enough to catch most people and would make exploiting pointless. Doubled up with the logging a logging system even if they kept changing their position or speed the anti-exploit would get them.

I’ve been making some scripts that I am going to turn into a good anti-exploit system, Using anti speed, teleportation and checking a few other things It will have a datastore that stores how many times the anti-exploit has taken action. For example when a player teleports rather than banning them straight up it adds to the datastore in case it was a fling glitch. Every time it is fired data will be added so that I can get the parts around the player, the character, camera properties and the running stats of the game. This means I can use the data in case someone claims the anti-exploit system has wronged them and also to see what they are doing to try to get around it.

I should probably explain it a little further.

The only remotes I use that have anything to do with my character validation is: a remote to synchronize the client’s time and a remote to capture the client’s ping.

Absolutely everything other than those remotes are strictly done on the server. Also, the Stepped function is not an anonymous function, nor is it local to that script. It’s in a module that can be accessed by any script that requires it. I did it this way on purpose so I can manually add positions in the table in case a script needs to teleport the player (a value check then prevents false flags).

I agree coroutines speed up underlying processes, but I think they aren’t true threads in roblox lua and instead use green threading. Please correct me if I’m wrong, but doesn’t that mean coroutines currently only help to speed up subsequent code, but does not speed up the total amount of time it takes to process all of the code. Meaning it does this:

  • Process line code before coroutine
  • Process code inside coroutine
  • Process code outside coroutine after it yields
  • Process code inside coroutine after outside yields
  • Process code outside coroutine after inside yields
  • etc…

Instead of actual multithreading like this:

  • Process line code before coroutine
  • Process code inside coroutine
  • Process code outside coroutine at the same time as processing inside coroutine

Note: I’ve heard that soon we will have true parallelization

Notice the amount of processes doesn’t change while using a coroutine, meaning it still takes the same amount of time to complete it within Stepped. A code example of what I’m asking about is this:

local module = {}
local runService = game:GetService("RunService")
local tab = {}
local count = 0

--Just pretend this is only to capture positions
function module.Stepped()
	count += 1
	tab[count] = count
	print("added entry")
end

--Just pretend this is used to validate everything including raycasts and such
function module.Heartbeat()
	if tab[count] == count then
		print("we're good to go")
	end
end

runService.Stepped:Connect(module.Stepped)
runService.Heartbeat:Connect(module.Heartbeat)

And to show a logical example how the total number of processes completed before the physics runs is reduced since we’re doing the majority of it inside heartbeat:

  • Stepped Captures Position adds it to table
  • Physics runs
  • Heartbeat does all of the validation checks that the Stepped captured