Clientside prediction / Rewind + Replay bug

Recommended Posts

Hey, I'm currently building a client/server game in C#. I've got a basic setup working pretty smoothly already, using the Lidgren network library. I based my implementation of network physics on Glenn Fiedler's article, which can be found here: http://www.gaffer.org/game-physics/networked-physics. It briefly describes a fixed timestep, input driven, client/server physics simulation. It also outlines client side prediction. My implementation is pretty straight forward. I keep a circular buffer of X number of recent moves sent to the server, and when a move comes back from the server, I check it against the move I stored in the buffer. If the position is within an arbitrary threshold, I do nothing. If the position is wrong, I go back to the matching move in my buffer, and set the correct position I just received from the server, then replay all the moves up to the present based on the corrected state. For the most part, it works just as it should. Problem is, there seems to be a bug in how I'm replaying the moves that sometimes results in a self-perpetuating loop of errors. It is always at the end of the replay that it shows up. Let's say the client is 5 moves ahead of the server when the server sends a correction. The client replays all 5 moves correctly, but #6 is wrong. Now, since we're 5 states ahead, we only find out that #6 is wrong when the client is on #11. So the client replays from 6 to 11, then 12 is wrong. Etc. The problem is that it only happens sometimes. And the error seems to cause the next one which causes the next one. There are other factors also. The above scenario happens when the input stream to the server is constant. I.e, holding down a directional key, moving your charactor. If you stop moving, the error loop will stop after a couple moves. (data is still being sent, just no movement changes). I've been trying to fix this for two weeks now, and I'm pretty much stuck. I've rewritten it 3-4 times, reviewed it 50 times, tweaked it interminabley and still I've got nothing. I don't really like asking someone else to try fixing it, because I'm not even sure exactly where the bug is, so I can't just post a little snippet of code. It would mean grabbing the whole project and figuring it out. The upside is that project is still pretty small: client is ~1000 lines, and the server is less. I'm using SlimDX for graphics, Lidgren for the network, and Farseer Physics engine for physics. IDE is VS 2008. Code is here: http://www.usedthoughts.com/mp1.zip If anyone can point out where I'm going wrong, I'd appreciate it very much.

Share this post

Share on other sites
I'm working on a client/server game that uses the exact same technique, and it works fine.

I don't have time to look at your code atm (I might later), but I can give you some advice and maybe it'll help.

It sounds like it could be an issue with timing/sequence - perhaps you're polling for input/sending commands on the client one tick too early/late? What I mean by that is you have to have worked out a very precise system where you know exactly where and when the next command occurs, what each input # represents, and so on.

Let's take your example:
Quote:
 Let's say the client is 5 moves ahead of the server when the server sends a correction. The client replays all 5 moves correctly, but #6 is wrong. Now, since we're 5 states ahead, we only find out that #6 is wrong when the client is on #11. So the client replays from 6 to 11, then 12 is wrong. Etc.

If the client replays all 5 moves correctly, what is #6? Is it the next command that will occur in the near future?

You have to be very precise in what you mean by command #6 or #5, etc.

If you already are, then try to put some output for each client/server message.

Print the command #, the player position before, position after, and the command itself (i.e. move left?).

Do that for both the server and the client. Play your game until you see this behaviour occur, then quit immediately and go through the latest output line by line.

Most likely the sequence numbers are off by one somewhere. It could be the difference between "++counter; doDomething(counter);" and "doSomething(counter); ++counter;".

Anyway, hope that's somewhat useful in case you haven't already done all that. But don't worry, you'll resolve this sooner or later. It probably took me about that long to get this system to be completely stable and bug-free.

Share this post

Share on other sites
Thanks for the pointers shurcool.

That it was an off-by-one error was my first assumption, but I'm pretty sure now that it's not the problem in this case.
I've debugged this thing to hell and back. I've printed out harddrives full of debug information, making sure everything is in sync.

One thing that is particularly annoying about this bug is that it doesn't happen all the time. It only occasionally gets stuck in the loop. If it was an OBO error, it should happen more consistently.

Due to the decoupling of the rate of my physics updates from the rate I'm sending updates over the network, the order in which I do updates is a little more complicated, but I still can't find any error in the logic there.

Share this post

Share on other sites
Ok, but can you explain what exactly you meant by #6 being wrong when the client is 5 moves ahead and replays all 5 of them correctly?
Quote:
 Original post by Kal_TorakOne thing that is particularly annoying about this bug is that it doesn't happen all the time. It only occasionally gets stuck in the loop. If it was an OBO error, it should happen more consistently.

Are you using threads? Could this be a thread synchronization problem?

Share this post

Share on other sites
I noticed you're simulating 300ms of latency; does the problem appear/disappear or vary in degree with amount of lag?
Does the lidgren net log display anything special when the problem appears? (resent packets etc? turn on 'verbose'...)
Btw, the base library class Stopwatch uses QueryPerformanceCounter internally so you can swap out HiResTimer.cs if you want less code in the project.

Share this post

Share on other sites
Quote:
 Original post by shurcoolOk, but can you explain what exactly you meant by #6 being wrong when the client is 5 moves ahead and replays all 5 of them correctly?

I'll try to explain how I'm doing the updates here.

(A side note first, I should have said in my first post. To reproduce the error, first hold down either left or right,(A or D) and press escape to force a message loss. It will lose one message only. To reset it, press spacebar. I usually have to keep losing messages for 15-20 seconds before it shows up.
So hold down A/D and press escape then spacebar repeatedly.)

The network update occurs at different intervals than the physics updating.
This is because I don't want to send updates at as high a frequency as I'm updating local physics. I don't want to cap the local physics updating to the network update frequency either, because that would slow it down more than I'd like.

As a result, I get a variable number of physics updates per network update.

while (AppStillIdle)            {                double tempElapsed = HiResTimer.GetElapsedTime();                _accumulator += tempElapsed;                _updateAccumulator += tempElapsed;                while (_updateAccumulator >= UPDATE_INTERVAL)                {                                       _updateAccumulator -= UPDATE_INTERVAL;                    MoveState ms = = UpdateGame(); //The input is acquired in the UpdateGame method                    //The Replaying is also done in the UpdateGame() function                    ms.Timesteps = _timeSteps;                    ms.Velocity = _playerManager.LocalPlayer.Paddle.Body.LinearVelocity;                    ms.Position = _playerManager.LocalPlayer.Position;                    //Build the state and hand it to the network manager                    _networkManager.OutGoingState = ms;                    _moveHistory[CurrentHistoryIndex++] = ms; //store in circular history buffer                }		//This sends the update directly after it's been built, and before any more physics updates can occur.                _networkManager.HeartBeat();				                while (_accumulator >= DELTA_TIME)                {                    _playerManager.LocalPlayer.LastPosition = _playerManager.LocalPlayer.Position;                    _playerManager.UpdatePhysics(DELTA_TIME);                    _accumulator -= DELTA_TIME;                    _timeSteps++;                }			Render()            }

This has the effect of introducing a constant lag equal to UPDATE_INTERVAL, because the input I just sent out has not taken effect. No physics updates have been calculated based on it yet. So we have to wait until the next round, at which time the number of updates we performed on it will be sent to the server, and then, a whole message later, the server can calculate the results of the last input and send it back.

This has some implications in the Replay correction code, which I'll point out below.
This is mostly pseudo-code for clarity.

 private void Correct(MoveState correctState)        {            Player.State = correctState;            //Apply any input for this state.            //Remember, this input here has not been calculated yet            Player.ApplyInput(correctState.Input);                       //Set the timesteps back to the last correct state            int timeSteps = correctState.Timesteps;            //We have to replay all the way up to CurrentHistoryIndex            while (i != CurrentHistoryIndex)            {                //Move to the next state                i++;                //Run the required number of physics updates for this state.                //These updates are acting on the input from the LAST state.                while (timeSteps < moveHistory[i].Timesteps)                {                    _playerManager.UpdatePhysics(DELTA_TIME);                    timeSteps++;                }                //Apply any input for this state.                Player.ApplyInput(correctState.Input);                                //Correct the position stored in the Move History               moveHistory[i.Position = Player.Position;            }            //Run the remainder updates, this will bring us up to the current state.            //Note that this is after all the moves in history have been corrected.            //These are the updates that have happened on the last state, but have not been sent out yet.            //            while (timeSteps < _timeSteps)            {                _playerManager.UpdatePhysics(DELTA_TIME);                timeSteps++;            }         /*This seems to be where the error is introduced.  The moves in history are always corrected perfectly, but the the update that will be sent out next, using these remainder updates that we just calculated, that update will be wrong.*/        }

Here is some debug output that I've prettied up to make it more understandable.
This is the error loop in action. Couple things to note, even though the error state is not always 1 digit ahead of the replay end state, it's still the next consecutive state. This is because there are sometimes 1, sometimes 2, physics updates per network update.

Erroring on this state: 1130
*replaying*
Replay end state: 1136
*normal network updating*

Erroring on this state: 1137
*replaying*
Replay end state: 1143
*normal network updating*

Erroring on this state: 1145
*replaying*
Replay end state: 1150
*normal network updating*

Erroring on this state: 1152
*replaying*
Replay end state: 1157
*normal network updating*

Erroring on this state: 1159
*replaying*
Replay end state: 1165
*normal network updating*

Erroring on this state: 1166
*replaying*
Replay end state: 1172
*normal network updating*

Erroring on this state: 1174
*replaying*
Replay end state: 1179
*normal network updating*

So, taking the first two outputs from the above sequence,

Erroring on this state: 1130
*replaying*
Replay end state: 1136 //This is where the correction ended. It is the next one, (the one for which we ran the remainder updates) that is incorrect.
*normal network updating*

Erroring on this state: 1137 //This is the next one sent out after the replay, one with the corrected physics state.
*replaying*
Replay end state: 1143
*normal network updating*

I hope this clarifies more than it muddifies. It's difficult to explain.
Thanks for the replies.

P.S. shurcool, I'm pretty sure it's nothing to do with threading; I haven't explicitly used multiple threads at all.

Also, @fenghus, no, the latency doesn't change it. I've tested with more and less than 300ms, with the same results.

Share this post

Share on other sites
Could be many things. First you should try sending network ticks EVERY frame to simplify the problem and use a fixed timestep (which I think you do). One input update -> one character update -> one packet sent.

But from where I stand, it looks like a timestep problem (or a mismatch of the number of updates performed for a given move sequence number).

Share this post

Share on other sites
Quote:
 Original post by oliiiCould be many things. First you should try sending network ticks EVERY frame to simplify the problem and use a fixed timestep (which I think you do). One input update -> one character update -> one packet sent. But from where I stand, it looks like a timestep problem (or a mismatch of the number of updates performed for a given move sequence number).

I'll probably restructure it that way for debug purposes, but as a final solution.. It's not a viable option to send messages 60 times per second.
The server is going to have possibly hundreds of concurrent clients.

Can someone give me a quick outline of how you could lock the local update rate (incl. input) to the required low network rate, and still the client update smoothly?
I can't see a nice solution right off the top of my head.

Share this post

Share on other sites
Do you mean your client sends 60 commands per second? That seems pretty high. In my game, my command rate is 20, and the update rate is independent (but at the moment is also set to 20). By update rate, I mean how often the server sends out authoritative state updates to the clients. The server->client updates don't have to be in sequence, as it's ok to skip some and always send only the latest.

As for the client commands, if you want to unlock the network sending from the physics tick rate, which I don't recommend doing, but it's possible by simply queueing up all the input commands since the last state that was authenticated by the server, and sending them all in one packet whatever times per second. By doing this, you're introducing extra artifical latency, so it's better just to send input commands to the server whenever you execute a physics update (i.e. as soon as one is avaliable).

Share this post

Share on other sites
Quote:
 Original post by shurcoolDo you mean your client sends 60 commands per second? That seems pretty high. In my game, my command rate is 20, and the update rate is independent (but at the moment is also set to 20). By update rate, I mean how often the server sends out authoritative state updates to the clients. The server->client updates don't have to be in sequence, as it's ok to skip some and always send only the latest.As for the client commands, if you want to unlock the network sending from the physics tick rate, which I don't recommend doing, but it's possible by simply queueing up all the input commands since the last state that was authenticated by the server, and sending them all in one packet whatever times per second. By doing this, you're introducing extra artifical latency, so it's better just to send input commands to the server whenever you execute a physics update (i.e. as soon as one is avaliable).

My client updates physics at about 60 times per second. I send network updates 30 times per second.
The server replies to messages whenever they arrive. It doesn't send out updates at set intervals.

I've basically got two different timesteps going at the same time.

const float DELTA_TIME = 00.0166f;const float UPDATE_INTERVAL = .030F;while (AppStillIdle)            {                while (_updateAccumulator >= UPDATE_INTERVAL)                {                  _updateAccumulator -= UPDATE_INTERVAL;                  ProcessInput();                  SendNetworkMessage();                }		                while (_accumulator >= DELTA_TIME)                {                   _accumulator -= DELTA_TIME;                   UpdatePhysics();                }			Render()            }

The Input is locked with the Network rate while still letting the physics update faster.

Quote:
 Original post by shurcoolAs for the client commands, if you want to unlock the network sending from the physics tick rate, which I don't recommend doing, but it's possible by simply queueing up all the input commands since the last state that was authenticated by the server

I'm already decoupling the network sending from the physics update, but in a different way. I just slow the input down to match the network, instead of speeding it up by queueing until the next network send.

Share this post

Share on other sites
I guess basically what I'm wondering is, if you're only updating physics 20 times per second, how do you keep it smooth?

My game is in the twitch game catagory, requiring precision and low latency.

Share this post

Share on other sites
Typically, you'll do extrapolation/interpolation for the remote entities, and you actually let the user turn faster than the physics rate. Note that players are much more sensitive to mouse lag than to button lag. Also, you can play gunshot animations/sounds as soon as possible, even if you wait 50 milliseconds to actually simulate the effect.

Share this post

Share on other sites
Quote:
 Original post by hplus0603and you actually let the user turn faster than the physics rate.

I'm not understanding this part... The player can't get ahead of their own physics can they?

Share this post

Share on other sites
I believe he means the direction the player is facing is updated every render frame, but the direction the player is moving is only updated every physics tick. For example; play quake (or most fps) and pull the network cable... you can't move or fire, but you're likely to be able to look around still.

Share this post

Share on other sites
Ah, I wasn't thinking in terms of FPS games.

Share this post

Share on other sites
I had run into similar problems a few months back when I was developing a small FPS demo.

The following are those I acquired from local perception filter, local player prediciton, quake3 source code to solve the problems:

1) network latency

frame-locked authoritative update from server to client

server: 1  2  3  4  5        \  \  \  \  \ \n         \  \  \  \  \ \nclient:   1  2  3  4  5

------------------------------------------

frame-locked input from client to server

server: 1  2  3  4  5           /  /  /  /          /  /  /  /client:  2  3  4  5

2) playback without prediction

first, you need to buffer the authoritative update sent by server

then you need some sort of methods to determine at what time you should be playing back the authoritative sequence received from server

my method is to estimate what time a given frame is at. whenever you received update from server, timestamp the update (don't use float, double or uint64_t is fine), to estimate, e.g. at what time will frame 6 update arrive:

frame6_time = ((frame1_time + inv_fps * 5) + (frame2_time + inv_fps * 4) + (frame3_time + inv_fps * 3) + (frame4_time + inv_fps * 2) + (frame5_time + inv_fps * 1)) / 5;

so, based on this association (6, frame6_time), whenever given a game time, you can calculate what pair of frames you are going to interpolate and the interpolation amount. if you want the playback be less susceptible by network instability, you may add some amount to the estimated time as such (6, frame6_time + some_amount), the amount may be based on network jitter and a small value, e.g. half a frame interval, however, what you will be seeing is more out-date.

3) input buffering and prediction
input buffering is needed for both client and server

in server, in order to process the input sent from client correctly in sequence, every input from client is frame stamped, and server will never process out-date input, if there is no input of the current processing frame, it will use the last frame's before the current processing frame's. so in order to always have input current to the processing frame, client must send input ahead, the estimation is similar to 2)'s, but minus ping (round-trip delay), jitter and a small value

in client, the purposes of input buffering are for prediction and network traffic optimization. to always predict 'correctly', whenever you receive authoritative update from server about local player, you predict from the server update frame plus 1's input in the input history to the last input frame you sent or you buffered. this is different from your detecting errors with some threshold then correcting, this approach is to always correct the prediction, i learnt it from quake3 source code, you may look into it, the comment said its not so bad, however, for larger network latency, the cpu load for always correct errors approach will be increased (if you are only predicting your local player, it should be fine).

4) conclusion
you need to work out the timing very accurately, don't try and error in the first place, always consolidate your work on paper or your mind and try dry run

in my FPS demo, for more instant interaction reason, server sends authoritative update 30 times a second, each update includes one frame's position, animation, ... etc, client buffers up to 2 input frames or some other critieria before flushes them out, will only buffer input different from the previous frame. physics update occurs 60 times a second. for demo scale reason, client and server are both singlethreaded design, but network is tcp multithreaded iocp with nagle algorithm disabled, v-sync is turned off for steady time estimation, rendering occurs when e.g. current frame should be 61, and last rendered frame is 60. prediction is in total 4 frames ahead at minimum, accounts for minimum 66.7ms latency

hope this helps

[Edited by - hellknows2008 on April 25, 2008 3:49:41 AM]

Share this post

Share on other sites
something occurred to me (eureka!).

I think it's a latency / rate problem.

In the elapsed time you send an incorrect packet and receive the correction from the server, you will have sent potentially several more incorrect packets, and the server will have to send corrections for them. And if you keep doing that, the errors will never catch up until you send a valid position (i.e. you stop moving).

However, if I get this right, the number of corrections the server should send would basically fit into the latency window. After you receive the first correction, that should re-sync your character to the server (if you are not bumpipng into weird things again) after say, 200ms if you have 200 ms latency.

A couple of things. Play with the tolerance (the thresholds used to detect an out-of-sync packet received from the client), and also, it should be possible to throttle sending corrections to the host (i.e. have a timer so you send corrections only every .1 seconds for example), that could stop the flooding of correction packets.

Thirdly, when using UDP transmissions, inputs and predictions (that happen every 60th second for example) are bundled together on the client and sent at the network rate (say, 20th of a second). They are sent in batch if you will, to avoid multiplying packet. They are also sent duplicated to cover for packet loss.

But using a reliable stream, I dont think that is necessary at all, and you can just write your data straight into the stream.

Another thing you can try for debugging, is to intentionally invalidate your prediction (say, if you press space, add 1 meter in the y position in your client-side calcualtions), and see what happens when the server corrects it. It should ping back in sync pretty quickly. Else, there is a pretty major bug in your code.

What we used to do is to apply one input per frame -> one physics update -> bundle inputs not sent yet -> send bundle at network rate.

We had some problems with making the predictions work EXACTLY the same way on the client and server. Many things came into play and both machines would calculate a slightly different outcome for what we thought was a similar set of inputs.

In the end, the inputs have to be exactly the same, and that means, identifying all your inputs that will affect your character physics.

Share this post

Share on other sites
and yeah, as Hellknows says, the right timing is critical. You need to timestamp your inputs accurately, and if you do not use constant frame rates, send the delta time, and you cannot compress the data you send (at least, no quantisation, only lossless compression).

Share this post

Share on other sites
Quote:
 Original post by oliiisomething occurred to me (eureka!).I think it's a latency / rate problem.In the elapsed time you send an incorrect packet and receive the correction from the server, you will have sent potentially several more incorrect packets, and the server will have to send corrections for them. And if you keep doing that, the errors will never catch up until you send a valid position (i.e. you stop moving).

Exactly. That is the problem that client side rewind & replay fixes.
As soon as a server packet comes in that doesn't match the client position, it rewinds back to the timestamp on the correct packet and replays all the moves it was predicted ahead. Thus, the very next packet that comes in will match the new corrected state, and the dreaded correction loop is entirely avoided.

The bug I have is sort of an edge case to this. It consistently replays all the predicted moves correctly. It's just the very next move, (that hasn't been sent yet) part of which was actually in the prediction/correction cycle, that is incorrect, and starts a new round of replaying.

Quote:
 However, if I get this right, the number of corrections the server should send would basically fit into the latency window. After you receive the first correction, that should re-sync your character to the server (if you are not bumpipng into weird things again) after say, 200ms if you have 200 ms latency.A couple of things. Play with the tolerance (the thresholds used to detect an out-of-sync packet received from the client), and also, it should be possible to throttle sending corrections to the host (i.e. have a timer so you send corrections only every .1 seconds for example), that could stop the flooding of correction packets.

See, the thing is, my server has no concept of the client being out of sync, or having a bad position. It merely takes a set of input, advances the client position, and sends back the results. So it doesn't send any additional corrections when the client messed up. If the client is out of sync, it's always the client's fault, and it's up to the client to fix it.

Quote:
 Thirdly, when using UDP transmissions, inputs and predictions (that happen every 60th second for example) are bundled together on the client and sent at the network rate (say, 20th of a second). They are sent in batch if you will, to avoid multiplying packet. They are also sent duplicated to cover for packet loss.But using a reliable stream, I dont think that is necessary at all, and you can just write your data straight into the stream.

My solution has pretty much the same effect.
Instead of queuing up all the input that happens between network sends, I just poll the input at the same rate as the network so no input piles up on me.

Quote:
 Another thing you can try for debugging, is to intentionally invalidate your prediction (say, if you press space, add 1 meter in the y position in your client-side calcualtions), and see what happens when the server corrects it. It should ping back in sync pretty quickly. Else, there is a pretty major bug in your code.

Forcing a change of of the client's position would basically have the same effect as losing a packet. It wouldn't show up for #frames predicted ahead, and it would be effectually the same as having incorrectly predicted ahead, and it would be corrected as such.

Quote:
 What we used to do is to apply one input per frame -> one physics update -> bundle inputs not sent yet -> send bundle at network rate.We had some problems with making the predictions work EXACTLY the same way on the client and server. Many things came into play and both machines would calculate a slightly different outcome for what we thought was a similar set of inputs. In the end, the inputs have to be exactly the same, and that means, identifying all your inputs that will affect your character physics.

Right now, I'm not doing any prediction on the server. The server just deals with the input as it comes in.
I may need to do some type of prediction down the road, to deal with giving all players a fair playing field regardless of latency. I'm not sure yet, haven't gotten that far. :D

Share this post

Share on other sites
Well.

I restructured it to do one input, physics update and network update per tick.
Still have the same bug.
I did a couple other variations on the same theme, but apparently that's just not where the bug is.

I'm all out of ideas.

Share this post

Share on other sites
When out of ideas, add logging!

On the client, for each timestep, log the user input, the simulation state, the step number, and what you're sending to the server.
For each received packet from the server, log the intended timestep and the server state.

On the server, log each time step, the state for each player for that timestep, what state you're sending to each player, and what input (for which timestep) you receive from the client.

Now, go through the logs, and compare the client and server states and inputs for each timestep. Processing your log into an Excel file might help with this, as will formatting the log to make it easy to machine parse. After doing this, you ought to figure out what the pre-/postconditions are for the bug firing, which in turn will let you insert conditional breakpoints, or asserts, to start debugging the problem.

Oh, and it's often useful to use a virtual clock, which always advances X milliseconds per loop of the game. That will let you stop and debug without losing the next time step, which you would if you used a real clock. Similarly, on the server, advance the clock to the earliest clock received from a client, but no more, which will let you halt the server when halting a client. And, finally, for debugging a multi-client situation, you should not let the clients advance more than what the server tells them they can, so that breaking one client in the debugger will effectively stop all the clients and the server, until that client comes back.

Turn all that off for release, though, or crashing players (or cheaters) will cause problems.

Good luck!

Share this post

Share on other sites
Quote:
 Original post by hplus0603When out of ideas, add logging!On the client, for each timestep, log the user input, the simulation state, the step number, and what you're sending to the server.For each received packet from the server, log the intended timestep and the server state.On the server, log each time step, the state for each player for that timestep, what state you're sending to each player, and what input (for which timestep) you receive from the client.Now, go through the logs, and compare the client and server states and inputs for each timestep. Processing your log into an Excel file might help with this, as will formatting the log to make it easy to machine parse. After doing this, you ought to figure out what the pre-/postconditions are for the bug firing, which in turn will let you insert conditional breakpoints, or asserts, to start debugging the problem.

Belive me, I've done all that. Repeatedly. In depth. Ad infinitum.
Everything matches up perfectly... Except that sometimes, even though everything syncs up, the numbers are wrong.

Share this post

Share on other sites
Hmm, here's another thing you can try.

What does your movement code look like so far? Does it have things like velocity?

If so, try to simplify it and make it just so that the player is moved by a fixed amount whenever he has a directional key down. Otherwise, he stays in place.

Something like...
// Input command processingif (command.getKey() == UP_ARROW) player.MoveForward(5);else if (command.getKey() == DOWN_ARROW) player.MoveForward(-5);else if (command.getKey() == LEFT_ARROW) player.MoveSide(5);else if (command.getKey() == RIGHT_ARROW) player.MoveSide(-5);else { /* Do nothing, player position is unchanged */ }
So that the arrows keys control your player's velocity directly, rather than indirectly through acceleration.

This way, assuming no input commands are dropped from the client, the player local prediction and server authoritative updates should match without a problem.

If there is one input command packet dropped, it should take only one update from the server to 'fix' it on the client (i.e. snap his position to the right place) and you should not be seeing your 'feedback loop of corrections' happening (unless the bug is still elsewhere).

If your controls actually control the player's acceleration only, then when the server sends out an update containing a corrected position but not velocity, there will be more than 1 corrections occuring until you let go of the key (since the velocity on the client will be greater than on the server).

I'm not sure how this applies to your problem, but maybe it'll help. Unfortunately, I wasn't able to try out your code because I don't have C# installed at home atm.

Share this post

Share on other sites
If you have full data logging, and you have full input logging, then you should have a reproducible case, where running the same program with the same input (from the log) each time will generate the same bug. You should be able to then single-step the program at the right time step, to figure out what is going wrong.

If you can't make input logging/playback work consistently, then that should tell you something about the bug, too :-)

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

• Forum Statistics

• Total Topics
628278
• Total Posts
2981789

• 10
• 11
• 17
• 14
• 9