blit

Oh no, he programmin’

Hi, I’m Blit! I make sand think about fucked up things like “Pareto Fronts”, “Normal Distributions” and “Interactions”. I think it’s fun to be a funny animal person online.

pfp is from Kaeti V, who you can check out at https://www.proteidaes.com/


I made the legendary mistake of being off by two, and also did a classic “oh shit, shouldn’t have mutated that” while trying to do something easy in the Simulation Mines. Computers are pain. But, I did figure it out eventually and at least the graphs are pretty?

Anyway, lets talk about some graphs of bouncing ball height when I told the computer to simulate 3 bounces.


So, take a good gander at the first image up there. Using the same framework that I'm using to simulate Bird De Flap, I’m simulating a ball. That bounces. It's a very complicated example. The framework breaks the world into two parts:

  • the continuous part, called flows. this is stuff that changes over time. The ball’s height and vertical velocity, in this example
  • the discrete part, called jumps. This is the stuff that changes instantly— a big shift in values within one time step. When the ball hits the “ground” at y_pos = 0, it’s vertical velocity jumps, suddenly becoming positive.

And uh. Didn't I say I only wanted it to bounce 3 times (four jumps according to the graph-- the first jump is the when the ball falls, before any bouncing1)? Why are there five jumps listed? Oh no2.

Let’s check the data that made that chart!

timey posy veljumps

0.63000.0532-6.18030
0.64000.00003.13921
0.64000.00003.13921
0.64000.00003.13921
0.65000.03093.04111

Ah. Um. Why are there… three? identical entries??? for a jump????? There should be two different entries at one time, not three of the same one? And I regret to inform you that the back part of the table looks like:

timey posy veljumps

1.78000.0039-0.76033
1.79000.00000.42924
1.79000.00000.42924
data ends here

Well, going back over the code, I was adding a point to the running solution after every jump but before every flow. When I jump to a new flow, that’s adding the same point twice.

But still. Where does the third extra point come from? That is due to memory unsafety.

sickos mode time


Like many things that just work, Python is great until it is not. The way I add new points to a running result from a simulation is roughly:

if can_flow:
    # flow until we like, hit the floor or something
    data_from_flow = simulator.flow(start=current_state)
    for time, values in data_from_flow:    
        running_solution.append((time, values))
    current_state = current_solution[-1]

Some of these assignments are copying the value around and some are just passing a reference to a value. Can you tell which is which? Spoilers: I couldn’t!

if can_flow:
    # flow until we like, hit the floor or something
    data_from_flow = simulator.flow(start=current_state)
    for time, values in data_from_flow:
        # this is a copy
        running_solution.append((time, values))
    # this is just saying that current_state references the last value of current solution
    # changing current_state will change the last value of current solution
    current_state = current_solution[-1]

So, it’s kinda trivial now that I highlight it, but: appending makes a copy and assignment just shifts a reference. I was mutating the reference later (thinking it was a copy and I had already recorded it), thus giving us this wonderful double-count problem. The first of my three duplicate entries was the original (that I mutated by changing a reference), the second was me adding the mutated point twice and the third was correct.

For the bug at the end of the data, it's the same problem! The flow would stop (hit the ground), and we'd still be eligible for one more jump (bounces = 4 is less than 3). We'd jump, mutating the last reference in the list and then add it again (add a point after a jump issue again).

Computers are great!


Ok, but I can fuckin' see the break in height graph on the "fixed" image two there, ears-for-brains. Clearly the sim still isn't right, balls don't teleport

-- the version of you I run in my head at all times. I'm sorry your mean up here. Everyone is. I'm working on it.

That's fair-- check out the zoomed in image three!-- but this isn't the simulator or framework's problem, it's that my model is not great. You see, the actual code I have for a jump is something like this:

def jump(self, ball_state:State) -> State:
        ball_state.y_pos = 0
        ball_state.y_vel = -restitution_coef * ball_state.y_vel
        return state

I set the position to be 0 on a "bounce", so I don't end up in infinite jumps forever mode. This is poor collision detection! But, good enough for a blog post to illustrate how the simulation framework... works. We're scruffy round here. Perfection is something to only expect of our opponents.


  1. Jumps is zero indexed in practice, I one-indexed them in the graphs, but am now realizing that was wrong.

  2. I'm also convinced that the velocity discontinuities-- the big vertical lines-- are not straight but I can't prove it.


You must log in to comment.