Nondeterminism in Bullet?

You can’t use bullet app state. You have to create your own physics space and use it directly so that you can update on the same thread with a fixed time step.

1 Like

Cristian:

Thanks for narrowing this down. I’ll take a look at the code you posted and report back.

–Stephen

2 Likes

I have been working for quite some time on a physics intense game (with planned features such as replays, demo mode, etc.) that depends on the simulation to be deterministic. And I’m using the awesome Minie library too, so here is my approach:

  • Do not use the Bullet App State (in fact I roll my own simple state system), because it does some weird stuff in the background - I need 100% control
  • Have a Physics Space instance and update it with a constant time step,
    e.g. space.update( 0.016f ) vs. space.update( tpf )
  • When applying forces, impulses, torques, etc. make sure to not have them depend on some variable like a fluctuating tpf float
  • Also do not apply forces in some input event handler, accumulate and apply right before update to get the timing correct
  • When you try to restore physics state make sure to reset everything on the rigid bodies correctly, e.g. position, rotation, linear / angular velocities, sleeping timers, etc.
  • Another approach to restore physics state would be to completly reconstruct the space and all rigid bodies, but that is of course less efficient

This is mostly in line with what others have said already, but I can say that this approach has worked for me so far.

4 Likes

Thanks people,

As you mention I will change the bulletappstate and let you guys know.

Best regards

Me again,

This time I got rid of bulletappstate, put a simple physicspace with fixed time and I got the same values every time :slight_smile:
So again an as usual @pspeed you are right
@Pauli yes, it seems that the bullet app state does weird stuff.

2 Likes

I still need to dig into to understand where the non-determinism is coming from. Seems to me it should be possible to make BulletAppState behave deterministically. In the worst case, it could single-step the physics repeatedly.

1 Like

I’m not sure if that is the question, but in case of com.jme3.bullet.BulletAppState it’s coming from this I think:

The tpf variable is being set here:

Since this is the same tpf that is fed to all app states and simpleUpdate this value may fluctuate depending on how fast your machine can update / render the application, which is of course non-deterministic. Thus the physics space update may also get different tpf values each frame which in combination with floating point accuracy problems may lead to different outcomes.

I am assuming the sequential threading type for the BulletAppState here, because parallel threading would also result in non-deterministic updates depending on how fast your hardware handles things.

I think a viable solution would be a fixed time step (as implemented in most of my realtime projects). Then the physics space would always be updated by a constant delta time value.

Hope this helps.

1 Like

Yes, that’s right. Bullet app state gives no way of using fixed time steps… so can never be deterministic.

Furthermore, OP’s original code was also using parallel mode which means even if the physics space was being stepped in fixed time, the adjustments to the physics space could land on different frames because they come from a thread that might be running a bit differently. Even though the two are stepped together, I wouldn’t count on it.

I also just noticed that bullet app state does its update in AppState.render(). I’m going to have to let that sink in for a while with respect to some folks’ camera sync issues.

The argument to PhysicsSpace.update(float) isn’t the time step; it’s the total amount of time to simulate.

The time step is supposed to be determined by PhysicsSpace.getAccuracy(). update(float) is supposed to split the time up into time steps of equal duration, taking maxSteps into account. Apparently it’s not doing that correctly. Most of that logic is in C++, not Java.

As soon as I tear myself away from learning CSS/Asciidoc/Node.js/Antora, I’ll run some tests and re-read the C++ code.

As I recall, it will step the appropriate number of times and then do a partial step of the remaining time. (It’s this potentially teeny tiny tiny teeny partial step that screws up forces and stuff in the bugs I saw the made me switch to fixed time steps for native bullet… jbullet worked fine.)

Bullet provides a way to do fixed time steps. JME doesn’t support it in BulletAppState.

One of these days, I will get around to producing two tests to show how native bullet craps the bed and jbullet doesn’t with respect to variable timesteps that are very close to ‘accuracy’ or whatever.

1 Like

With maxSubSteps > 0 (the default case for BulletAppState), btDiscreteDynamicsWorld maintains a m_localTime field that tracks leftover time from invocations of stepSimulation. The leftover is then added to the next invocation.

If there’s a bug, I’m not seeing it:

Whatever the reason, native bullet is broken when the actual time step is very close to the ‘accuracy’ or whatever. I just don’t have time to put the test together to prove it other than the SiO2 bullet character demo already built. If it’s modified to run against native bullet then the force is barely enough to move the character and eventually things go to NaN and the program crashes.

It doesn’t happen if running jbullet. It doesn’t happen if using fixed time steps with native bullet. I tried doing force application in the physics tick listener, between steps, wherever… nothing worked that I tried. So either it’s strangely difficult to get working right or just broken.

I even tried debugging the C++ code. I could see the values go bonkers but never stepped in deep enough to see what caused it.

I cloned your SiO2 project, but I’m having difficulty building it. I see there are build scripts for both Ant and Gradle. Which build system do you recommend?

Gradle.

Not sure what state its in related to other simsilica dependencies at the moment.

Edit: and if I seem a little less responsive lately it’s because we’re in the last sprint for our first major milestone on a five year project… so I’m heads down working most of the day.

2 Likes

After hacking several build scripts to cope with configurations.compile.transitive = false and various other nuisances, I got the sio2-bullet-char-demo running with jme3-bullet-3.3.2-stable.

I let it run for a few minutes and nothing blew up. What should I do if I want to reproduce your issue?

Were you able to run around and jump and stuff?

Last time I ran it, pressing ‘w’ only moved the character very very slow. Intersecting with moving platforms and such would often cause NaN style crashes.

1 Like

I’m able to move around using W/A/S/D, though it does seem painfully slow. I’ve collided with moving platforms several times and not noticed any ill effects.

Edit: I’ve uncovered a clue regarding the sluggish movement. After I made the following optimization to CharInputDriver, movement became reasonably responsive:

@@ -248,7 +248,7 @@
             force.multLocal(airImpulse);
 //System.out.println("   airForce:" + force + "  vTemp:" + vTemp);                  
         }
-        body.applyCentralForce(force);        
+        body.setLinearVelocity(desiredVelocity);        
  
         Quatd facing = input.getFacing();
         qTemp.set((float)facing.x, (float)facing.y, (float)facing.z, (float)facing.w);

Perhaps the force calculation should account for the mass of the body—and maybe the timestep as well.

by the way, have you set this to true:

by default it uses jbullet.

Except it works fine with jbullet and fine with max substeps 0 and a fixed time step.

For example, if you change BulletSystem’s:
pSpace.update(t);
to:
pSpace.update(1/60f, 0);
(from memory)
…then it works fine.

From experimenting there was a t that could be passed that made it fail every time but I have like 5 different values commented out in my local version. I’d have to re-experiment to figure out whether it was 0.99/60 or not. But it was something like that. Or maybe when it was exactly 1/60 it was bad and almost 1/60 was ok.

…I left too much of a mess in my own code to remember without trying it. If this conversation is still happening in a couple weeks, I’ll try to run this code again.

Oh wow, I didn’t expect there to be such logic behind space.update( tpf )!

So what would be the correct way to do a single fixed time step for a physics space using stock Minie?
Should I use stepSimulation( ... ) directly?

I mean I was de facto getting deterministic fixed-time-step-like behavior by using space.update( 0.016f ) but didn’t expect there to be such things running in the background…

1 Like