PhysicsBody.applyForce() is not independent of speed, timeStep and FPS

I've setup a scene with SceneKit where a ball is rolling on a plan. I've simulated the grass friction with applyForce() in order to make it stop after a while (dampen & friction parameters do not reach that effect).

Everything works well till I play with runtime parameters, like scnScene.physicsWorld.timeStep, sceneView.preferredFramesPerSecond and scnScene.physicsWorld.speed.

I did a comparison with default gravity. Let's take a plan, with a light slope and launch a ball on it:

let planSlope = (Float.pi/180.0)*3.0
planNode.rotation = SCNVector4(x:0, y:0, z:1, w: planSlope)
...
let impulse =  Float(ballMass) * speed    // N.s = m*v
let forceVector = simd_float3(1.0, 0.0, 0.0)
let force = SCNVector3(forceVector * impulse)
ballNode.physicsBody?.applyForce(force, asImpulse: true)

The ball will climb the slope up till a certain distance and come back after. If I change timeStep, FPS or speed, the ball is still reaching the same point. That's great.

Now, I disable gravity and create a custom force to simulate it using applyForce():

scnScene.physicsWorld.gravity = SCNVector3(0.0, 0.0, 0.0)
func physicsWorld(_ physicsWorld: SCNPhysicsWorld, didUpdate physicsContact: SCNPhysicsContact) {
     ...
     let gravity = simd_float3(0.0, -9.8, 0.0)
     let mass = (ballNode.physicsBody?.mass)!
     let force = SCNVector3(gravity * Float(mass))
     ballNode.physicsBody?.applyForce(force, asImpulse: false)

This gives exactly the same good result with default constants:

scnScene.physicsWorld.speed = 1.0
scnScene.physicsWorld.timeStep = 1.0/60.0
sceneView.preferredFramesPerSecond = 60

But as soon as I change one of them, the distance changes:

scnScene.physicsWorld.speed = 2.0
scnScene.physicsWorld.timeStep = 1.0/120.0
sceneView.preferredFramesPerSecond = 30

So, I needed to take them into account on the force:

func physicsWorld(_ physicsWorld: SCNPhysicsWorld, didUpdate physicsContact: SCNPhysicsContact) {
     ...
     let gravity = simd_float3(0.0, -9.8, 0.0)

     // Compensate timeStep & FPS & Speed !?
     gravity *= Float(scnScene.physicsWorld.timeStep)/(1.0/60.0)
     gravity *= (1.0/60.0)/(1.0/Float(sceneView.preferredFramesPerSecond))
     gravity *= 1.0/Float(scnScene.physicsWorld.speed)

     let mass = (ballNode.physicsBody?.mass)!
     let force = SCNVector3(gravity * Float(mass))
     ballNode.physicsBody?.applyForce(force, asImpulse: false)

That's weird, I guess I missed something?

The risk is that if FPS changes dynamically due to GPU overload, the result will differ.

PhysicsBody.applyForce() is not independent of speed, timeStep and FPS
 
 
Q