This week, atoms finally play nice with moving bodies thanks to last week's particles.
That means you can fling a moving body into water and the water makes a little wave, and moving bodies & players don't have their movement blocked by falling sand anymore! (Oh, also, atom movement is vaguely plausible again, unlike last week - yay!)
There was a lot of fiddling to get this all somewhat working, so let's get our feet wet.
Debug log console
If I printed out all the code responsible for moving atoms, it'd have about the same page count as the average curriculum-mandated book for a 3rd grader - it's far too big to run it all in my head for more than one atom at a time.
So to find bugs in it, I do what programmers call "print line debugging": whenever anything happens, it writes a message to a log. Then when things go wrong, I read the logs to work out what happened to & figure out what programming mistake I made.
Reading this output is tricky because usually I am focused on specifically why one atom behaved oddly, and updates for it are many screens of log lines apart. Yet I also need the other atoms' logs to understand where they were, deduce the update order of atoms (a topic for another time), etc - so I can't fully turn off their logs.
So I made a little in-game debug console that lets me filter the logs for a play session down to just a particular atom:
It's integrated with last week's time travel feature so that it only shows log entries up to the current point in time, and I can click a frame number on the left to time travel & see what the game looked like at that point in time. While I was at it, I also accidentally made the game better at handling high volumes of log output.1 2
Better atom movement
Using the debug log console, I fixed the "atoms are drifting to the right as if by wind" bug (a stupid rounding error on my part), so atom movement is now sane-ish again:
Atoms still lose too much horizontal velocity when they hit the roof, but it's better!
I also made bouncing for solid materials a bit better; here are some "bricks" bouncing in a very un-brick-like fashion:
Again, the bouncing isn't perfect, but I'm not sure yet whether "perfectly physically accurate" collisions and bouncing will actually matter in the game.
Moving body water splashes, powered by particles
With particles that move sanely (from last week) and atoms that also move sanely, I also finally made moving bodies splash water around:
The flinging is pretty naive because it always flings particles in the direction of "up plus the horizontal velocity of the (particular point on the) body that hit the water". This looks good as long as water is flat but can look a little odd when the water is moving horizontally:
The body also isn't slowed down by the water yet, and also can't be buoyed by the water. The former is easy to fix (apply a force to the body at the point that is displacing the water) but I'm still thinking about how to do the latter without making performance drop off a cliff.
Moving bodies get crushed by atoms even less
So if a water atom gets flung out of the way, we can just do the same with sand atoms that are in the way, right?
Unfortunately not, unless you want to create quicksand: it would make the moving body sink into a sand pile. That's why we create fixed colliders for the sand instead; we want the body to be stopped by the sand.
But when sand is falling, it's terrible to have a body bouncing off a single (or many) grains of falling sand, and even worse to have a single grain of sand crush a body into the ground.
One fix is to say that movable atoms with velocity greater than 0 are excluded from fixed collider generation - but in practice this doesn't work because some of the atom movement code occasionally overrides the velocity (that's how sand moves diagonally sometimes).
And last week I tried a quick semi-fix that said "if an atom moved in its last update, then it can't be a collider". But if you have a whole group of atoms in the air then the ones at the top sometimes are stationary for a bit, as they wait for the atoms below them to move - and having them block moving bodies still feels bad.
Grounding atoms
To fix this, I dreamed up an algorithm which I call "Grounding":
- If an atom can't move into the space directly below it, then it is "Grounded" if and only if the atom below it is also Grounded.
- Sand follows the same rules but also check diagonally down to either side (since they can move diagonally down if they can't move down).
- Atoms that inherently float (such as the grey platforms so far) or are resting on the bottom of the world are always treated as Grounded.
- Water never counts as Grounded (since we want it to be splashed out of the way still).
Then, only Grounded atoms contribute to the collider generation, which means that falling atoms will never create colliders, and atoms that aren't Grounded can just be flung out of the way as particles.
The catch is that an atom that's on top of a moving body needs to also count as being Grounded, otherwise a pile of sand that's resting on a moving body wouldn't generate a collider, which means that a second body hitting that pile of sand would go straight through it.
However, colliders are generated from simplified shapes roughly based on the atoms - so atoms that rest on a moving body can create colliders that intersect with the moving body and cause it to wobble weirdly:
Semi-grounding
I experimented with allowing some space around the colliders, but that caused some odd gaps.
So instead I introduced a Semi-Grounded state: an atom is Semi-Grounded if one of the places it could move to is currently occupied by a moving body.
Atoms that are grounded via Semi-Grounded atoms are themselves (regular) Grounded, so a Semi-Grounded atom basically means "an atom that's resting stably on a moving body".
Having that distinction allows treating them differently:
- Semi-Grounded atoms are excluded from collider generation to give the moving bodies some wiggle room.
- Semi-Grounded atoms can always be moved out of the way of moving bodies. To make their movement less obvious when the overlapping body is only moving slowly, semi-grounded atoms can be just quietly repositioned in the first available space above the body (rather than being flung upwards and outwards).
The last piece of the puzzle is accounting for movement of the body itself: if a body is moving quickly, then we shouldn't treat any atoms as being semi-grounded via that body.3
Results
After all that, body and atom interaction is much more reasonable:
It still glitches out a little in some circumstances (but hey, so does Noita's implementation!), and there's a bit of tuning left to do for atoms that get flung out of the way to make it look a bit more realistic.
Here's a zoomed in demo, with a Grounding debug visualization turned on:
Still to do
There's one big remaining glitch that's really easy to trigger:
What's happening?
- The collider generation algorithm I'm using doesn't support holes in colliders, so as soon as all the sand around the body is grounded, the created collider has no hole in it.
- The body physics engine says "oh no, this body is inside a fixed collider, let's move it the hell out!"
- The atom simulation says "woah this body is moving quickly, let's make sure no atoms are grounded via it!"
- All the atoms that were sitting on top of the body get removed4 from the collider.
- The body physics engine sees a big empty space to move the body into.
- ... which causes all the atoms to be flung out of the way of the moving body.
So in theory, fixing #1 should fix this. In theory.
Oh, and the other big thing is that none of what I talked about today is actually implemented for the "player" yet, so you can't jump into water and have it splash, nor push falling sand out of the way (though at least falling sand doesn't block you moving anymore). So I should fix that too.
Playable web build
Press 4 for sand drawing, 5 for water drawing, B for body drawing, G for body grabbing, then use left click to do those things.
- You can also turn on the grounded visualization via F2 if you like.
- L to enter freelook and arrow keys to move freelook around if you want more space to experiment.
- C to clear everything you've drawn.
Technical details for making lots of logging statements not slow down the game quite as much:
- I integrated Rust's tracing library so I can turn sections of logs on and off easily, though not (yet) at runtime. Tracing's spans also makes it easy to use spans to say "these log lines are all for atom X".
- I send logs to a background thread which actually writes the log to "standard out" (which is read by my terminal). I originally did this so that I could filter the log lines in a background thread, but it turns out that this way the main game thread isn't slowed down by standard out needing to pause its writes because the terminal is being slow to read lines from the game, which makes a surprisingly big difference!
Keeping high fidelity logs around uses up a lot of memory, so despite the speed improvements I still turn the default log level down for web builds.
Yes we have proper footnotes on Slow Rush now! At some point I'd like to make it so you can click them and they render in a little popover bubble like waitbutwhy does. Baby steps :)
Otherwise a body moving upwards that collides with a falling block of atoms would cause the 1st row of atoms to be Semi-grounded, the second row to be fully Grounded, and that row would create a collider which the moving body would bounce off!
Technically the atoms don't get removed from the collider - the collider just is recreated without them.