Object on server moving slower than object on client

For context, I’m coding a tower defense enemy system. They follow a set amount of points along the path only on the client. For the server, to reduce memory usage and ping, it is simply a folder with values. The primary values at hand here is the Speed value and the CFrame value. The speed value is used in a formula that is the same on the server and client to move it accordingly.

SERVER: It measures the time it will take from each point to the next (they are equally spaced apart) and it cuts it in half. Once it waits half of the time, it sets the CFrame value to the midpoint between the two parts, once it waits the other half it sets it to the destination. The half transparent part in the clip is the server side part.

CLIENT: It measures the time it will take from each point to the next and tweens the enemy object to the next point via CFrame on the root part. Everytime the CFrame value from the server changes, if it is too far away it will correct itself once it reaches the next checkpoint.

The issue: The time values are the exact same and I’ve tried multiple fixes. I’ve noticed that the Client object goes faster than the server object, both ending at different times.

CLIENT CODE:

local function setUpTweeningConnection()
				repeat wait() until disconnectTweens == false
				
				if connection then
					connection:Disconnect()
				end
				if deathConnection then
					deathConnection:Disconnect()
				end
				
				connection = cframe:GetPropertyChangedSignal("Value"):Connect(function()
					local mag = (root.Position - (cframe.Value).Position).Magnitude
					if mag >= maxDist.Value then
						print(mag)
						disconnectTweens = true
						setUpTweeningConnection()
					end
					--root.CFrame = cframe.Value * CFrame.new(0,root.Size.Y * 1.5,0)
				end)

				deathConnection = game.Workspace.ServerEnemies.ChildRemoved:Connect(function(child)
					if child == EnemyData then
						if cleaned == false then
							cleaned = true
							disconnectTweens = true
							enemy:Destroy()
							connection:Disconnect()
							deathConnection:Disconnect()
						end
					end
				end)

				for waypoint = currentNode.Value, #pathFolder:GetChildren() do
					local mag = (pathFolder[waypoint].Position - pathFolder[waypoint - 1].Position).Magnitude
					local TimeToWait = 4/speed.Value
					print(TimeToWait)
					ts:Create(root,TweenInfo.new(TimeToWait,Enum.EasingStyle.Linear),{CFrame = pathFolder[waypoint].CFrame * CFrame.new(0,root.Size.Y * 1.5,0) * offset}):Play()
					wait(TimeToWait)
					if waypoint == #pathFolder:GetChildren() then
						cleaned = true
						disconnectTweens = true
						enemy:Destroy()
						if connection then
							connection:Disconnect()
						end
						if deathConnection then
							deathConnection:Disconnect()
						end
						break
					end
					if disconnectTweens == true then
						disconnectTweens = false
						break
					end
				end
			end
			
			setUpTweeningConnection()

SERVER CODE:

for waypoint = 2, #pathFolder:GetChildren() do
							if newEnemy and newEnemy:FindFirstChild("cframe") and newEnemy:FindFirstChild("Speed") and newEnemy:FindFirstChild("Health") and newEnemy:FindFirstChild("CurrentNode") then
								local mag = (pathFolder[waypoint].Position - pathFolder[waypoint - 1].Position).Magnitude
								local TimeToWait = 4/enemySpeed
								print(TimeToWait)
								print(TimeToWait/2)
								if destroyed == true then break end
								wait(TimeToWait/2)
								newEnemy.cframe.Value = CFrame.new(pathFolder[waypoint - 1].Position,pathFolder[waypoint].Position)
								if destroyed == true then break end
								newEnemy.cframe.Value *= CFrame.new(0,0,-mag/2)
								wait(TimeToWait/2)
								if destroyed == true then break end
								newEnemy.cframe.Value *= CFrame.new(0,0,-mag/2)
								--[[local mid = getMid(pathFolder[waypoint].Position, pathFolder[waypoint - 1].Position)
								newEnemy.cframe.Value = CFrame.new(getMid(pathFolder[waypoint - 1].Position, mid),pathFolder[waypoint].Position)
								wait(TimeToWait/4)
								if destroyed == true then break end
								newEnemy.cframe.Value = CFrame.new(mid,pathFolder[waypoint].Position)
								wait(TimeToWait/4)
								if destroyed == true then break end
								newEnemy.cframe.Value = CFrame.new(getMid(pathFolder[waypoint].Position, mid),pathFolder[waypoint].Position)
								wait(TimeToWait/4)
								if destroyed == true then break end
								newEnemy.cframe.Value = pathFolder[waypoint].CFrame
								wait(TimeToWait/4)--]]
								if destroyed == true then break end
								newEnemy.CurrentNode.Value = waypoint
							end
							--ts:Create(newEnemy.Position,TweenInfo.new(TimeToWait,Enum.EasingStyle.Linear),{Value = pathFolder[waypoint].Position}):Play()
						end

https://gyazo.com/5aa75b88d23857c4078aed7dafc264bb

Forgot to add the clip, sorry.

This is expected and you just need to make it so it doesn’t show it on the server and just calculates the damage it does.

No this is precisely the issue. This isn’t supposed to happen, as the positions between the two should sync up and be close to perfect in studio as it has no latency and to calculate the damage, or to calculate if it should be hit, they need to sync up.

If the towers are checking what enemies are in range it would only make sense for the client to see where it is for the server. It especially breaks when the enemy is fast, they will keep correcting back to the correct spot, however this looks terrible in action.

I think something was added to studio several years ago saying that this is a simulation of server communication and it’s intentionally laggy.

Make them use lerping or compensate for the client’s ping.

I’ve already tried lerping and I don’t think this was purposely added.