Feedback on rewind script

Hi guys! I have created the following code for hitreg systems in my game!

local entities = {}
local savedRewinds = {}
local index = 1

local Current_Step = 0
local Max_Savelength = 60


--function that inserts an entity into the array
local function AddNewEntity(e)
	local data = {}
	for _, i in pairs(e:GetChildren()) do
		if i:IsA("BasePart") then
			data[#data+1] = i
		end
	end
	data.Hum = e:FindFirstChildOfClass("Humanoid")
	
	entities[index] = data
	index = index + 1
end

--function to locate data based on a given humanoid
local function getDataFromHumanoid(hum)
	local humSet
	local ind
	for x, i in pairs(entities) do
		if i.Hum == hum then
			humSet = i
			ind = x
			break
		end
	end
	
	if humSet then
		return humSet, ind 
	else
		return false
	end
end

--function to retrieve data at given time
local function getEntityCopyAtTime(humanoid, timestamp)
	--retrieve entity parts
	local humSet, ind = getDataFromHumanoid(humanoid)
	if not humSet then print("humanoid not found in data!") return false end
	
	
	--retrieve chronologically closest data
	local nearestData
	local nearestDist = math.huge
	for _, i in pairs(savedRewinds) do
		
		local saveTime = i.timestamp
		if not nearestData then nearestData = i continue end
		local timeDist = math.abs(timestamp-saveTime)
		if timeDist < nearestDist then
			nearestData = i
			nearestDist = timeDist
		end
	end
	
	--get data specific to this humanoid
	local humData
	for x, i in pairs(nearestData) do
		if x == "timestamp" then continue end
		local saveHum = i.Hum
		if saveHum == humanoid then
			humData = i
		end
	end
	if not humData then print("humanoid not found at timestamp!") return false end
	
	--build copy and position it based on saved data
	local entityCopy = {}
	for x, i in pairs(humSet) do
		if i:IsA("BasePart") then
			local partCopy = i:Clone()
			partCopy:ClearAllChildren()
			entityCopy[x] = partCopy
		end
	end
	for x, i in pairs(entityCopy) do
		if x == "Hum" then continue end
		i.Anchored = true
		i.CFrame = humData[x]
	end
	return entityCopy
end



--insert all objects that already exists
for _, i in pairs(game.Workspace.Entities:GetChildren()) do
	AddNewEntity(i)
end

--connect the childadded event to the addnewentity function
game.Workspace.Entities.ChildAdded:Connect(AddNewEntity)


--update function 
game:GetService("RunService").Heartbeat:Connect(function()
	--new entry
	local data = {}
	data.timestamp = tick()
	
	--increment step
	Current_Step = (Current_Step+1)%Max_Savelength
	
	--build entry
	for x, entity in pairs(entities) do
		local entityEntry = {}
		for v = 1, #entity do --   -1
			local part = entity[v]
			if part then
				entityEntry[v] = part.CFrame
			end
		end
		entityEntry.Hum = entity.Hum
		data[x] = entityEntry
	end
	
	--insert entry
	savedRewinds[Current_Step] = data
	
	--update server time
	game.ReplicatedStorage.ServerTime.Value = tick()
end)

The program registers each entity in the game, saving in an array all of that entity’s pieces and the entity’s humanoid. Then using the same indices in which the pieces were registered, it saves the CFrame of each piece in an array. Later on, the function getEntityCopyAtTime() can accept a humanoid and a timestamp as parameters, and will look for the dataset containing that humanoid that is chronologically closest to the time provided. the code is configured to save CFrames up to 60 instances, at which point the code will start overwriting the oldest instances in the array. assuming the average heartbeat is .05 seconds, this configuration will allow the game to look up to 3 seconds into the past to see where players were, allowing a maximum effective ping of 3000 for accurate hit registration.

In the gif below, I tested the code by having it place a copy of my character in the world from 2 seconds prior every time I press the L key.
651135600922e389916ef5fa7706c800

current pros and cons of this code:

pros:

  • allows for up to 3 seconds of rewind vision for the server
  • accurate up to approximately .05 seconds of error margin

cons:

  • data heavy. ~16 players per server * 16 pieces per player character * 60 time instances saved = roughly 15,360 CFrame values saved to memory at any given moment, or 983,040 bytes, which is equal to almost a megabyte. (okay, maybe not actually that bad)
  • lossy. Every time this data needs to be referenced for hit registration, the code duplicates the character model and positions it on the indicated CFrames. this duplicated model is not cached, as the many differences in character piece colliders and piece sizes would make standardized caching difficult.

if you guys have any recommendations to improve upon this, I’d love to hear them!

Maybe an idea:

Have this run on the local script. When for example, a shot is fired, make the server request the data packet. This should reduce lag, since the server isnt making the calculations it should save data being sent.

Probably wrong tho.

Exactly, if you’re allowing up to 3000ms ping, you might as well just run the hit calculations on the client.

The whole point of this is so that the server can verify hits and so that the server can maintain authority while also accounting for laggy players. If the client has control over all of this, whats the point of even having it?

You don’t need to save 3 seconds worth of frames. Most competitive games outside of Roblox only do like a second max. I would say 500 ms and anything more than that should come with input delay (or kicks you from the game which is also standard for competitive games). Also you don’t have to save every 60 frames. You can do it every 30 frames and interpolate two frames if you need to check between them. Not sure if you are doing that already

Honestly, unless you have a custom interpolation thingy, this will never be accurate. It’ll be like maybe 80% accurate. Since Roblox doesn’t disclose their interpolation date for networking