Jelly48 icon Jelly48

The physics of Jelly48: how a soft-body 2048 works

June 10, 2026

Jelly48 looks like 2048, but nothing in it is a grid animation. Every tile is a small physical body being simulated about 600 times a second, and every wobble, squash, and merge falls out of that simulation. This post is a short tour of how it works.

A tile is a ring of 16 particles

Each jelly tile is a closed ring of 16 point masses tracing a rounded square. The particles are held together by three families of constraints: distance constraints along the perimeter (the skin), bending constraints that resist sharp creases between neighboring edges, and diagonal bracing across the interior so the tile keeps its proportions instead of collapsing like a wet paper bag. Stiffness is expressed as XPBD compliance, which is what lets a tile read as "firm jelly" rather than either rigid plastic or soup.

XPBD in one paragraph

The solver is XPBD — extended position-based dynamics. Instead of accumulating forces, each simulation substep moves every particle by its velocity, then repeatedly projects the positions back toward satisfying every constraint, with each constraint allowed an error budget set by its compliance. Velocities are re-derived from how far positions actually moved. PBD-family solvers are unconditionally stable, which matters in a game where a player can slam a pile of jelly into a wall sixty times a minute. Jelly48 runs 10 substeps per 60 Hz frame, so the constraint solver effectively ticks at 600 Hz.

The input is gravity itself

A swipe doesn't move tiles — it rotates gravity. Swipe left and the whole world's "down" becomes left; every tile tumbles and re-piles against that wall. That one decision is most of the game's feel: moves resolve as real avalanches, with tiles shouldering each other out of the way, instead of lockstep grid shifts.

Merges are real geometry

In classic 2048 a merge is a table update. In Jelly48 two equal tiles merge when the physics says they're genuinely touching — the collision solver reports actual contact, not proximity. The game then computes a boolean union of the two deformed outlines, resamples that blob back to 16 vertices, and spawns it as a single new body that inherits the pair's momentum. Over the next half second the blob morphs toward the proper tile shape by retargeting its constraint rest-lengths — so a merge reads as two jellies fusing and settling, because that is literally what the simulation is doing. If the union geometry would be degenerate (tiles barely kissing at a corner), the merge is rejected and the tiles bounce — better an honest bounce than an exploding blob.

Self-collision, or: jelly should not pass through itself

Squeeze a soft ring hard enough and its outline tries to fold through itself. Each substep, the engine runs a swept-segment test per vertex against its own body's non-adjacent edges — "did this vertex cross that edge since the last substep?" — and unwinds any crossing through the regular contact solver. It's the difference between a tile that creases believably under a pile and one that turns inside out.

Where it runs

The engine is written in Rust and compiled to WebAssembly; rendering is WebGPU. The physics itself runs on the CPU at game scale — which sounds backwards for a "GPU physics engine" until you see the measurements. That story (176 GPU dispatches per frame to simulate 192 particles, and what we did about it) is the subject of the next post.

Play Jelly48 →