Jupp, I can confirm theres no more wiggling back and forth.
Just noticed that it has a minor side effect of delaying the coming back to the norm once latency changes, but changing it to power of 2 makes everything more or less ok and allows ping spike recovery.
double delta = Math.Pow(Math.Abs(displayTime - targetTime), 2);
Yup.
[quote=“cybercritic, post:106, topic:5747”]
Just noticed that it has a minor side effect of delaying the coming back to the norm once latency changes, but changing it to power of 2 makes everything more or less ok and allows ping spike recovery.[/quote]
Well, it kindda works, but it makes stabilization take quite a while. Like, a minute or so from what I tested. Basically you’ve nerfed the power of the time smoother so much that it’s almost as if you had toggled it off… which makes the jittering go away, at the cost of taking forever to stabilize in case of a latency change. Although to be honest I have no idea how agressive it’s supposed to be set to, to adapt to real Internet conditions. But since the change is very easy I can implement it in game and see how it works.
I’m glad it’s not my decision because there’s a lot of mileage to be had out of the server determining where everything is all the time. Interaction at speed, orbiting planets/stations, spinning planets, and so forth. I think client prediction is going to be forever haunting the project as you move forward because it just wasn’t designed with Battlescape’s velocities in mind.
I’ll reiterate my thought that clients might perform orientation prediction, but not translation prediction. Orientation seems eminently practical because of the scale of the numbers, while translation prediction is the real sore point. I wonder how bad the lag could get with orientation prediction before players got annoyed with ship controls.
Going with the need to have the recovery from latency spikes process quicker, I made some dirty adjustments to the code. I keep an accumulator in the extrapolation function, which is then used in the smoothing function. In essence when the client is far behind because of a lag spike the time smoothing is increased a bit. Now everything functions more or less as the original code except the stutter is gone.
double delta = Math.Pow(Math.Abs(displayTime - targetTime + this.accExtrap), 2);
delta = Math.Max(delta, 0);
delta = Math.Min(delta, 0.75);
displayTime = lerpDT(displayTime, targetTime, delta, elapsed);
int currentSnapshotID = findSnapshot(displayTime);
if (currentSnapshotID == -1)
{
// extrapolate latest snapshot
isExtrapolating = true;
if (snapshots.Count > 0)
{
Snapshot lastSnapshot = snapshots[snapshots.Count - 1];
double t = displayTime - lastSnapshot.m_time;
localPos2 = lastSnapshot.m_pos + lastSnapshot.m_vel * t;
this.accExtrap += t * 2;
this.extraCount += 2;
}
}
else
{
isExtrapolating = false;
// interpolate between current and next snapshot
Snapshot snapshot0 = snapshots[currentSnapshotID + 0];
Snapshot snapshot1 = snapshots[currentSnapshotID + 1];
double t = (displayTime - snapshot0.m_time) / (snapshot1.m_time - snapshot0.m_time);
t = Math.Max(t, 0);
t = Math.Min(t, 1);
localPos2 = snapshot0.m_pos + (snapshot1.m_pos - snapshot0.m_pos) * t;
localVel2 = snapshot0.m_vel + (snapshot1.m_vel - snapshot0.m_vel) * t;
if (this.extraCount > 0)
{
this.accExtrap -= this.accExtrap / this.extraCount;
this.extraCount--;
}
else
this.accExtrap = 0;
}
There is probably a more reliable way to do this…
Everything I love about this community is right here in this thread.
A developer that shares problems, and knowledgeable players that want to help solve it! This level of coding goes way over my head, but I think this kind of collaboration is fantastic. You all win 10 Awesome Points!
Ok @inovaeflavien I think I got it.
TLDR
Rewrote setTargetTime to be more self explanatory. Fixed time stuttering issue.
Limited snapRate (snapStep) to be multiple of physicsStepServer. Fixed interpolation stuttering issue.
Added limited execution on client for both render as well as physics to make them more controllable. Will probably be in the game anyway.
Here it is:
http://lomsor.com/inovae/InterpolationTest/InterpolationTest_Lomsor_10.zip
The cyan ring is the render Position. Ignore the Green one. The green one represents the client simulating physics of Entity2. I only gave it the initial value that’s why it’s where it is.
Gray rectangles represent all the positions the client knows about.
Black rectangles are snapshots send by the server but not received by the client yet.
I still want to add local player setback when time is changed. Will do that later. That’s not that important.
The issue with the stuttering that appeared when setting “snapRate” to, for instance, 1/30 was due to the server sending packets at a different rate than its physics update. Resulting in sent positions that were not correct at the specified timestamp.
serverPos2 = serverPos2 + serverVel2 * physicsStep; // move local player
snapshot.m_time = (double)snapTickID * Settings.snapRate;
If those are not multiple of each other the serverPos is valid at a different time than m_time.
@cybercritic pointed out this might not the case. But there being some leftover time on the physics while loop creating the discrepancy. Would be good to know the real source. Timing is hard to do. 
Limiting snapRate (I called it snapStep in my version, it is an interval like physicsStep after all) to be a multiple of physicsStepServer solves this issue.
snapRate (snapStep):
If it’s smaller than physicsStepServer it sends data faster than it is generated on the server.
If it’s a multiple of physicsStepServer sends correct data with correct timestamps.
If it’s not a multiple of physicsStepServer sends correct data but with incorrect timestamps
I tried to rewrite the server so it sends out the correct timestamp for older data. But must have introduced another source of stutter. That didn’t work.
It would be the best solution though. Allowing for any physics update rate and any snapshot update rate. Also allowing to separate them.
Here’s my attempt at it: (THIS DOES HAVE STUTTER - Work in progress)
http://lomsor.com/inovae/InterpolationTest/InterpolationTest_Lomsor_11.zip
The executable in the 10 file is giving me the standard red/green circles and nothing else. However, the 10 executable in the 11 file is giving me the above behavior. I love the additional information, and it’s obvious what’s happening as lag increases. I’d like a greater contrast between the two sets of snapshots however. There’s no need to be subtle.
I’m a stickler for names as well and there are a number of variables that I would rename in order to better understand what’s going on. I just don’t have the time to look at this too hard.
As with the executable in the 10 file, I got the straight red/green circles so I’m not sure if it’s showing me what you intended.
Sorry I didn’t check the debug folder. I renamed the project to keep track. (Bad practice. Sorry)
![]()
This should be the right executable.
I found a bug with the time smoothing where “delta” might get stuck on value “1” and won’t interpolate.
You can try to reproduce it by setting “snapStep” to “physicsStepServer*1” (100 snaps a second). Then increasing virtual latency and after it stabilised removing all the latency fast (hold button).
“delta” will be stuck at “1” and thus not interpolate.
I did touch the delta calculation a little. delta does influence the speed of interpolation. In case when interpolating from current position to target position.
@alllistening What are the drawbacks of just using a fixed “delta” or “f” like it’s called in the article?
I think I’ll go with fixed for now.
Ok. I was asked to strip all the remenants of my debuging so the actuall changes I made are more obvious.
Here it is:
http://lomsor.com/inovae/InterpolationTest/InterpolationTest_Lomsor_12.zip
I left in the “snapshot” display.
It looks good, Lomsor. If anything, it looks like the actual ship position is more jittery than the interpolated position, but that perception may be influenced by the packet objects moving around so much.
I’ll again ask for greater contrast in the coloring of packets that have been received versus the packets that have been delayed. Also, why is there a gap in the packet stream between those two sets?
A thought that occurs to me is the stability of latency (aka “packet delay variation” or “jitter”). The test rig introduces a 5% variability, and the smoothing handles that just fine. A significantly-larger variability will cause the server ship to move forwards and backwards along its actual path. I threw 50% variability just to see the effect, and it was certainly noticeable with such extreme variability.
The question is: how stable is latency? If latency changes regularly, then players shooting at another ship are going to miss because it’s going to move in non-newtonian ways. It will move forward or backwards in time along the path that the actual ship flew.
You can simulate this by mashing PAGEUP and PAGEDOWN buttons to change the latency quickly. Just pushing the latency from 0ms to 1000ms is a clear way to see the effect as the server ship falls far behind. When the latency is constant, the algorithm does well, and the ship is pretty darned solid and steady. But that’s the concern: will latency remain constant (or within some “narrow” range of values)?
That’s because everything received by the server is moving forward and backward in time when time is adjusted. If the client would also move forward and backward in time that could eliminate such a situation.
Configuring this whole network thing sure won’t be the easiest. Jitter could be measured though and be used as an input for the buffer. The higher the jitter. The more leeway is given.
So I analysed @lomsor’s code and getting to the hard core of it, he puts a threshold on the minimum smoothing rate (by doing a lot of fancy things). Essentially the same can be achieved with just changing a single line of code in @inovaeflavien’s original code.
from
if (smoothTimerState)
to
if (smoothTimerState && Math.Abs(displayTime - targetTime) > 0.06)
and the micro stutter is gone. Now, I’m not sure if the stutter will always stay in such a range at different velocities.
I changed this too for good measure, that ramp should never go to 1.0, it’s an instant jump.
double delta = Math.Abs(displayTime - targetTime) * 3;
delta = Math.Max(delta, 0);
delta = Math.Min(delta, 0.75);
displayTime = lerpDT(displayTime, targetTime, delta, elapsed);
Yes, if we were flying along in space next to each other, we’d stay rock steady. Unfortunately, we’d have to fly that way with input control lag. If a roundtrip to the server is 100ms, then everything we do would be delayed by 100ms. Longer roundtrips make it progressively worse, of course.
On top of that, if we were flying above the surface of a moon, we’d see our ships accelerating and decelerating for no apparent reason. The moon serves as an absolute coordinate reference and the impact of variable latency on the smoothing algorithm would be visible.
I say INS should do a card game instead.
Nice cutdown on complexity.
The reason I did the whole averageLatency thing was to get a more precise reading of the actual target time. The source of the jitter lied in the original code just taking whatever it got,
Although actual target time is not so important. I give you that.
Now when you try to minimise the buffer as much as possible to get the renderTime as close as possible to the serverTime that’s a little bit different.
I just tried out measuring jitter and using that as part of the targetTime calculation and I was able to dramatically decrease the fixed buffer time. Thus bringing the Pos2 much closer to its actual position without extrapolation.
No.
You change time (smooth or not) at which point everything (including the player) is set forward and backwards in renderTime. You can still let the player control that rendered ship directly after and before that.
The drawback of this method is that player with lower ping might get hit behind corners by a player with higher ping.
This method is used by game after game. It’s the one valve uses. (< link Flavien posted earlier)
In a game with not many corners I think this approach may even be the best it can ever get.
That’s why the code needs to be tuned. Either by human or code.
As you can see the time smoother should not be active all the time. From the beginning I said, if you use it YOU WILL GET DISTORTIONS. Because you are literally distorting time by stretching and contracting it. Only use it if needed.
I understand the Valve technique. I apparently don’t understand what you’re saying.
That’s alright.
Because now you can try it out. 
Here it is:
http://lomsor.com/inovae/InterpolationTest/InterpolationTest_Lomsor_14.zip
It includes the jitter compensation as well as physics based on the render time. So if the time changes or even stops, everything stops.
Edit: OH nearly forgot. You can move your own ship with WASD.
To not have to save past positions of the player ship I limited the time interpolation to only be able to stop time. Not run it backwards.
All i had to do after that is save the renderTime when the last render happend.
Then first run smoothTime.
And after that the physics calculation that calculates physics on the basis of elapsed time since last render instead of just elapsed time like it was before!
The jitter compensation introduces some “bounce” to the timeSmoothing. This is due to a change in latency also being registered as a massive jitter spike. I think that bounce isn’t that bad. But one could make the algorithm indentify what is a jitter and what is a permanent change in latency.
I don’t think this is needed though. As in real conditions jitter is all that actually happens and the jitter compensator adds enough buffer.
The fixed buffer is actually set to exactly 1 snapshot. This is the lowest it can go! 
@cybercritic criticised my heavy use of constants. This is a problem yes. But I think most of those could be found by studying their relations, programatically and on the fly.
I work with regulators all the time and there you have a lot of constants you need to configure to get a system running. This problem is much less dynamic and we have much more, precise information than some of the stuff I work with.
I think either we can find direct relations to the settings. Like with the snapRate thing or the timeOffset = snapStep.
Or in the worser case have code adjust the settings based on real world conditions at the client.
Also. If you wonder why you break formation when you change speed. This is due to the change speed command being transmitted FTL to the server. But you receiving the packet takes some time.
For instance. You are at snapshot 104. The newest Snapshot on the Server is 122,
When you change speed you change the speed of your ship in snapshot 105. But the other ship changes speed in snaphot 123.
This is why you can shoot people that have already moved behind corners.
What my solution solves on the other hand is that you can keep formation even if your internet connection changes.
Edit: I should have put in a “speed of time” indicator in the top bar. O welp. You can see how time slows when your ship seems to fly slower but its speed is still the same.
Edit Edit: Sorry forgot the contrast thing.
Thanks for the update.
As it runs right now, it has some very appealing traits for interaction at speed.
So if the commands were sent with latency, what would I see?
There remains the problem that I mentioned earlier: if you play with the latency, you’ll find that the client ship slows down and speeds up. You’ll need to stay on the grid to see this happening, of course. I was moving at speed 3, going to zero latency, letting things settle down, then going to 1000ms latency as fast as possible. You’ll see the grid sliding by more slowly. I assume that the effect would be more pronounced at speed 4.
That’s the effect I was talking about.
I don’t see it as a deal breaker, but it’s something that INS would have to be prepared to explain to players on poor quality connections because they would see non-newtonian motion. It would only be noticeable near stationary objects. Rings, terrain, stations, etc.
Slowing down the local player’s time doesn’t sound like a good solution to me. It seems to work in the context of this test app, but for the real game the client is still supposed to send its inputs to the server for validation, and both are supposed to run the exact same physics with the same tick rate. That’s where error correction from the server being authoritative comes from and Lomsor’s proposal seems like this wouldn’t be doable anymore. But maybe parts of the code can be salvaged, I’ll give it a try.
As for the idea of adding a “stabilization threshold”, I had already tested that some time ago. I abandonned it because it always seemed like some constants were working in some cases and breaking in other conditions, but maybe I didn’t go deep enough with that idea.
I was able to dramatically truncate everything down.
Also removed some remenants.
setTargetTime is now run every time. No stutter.
Here it is:
http://lomsor.com/inovae/InterpolationTest/InterpolationTest_Lomsor_15.zip
Did some changes to setTargetTime. Significantly simplified.
It reacts fast to increasing jitter/latency. Slow to decreasing jitter/latency.
Most constants are gone!
Only constant left is this one:
uint latencySampleAmmount = (uint)(1 / snapStep * 8); // Ammount of latency samples to take in order to calculate targetTime. (Modify multiplier to set number of seconds buffered)
Increasing it reduces likelihood of extrapolation but increases delay before jitter/lag recovery.
Decreasing it increases likelihood of extrapolation but allows for faster jitter/lag recovery.
Likelihood of extrapolation depends on jitter amount and size of buffer.
Reducing the sample Amount (not multiplier) to one or similar low amounts will make the stutter resurface.
And this one:
public static double timeOffset = snapStep*1; // artificial time offset between snapshot and display time. Additional to measured jitter/latency.
Increasing it reduces likelihood of extrapolation but adds constant latency.
Decreasing it increases likelihood of extrapolation.
Bolow 1 multiplicator results in guaranteed extrapolation.
Human/Algorithm needs to choose how to set those two … there are several options. Depending what you favour.