Q:

Converting Frame-Dependent Movement to Frame-Independent

I fine-tuned some movement physics for a 2D platformer in a frame-dependent manner and now I want to convert them to frame-independent.
I though this would be as easy as multiplying a few things by 60 (my FPS) and a few other things by a time delta.
Unfortunately, the resultant physics is not the same as the original, especially when I drop the frame rate from 60 fps to 30 fps.
Here's how the frame-dependent movement works in my Player class, simplified a bit for the purposes of illustration.
PLAYER_ACC = 1.5
PLAYER_FRICTION = -0.23
PLAYER_GRAV = 1.75
PLAYER_JUMP = 20

def get_input(self):
    if self.game.input("left"):                
        self.acc.x = -PLAYER_ACC                    
    if self.game.input("right"):                
        self.acc.x = PLAYER_ACC 
    if self.game.input("up"):
        self.vel.y = -PLAYER_JUMP

def update(self):
    # reset acceleration
    self.acc = vec(0, PLAYER_GRAV)   

    # process input
    self.get_input()

    # apply friction
    self.acc.x += self.vel.x * PLAYER_FRICTION     # The faster you're going, the more friction slows you down.

    # calculate velocity
    self.vel += self.acc          

    # if the x-velocity approaches 0, set it equal to 0    
    if abs(self.vel.x) < 1.5: 
        self.vel.x = 0                                   

    # downward velocity cap
    if self.vel.y >= 14:
        self.vel.y = 14   

    # adjust position
    self.pos += self.vel + (PLAYER_GRAV * self.acc)    # Got this equation from a tutorial and I like how it feels.

Here's my attempt at making it frame-independent. My time delta is self.game.dt.
PLAYER_ACC = 1.5 * 60
PLAYER_FRICTION = -0.23
PLAYER_GRAV = 1.75 * 60
PLAYER_JUMP = 20 * 60

def get_input(self):
    if self.game.input("left"):                
        self.acc.x = -PLAYER_ACC                    
    if self.game.input("right"):                
        self.acc.x = PLAYER_ACC 
    if self.game.input("up"):
        self.vel.y = -PLAYER_JUMP

def update(self):
    # reset acceleration
    self.acc = vec(0, PLAYER_GRAV)   

    # process input
    self.get_input()

    # apply friction
    self.acc.x += self.vel.x * PLAYER_FRICTION    # The faster you're going, the more friction slows you down.

    # calculate velocity
    self.vel += self.acc * self.game.dt          

    # if the x-velocity approaches 0, set it equal to 0    
    if abs(self.vel.x) < 1.5 * 60: 
        self.vel.x = 0                                   

    # downward velocity cap
    if self.vel.y >= 14 * 60:
        self.vel.y = 14 * 60 

    # adjust position
    self.pos += (self.vel + (PLAYER_GRAV * self.acc)) * self.game.dt

Unfortunately, the resultant physics did not feel the same as my original. Especially when I drop the frame rate from 60 fps to 30 fps.
I was thinking that maybe when I apply the friction to the acceleration, I need to raise it to the time delta, but this didn't help either. I am working at a small resolution (640x360) and I'm not sure if this is impacting anything.
My question is: How can I convert my current physics to frame-independent?
There are a few questions similar to this on Game Dev SE already, but none of them has led me to a solution.

A:

There are two main ways of doing frame independent update loops. I highly recommend reading through and understand this: Fix your timestep!
What you are trying to do is a variable timestep update loop, which can work, but it will be non deterministic, as in it might look like that it is frame-rate independent but error will be introduced depending on the frame rate frequency (less FPS, more error). It is best used in systems that produce data that are self-contained and no other systems rely on them. Particle systems or cloth simulation can be such, because they usually have no physical impact on the game world and are purely visual.
For gameplay and physics simulations what usually is recommended practice is the fixed timestep update loop. The basic idea is that you have a predetermined (fixed) amount of simulation resolution for a unit time period. Your fixed timestep resolution is one simulation step per 1/60 seconds, so 60 simulation steps per second. If your overall performance drops to 30 FPS that means that you have to update twice the amount with the same fixed timestep resolution, so 120 simulation steps per second. Basically:
static float accumulator = 0.0f; // this is a static or global variable
accumulator += game.elapsedTimeSincePreviousFrameInMilliseconds;

while(accumulator >= 1.0f/60.0f)
{
  UpdateGame();
  accumulator -= 1.0f/60.0f;
}

The above code demonstrates a simple case and can be refined further to accumulate for leftover time from the previous update, but that is more involving than to post here. The above mentioned link has all you need, basically.

