The Problem With Arcade Physics
TL;DR: I have a fix for arcade physics, but it's disruptive, so I'm not trying particularly hard to get it upstream. Let me know if I should! My asks (etc) are open!
If you want it, it's available @ [my github](https://github.com/browndragon/phaser/tree/solidArcade)
Phaser comes batteries-included with arcade physics, an AABB basic physics engine. So of course I'm building fidough, my ridiculous boxes on conveyor belt game with it. Which is insane. It's fine.
But I hit a problem: naive conveyorbelts didn't work:
Okay. So why does that happen?
The code was simple:
function overlap(sprite, tile) {
const {x, y} = this.offsets[tile.index];
console.assert(Number.isFinite(x) && Number.isFinite(y));
ticknudge(sprite, x, y);
}
function ticknudge(sprite, x, y, still=.01, nudge=.001) {
sprite.body.x += x;
sprite.body.y += y;
sprite.body.updateCenter();
}
So what gives? that looks right! It isn't.
_dx and _dy: why?
The old code had a simple flaw: it had multiple representations of the same data, and needed to remember them all. The core metaphor of arcade physics separation is to determine the motion of the two intersecting objects and to restore them roughly antiparallel to the direction of motion.
This is deadly: motion is underspecified. Antiparallel isn't hugely specified. Motion brings in the notion of time.
Obviously, the old code had `velocity` concept, it is a physics engine. Velocity is tricky. In a physics engine, it represents the continuously updated per-frame motion component, but it is obviously not the actual total amount of movement that was definitely executed. For instance: if the physics body has a lot of drag and perpendicular acceleration and lots of colliding objects, its actual displacement over the last tick (upcoming AND over the last frame!) isn't necessarily the velocity times time.
This is a problem for me. Remember, my whole schtick is walkways, which are effectively sticking an object in a constant velocity frame of reference relative to the camera, which for one reason and another isn't ideally represented with careful force-and-drag (or absolute max veloc), but instead a per-tick direct modification to colliding objects' velocity.
The first advice was to ensure that if I modified `position.x`, to modify `_dx`. That's definitely good advice; the way that Arcade Physics detects motion is through that _dx thing; if I modify position.x of a completely still object, it won't reflect in its actual motion, and so it won't reject from the walls. QED.
Okay, so let's grab a snap with the above code modifying _dx & _dy whenever we modify position.x and position.y. Spoiler, it still doesn't work!
This is the `embedded` logic biting us, deciding that two colliding objects without motion can't be repaired. At first, I thought that Phaser was wrong when it estimates how much an arcade body has moved, because it uses velocity ("per-tick added motion") instead of a continuously evaluated _dx & _dy ("per-tick observed displacement"); it does so much math already that I just deleted _dx and _dy in favor of subtracting previous position from current position. I also gave it enough intelligence to extrapolate a per-second displacement from the observed previous-tick displacement.
And do you know what? It still didn't work!
On passing data between ticks
The logic was simple. The physics algorithm is:
For each body: Start the frame: copy bounds and transform information from the game object back into the arcade body
For each body: Start the tick: calculate next position from velocity, drag, acceleration, etc
For each collider: Execute it! (and by coincidence, I'm doing object intersection first and walls nearly last)
Goto 2 if there's more time left in this frame
For each body: End the frame: Push position information back to the game object.
So what's missing? The only logical time to snapshot prev (and calculate dx) is on step 2. But my treadmills execute in step 3! So their motion is *always* forgotten by this algorithm, since the snapshot captures the value they'd written; any objects that are stranded within walls seem always to have been there, since the current and previous position are equal as an accident of this algorithm.
The Fix
We truly don't need dx as a body property; it's just the difference of current and previous position, so we can throw that out. But we need to capture previous position in such a way that the velocity in the last tick and the wall rejection, tread application etc are included! The obvious fix is to calculate a snapshot just *after* calculating position at 2, but to put it in place just *before* calculating 2 on the next run through. As a result, the displacement on each tick is the previous tick's collider adjustments, any updates from the game object position [for ticks that span frames], and the current round's velocity update. There's a risk here! We actually sync out to the game object a value which is never actually used as a previous value in the physics engine. This doesn't seem to be a problem in practice, but perhaps there are exotic collision schemes which pose a problem here.
This version no longer uses velocity as an input to collision or separation, preferring to use calculated-on-demand dx & dy values. Everywhere. This means that everything "just works":
If you modify your game object's position during e.g. `update`: the body will be set to that position during its next step 1 as before (and then have its velocity added), but newly, the body's prev during update & collision will be the value just after the velocity was added
If you modify your body's position during a collider: the body's prev was snapshotted before any colliders ran, so that new position will count as motion for collision and embedding logic.
So, the ridiculous finished project: fully generic relative motion on walkways in arcade physics. Whew!








