(Note that is way more fitting in #scripting-support)
Honestly, your question is very spot on, and has been a conversation for a rather long time. Useful link 1, Useful link 2.
Pretty much, almost as you said, the amount of time wait() waits depends on your performance, if the CPU is currently doing a lot of operations, thus making wait() wait longer than expected. And even when the CPU isn’t exhausted, wait() is almost always off by a certain but. There are tricks that you can do, like for example the delta time (dt, change in time) parameter which RenderStepped provides, gives you the amount of time that passed since the last frame. If at a certain frame the CPU was exhausted, which means the time till the next frame is rendered is longer, there would be a delay, stuff happening late because more time is used. (The second link explains this wonderfully). Let’s say you wanted to increment a value by 1 each second, RenderStepped will make the incrementing work perfectly, using the dt (delta time) parameter which it provides, which is basically how much time passed since the mast frame rendered (in seconds), if the game lags, the longer the time dor the next frame to render to be rendered. You set the value that you wanna increment to 2*dt, the increment times how much time passed. This is good practice. Let’s say exactly 1 second passed since the last frame was rendered (in real cases it would always be a tiny fraction of a second), you essentially multiply 2 by 1 to get 2, if the frame took 0.5 seconds to load you get 1, because half the time passed. This is useful for cases where lag occurs. If the next frame took 2 seconds, you increment by 4. We incremented the value according to how much time was passed, so even though it’s lagging, the values are set to the expected result, if we didn’t multiply by dt, we would wait 2 seconds and just add 2, and not 4 as we would expect in a lag-free game. I hope you understand.
If you’re looking for a better wait, here it is.
local RunService = game:GetService("RunService")
local function wait(n)
local dt = 0
while dt < n do
dt = dt + RunService.Heartbeat:Wait()
end
return dt
end