So I've been working on syncing the physics between the client and server implementations. It's been... a bit painful.
Varying Update Rates Hertz a Lot
One of the first things that tripped me up was the different update rates. The client is running at the regular 60 fps, i.e. updating every 0.016ms. The server is running a much more sedate 10 updates per second, or every .1ms.
I had assumed that passing the delta time into the physics simulation would deal with the difference. I.e. it would know either one sixtieth of a second or one tenth of a second had passed, and simulate that amount of time passing:
physicsSpace.Update(elapsedMilliseconds);
First, this was WRONG, and a very common mistake! Turns out Bepu wants the time in seconds, so really I'm telling it to simulate x1000 speed! This is correct:
physicsSpace.Update(elapsedMilliseconds/1000.0f);
Why didn't I notice this right away? Well, it turns out Bepu has a default timestep for its simulations, which happens to default to 1/60th of a second. And it will only perform a certain amount (default three) physics steps per update. So by default Bepu will simulate .005 seconds of time (3x0.016), maximum, per Update call. Mind you, these are quite sensible numbers for defaults.
So really when I was passing in weird values like 16 (16 milliseconds, 1 frame at 60fps) I was actually asking it for SIXTEEN seconds. In return it gave me 0.05 seconds, which is near enough to .16 that by the naked eye I couldn't tell the difference. On the server it was getting 100 (100 milliseconds, the update period at 10 fps), so it though I was asking for a bit over a minute and a half...
So, problem number one was I was passing milliseconds rather than seconds. The defaults were ok for the client, but clearly the server running at 10hz would still be wrong. Each update would need to simulate 100 milliseconds of time, but by default Bepu would only step 50 milliseconds.
Easily fixed by a bit of reading through the docs and forums, however. You can change the maximum number of timesteps simulated:
physicsSpace.MaximumTimeStepsPerFrame
... though I did not change this. Really you want a one-to-one normal ratio, with the ability to simulate extras if for some reason the simulation falls behind.
More importantly, you can change the expected simulation step temporal duration:
physicsSpace.TimeStepSettings.TimeStepDuration = timeStepDuration
where physicsSpace is the Bepu object, and timeStepDuration is set in the simulation engine initialization call. So the client and server both pass in their expected update tempo, and the physics engine deals with it.
Velocity Mismatches
That fixed some of the differences I was seeing between the client and server, but there was still one bothersome one.
For small accelleration values (angular or linear) despite the fact that I had set the entity's AngularDamping and LinearDamping values to zero (this is a space simulation after all) the AngularMomentum and LinearMomentum values were still damping down to zero over a short time... but only if they were very small.
I was deeply confused and verified again and again that the damping was zero, and kept looking for where I had gone wrong. This was throwing off the client and server sync, ans the client seemed to damp down quite a bit quicker (which seemed like a useful clue, as I knew it ran more, but shorter, simulation steps. So it had to be some sort of per-step, rather than per-time-unit effect).
I finally found the answer.
On the Bepu forums, someone poses basically the same question, and Norbo answers:
That's caused by the deactivation system. It will attempt to stabilize and disable very slow objects. You can prevent it from freezing entirely by setting the object's IsAlwaysActive property to true. However, stabilization will still be applied. You can disable stabilization separately by setting the Space.DeactivationManager.UseStabilization to false.
Stabilization is nice to have on in the general case, though, particularly if you have complicated interactions like large stacks or constraint systems.
You can also tune the Space.DeactivationManager.LowVelocityTimeMinimum and Space.DeactivationManager.VelocityLowerLimit. Stabilization only occurs under the velocity limit, and deactivation will not occur until an object is below the velocity limit for the time minimum.
By default the velocity lower limit is .026f. Thus I added:
physicsSpace.DeactivationManager.VelocityLowerLimit = .001f;
Well, that solved a lot of problems.