Vector3 interpolation estimation not working how expected (ping compensation)

Currently I’m trying to work on a lag compensation system that grabs what the player would have seen when they tell the server. However it is absolutely vital that the ping is effectively removed in the calculations carried out on the server as it is very much a real time thing (its to do with hit detection).
I’m currently using Vector3s instead of CFrames just for output reasons.
At the moment, I’m trying to use the time between two known recorded points (could be 0.1s of each other) and using the time of the ping to calculate the alpha value between each two points, in order to calculate a Vector3 which would be what the character would see.
It requires saving lots of Vector3 bits of data (between 8 and 30) in a table and updating them regularly. This could optimally range between 8Hz and 30Hz.
This is my current script:

local start = Vector3.new(0, 0, 0)
local numx = 90
local finish = Vector3.new(0, numx, 0)
local current = Vector3.new(0, 0, 0)
local alpha = 0
local atarget = numx
local flip = false

local ping = 30/60

local refresh = 30
local seconds = 1
local tim = tick() - 1/refresh
local step

local stored = {}

game:GetService("RunService").Stepped:Connect(function(st)
	step = st
	if not flip then
		alpha = alpha + 1
		if alpha == atarget then 
			flip = true 
		end
	else
		alpha = alpha - 1
		if alpha == 0 then 
			flip = false 
		end
	end
	current = start:lerp(finish, alpha/atarget)
	if tick() - tim >= 1/refresh then
		table.insert(stored, 1, {cf = current, t = tick()})
		tim = tick()
		if tick() - stored[#stored].t > seconds then
			table.remove(stored, #stored)
		end
	end
end)

local function lerp(a,b,c) return a + (b - a) * c end

local function round(n, x)
	if not x then x = 4 end
	return math.floor(n*(10^x) + 0.5)/(10^x)
end

wait(2-ping)

local target = current
local targettime = tick()
local x = 0

local uf5 = tick()
repeat game:GetService("RunService").Stepped:Wait()  until tick() - uf5 >= ping
print(tick() - uf5)
ping = tick() - uf5

local t2 = tick() - ping

local percent

local f1, f2
for i,v in pairs(stored) do
	if v.t <= t2 then
		f1 = stored[i-1] or {cf = current, t = tick()}
		f2 = stored[i]
		percent = (t2-f1.t)/(f2.t-f1.t)
		print(f1.t, t2, f2.t, percent, targettime, t2, ping)
		print(f1.cf, target, f2.cf, lerp(f1.t, f2.t, percent))
		print(f1.cf:lerp(f2.cf, percent))
		break
	end
end

I am aware this code may use some bad practises and doesn’t actually involve any communication with the player at the moment, I’m just trying to simplify the problem so that it works out for me.
Half the program is to do with updating a Vector3 regularly at 60fps (what would happen in a server), then grabbing the Vector3 every other frame with the Vector3 and the timestamp.
Ignoring all bad practises, can anyone offer any insight to why this might not be working? I’ve been stuck on this for about 2 and a half weeks now and its really slowing down my program flow and it’s something I need to figure out ASAP.

Thank you to everyone who helps!

2 Likes

You might not be getting replies because it’s not clear what you’re trying to do, and how the code relates to it. It also looks like about half this code is just test code?

I know what latency compensation normally looks like, but I can’t make any sense of this, or why you have a parity flipping mechanism involved at all.

I can’t tell exactly what is going on in your code, but personally the way I would go about doing this is storing data per server Heartbeat (physics step) for a set amount of time (e.g. 1 second aka 60 frames).

Now, based on the target ping I can “snap” to a physics location.
Each frame should have an average delta of 1/60th of a second.
To get the target frame I would use this: targetFrame = math.floor(realDeltaTime/(1/60)+0.5)
I first find the number of frames that should have elapsed and then round to the nearest whole number to select my frame.

You could do something more complicated using real frame deltas if you need extreme precision (and this would be more robust during server physics lag). If you want extreme precision you can store frame deltas per frame and do something like this when you want to get the target frame:

local realDeltaTime = 30/60 -- The ping
local frameDeltas = {} -- Deltas per frame
local framePositions = {} -- Positions of player each frame
local totalDelta = 0 -- The total delta accumulated
local curFrame = 1 -- The current frame

-- Populate frameDeltas per frame

-- When getting targetFrame:
while totalDelta < realDeltaTime do -- While the accumulated delta is under the ping
	totalDelta = totalDelta + frameDeltas[curFrame] -- Increase the accumulated delta
	curFrame = curFrame + 1 -- Increment the frame counter
end

targetFrame = curFrame -- Now we have our target frame
targetPos = framePositions[targetFrame] -- Now we have the position at this frame

Your first approach is the way to go, keep it simple. You can’t really improve the system to get “extreme precision”, because the biggest and most variable unknown is always going to be users’ actual ping times, so how far you roll the simulation back is already a gross approximation.

I’m quite new to any kind of lag compensation, this is a sort of solution I came up with, if you could give me any other solutions to lag compensation I would appreciate it.

To describe it:

  • Client fires bullet (creates a ray)
  • Client tells the server about ray creation
  • Ray detects hit on another player on client, hit marker is created on client
  • Server receives data from client about ray (lets say with 150ms lag)
  • Server attempts to backtrack to where the other players would have been 150ms ago to be what the player would have seen
  • Server creates a ray using backtracked CFrames
  • Server correctly detects hit and causes damage to target player despite the player not being in same position as what the server currently sees

I’m not sure if I want to store 60 frames worth of data as it could be quite costly, However I will try your solution.

That could work. The devil is in the details, as they say.

  • What do you consider “lag”, and how you measure it?
  • Are the weapons all effectively lasers, or are bullet travel time, bullet drop, and ricochets considered?
  • What do you mean by “Server creates a ray using backtracked CFrames”? More than likely, what this implies (i.e. the server doing an actual raycast) is prohibitively expensive.
  • “Server correctly detects hit”… is it really detecting hits, or validating them?

I wouldn’t see that as costly. If you have 50 players with 60 frames each you’ll have 3000 frames stored total which doesn’t amount to that much memory (at most about a megabyte but most likely less).

I would suggest measuring ping by firing a RemoteFunction and measuring the time it takes for it to finish firing on the server (the client has to technically respond). You can turn the “Server receives data from client about ray” into a server-issued request to make ping spoofing a little bit harder. When you have your measured ping you can then verify the hit at that ping on the server.