Collision: AABB and tile-based
The mechanism
Two AABBs overlapping — the separating axis theorem in miniature
A box is given by its edges [minX,maxX]×[minY,maxY]. Two boxes do not intersect if there is an axis along which their projections come apart. For axis-aligned boxes there are only two candidate axes (X and Y), so intersection is the negation of "they separated on at least one":
Four comparisons, zero multiplications — which is why this is the basic broad-phase test in every engine. If all four are true, the boxes overlap; the penetration depth on each axis is the smaller of the overlaps, and the cheapest way to push an object out is along the axis of least overlap (the minimum translation vector).
Tile collision — O(1) instead of "everything against everything"
Checking an object against every wall is O(n). But if the world is laid out in tiles of size T, the object's coordinates directly address the cells: it is enough to look at the 2–4 tiles its box covers.
This reduces "find the nearest wall" to a lookup by index — the same advantage the tilemap gives rendering. The key resolution technique is moving and resolving one axis at a time: move along X → check and push out of walls on X; then move along Y → check and push out on Y. Keeping them separate is critical: resolving both at once makes the object snag on the corner of a tile and get stuck on a flat floor made of tile seams.
Tunneling and sweeping (swept AABB)
A discrete check looks at the position after the step. If the object moved farther than the wall is thick, it was in front of the wall last frame and behind it this frame, and nobody checked the contact in between → it flew through (tunneling). Two cures: a small fixed timestep (which caps the displacement per tick — see the game loop lesson) and a swept AABB — computing not "do they overlap now" but when the first contact happens along the movement segment. For motion along an axis, the entry time per axis is:
where d_near is the distance to the near face of the obstacle along that axis and v_axis is the velocity along it. The contact is real if t_hit ∈ [0,1] and, at that moment, the projections already overlap on the other axis. Put the object at the position at t_hit, kill the normal component of the velocity and "slide" along the tangent.
A worked example. A bullet flies right at 50 px/tick; a wall 8 px thick sits 30 px ahead of it. Discretely: in one tick the displacement of 50 > (30+8) → the next position is already past the wall, there is no overlap at check time → tunnel. Swept: t_entry = 30/50 = 0.6 ∈ [0,1] → contact at 60% of the step, and the bullet honestly stops in the wall. A smaller fixed step (say 4 substeps of 12.5 px) also catches the wall — but costs more.
🕹 Games to play — and what to notice
From "collision in one line" to the pixel-exact integer physics of platformers. For each case: how it was done and what to provoke to feel the collision model with your hands.
The ball and the paddle are effectively AABBs, but what is interesting is the behavior after contact: the bounce angle often depends on where on the paddle the ball landed (the edge gives a steeper angle) — that is no longer pure physics but a design layer on top of a simple overlap test.
🎮 Play: in Pong, hit the ball with the edge of the paddle versus the center — compare the bounce angles. Check whether the paddle is a segment or a rectangle: catch the ball right at the tip.
Bullet against alien is a box overlap; the aliens stand in a grid, so the check goes through addressable cells rather than "every bullet against everyone". The shields are a bitmap chewed away pixel by pixel: a bullet touching a shield erases pixels. Two different collision modes in one game: coarse (grid AABB) and exact (a bitmask on the shield).
🎮 Play: shoot into a shield at an angle — it degrades pixel by pixel, in irregularly shaped holes. That is a pixel mask, not an AABB. Whereas a hit on an alien is an instant "box in box".
Mario is an AABB against a tile grid. The genre classic: separate resolution of X and Y (otherwise you snag on tile seams), a "head upward" check for hitting a block and a "feet" one for landing. At high speeds tunneling shows up — speedrunners squeeze through thin walls once they build up momentum.
🎮 Play: in SMB, build up speed on a downhill and fly into the corner of a block — you'll feel the game nudge you one axis at a time. Look up the speedrun tricks that pass through a wall — that is tunneling at high speed.
Maddy Thorson builds collision on whole pixels: an object is moved one pixel at a time with the fractional remainder banked, and each step is a simple AABB test against the tiles. That is anti-tunneling by brute force (the displacement is capped at a pixel) plus determinism (integer coordinates), which is what frame-perfect tricks and TAS rest on.
🎮 Play: in Celeste, notice the "forgiving" collision at edges (corner correction nudges you past a corner). That sits on top of honest per-pixel AABB — a design layer, like the bounce angle in Pong.
Where AABB isn't enough: slopes and loops. Sonic is not a box: he has sensors (rays pointing down and sideways) that read the tiles' height arrays (a height profile per tile). That is no longer "boxes overlapping" but "probing the surface" — the price for the speed and terrain that a flat AABB cannot give.
🎮 Play: ride a loop or a slope in Sonic — notice how he sticks to the surface at any angle. A box can't do that; those are sensors reading tile height maps.
Deep end · theory: SAT, the Minkowski sum and time of contactskippable
The AABB test is a special case of the separating axis theorem (SAT): two convex bodies do not intersect ⇔ there exists an axis on which their projections do not overlap. For arbitrary convex polygons the candidate axes are the face normals of both bodies; for AABBs the normals degenerate into X and Y, so there are only two axes and the test is that cheap.
The Minkowski sum — why "box against box" = "point against box"
A intersecting B is equivalent to the origin lying inside the Minkowski difference A ⊖ B. For two AABBs that difference is again an AABB (with the half-sizes added). Hence the trick: the problem "a moving box against a wall" collapses into "a moving point (the center) against an inflated wall", and the swept test becomes a ray-AABB intersection — the classic slab method.
The slab method and entry/exit times
A ray p + t·v against an AABB: for each axis you compute the interval [t₁,t₂] during which the ray is inside that axis's slab, and take the intersection of the intervals over all axes:
There is an intersection ⇔ t_enter ≤ t_exit and the interval touches [0,1]. The axis that produced the maximum at entry defines the contact normal — velocity is killed along it and slides along the other. That is continuous collision detection (CCD) for AABBs in its purest form.
Deep end · engineering: broad-phase, spatial indices, determinismskippable
- Broad-phase → narrow-phase. First a cheap conservative cull of candidate pairs (AABB overlap), then the expensive exact test only for the survivors. The junior anti-pattern is running the exact test on every pair: that is O(n²).
- Spatial hash / uniform grid. You bucket objects into cells and only check within a cell and its neighbors. For objects of similar size this is close to O(n) and simpler than a tree. Tile collision is the degenerate case where the grid already exists.
- Sweep and prune. You keep objects sorted by their projection onto an axis; the candidate pairs are those whose intervals overlap. It works well under temporal coherence (little changes from frame to frame).
- Hierarchies for mixed sizes. When object sizes differ a lot (and a grid is a poor fit), you take a BVH of AABBs — the same primitive, but in a tree. A direct bridge to rendering (frustum/occlusion culling) and ray tracing.
- Determinism. Integer/fixed-point collision + a fixed order of resolving pairs = a reproducible result for lockstep and replays. Float and a non-deterministic order break network sync.
ML / AI (your domain): AABB overlap is literally IoU (intersection-over-union) in object detection; NMS (non-max suppression) suppresses boxes by the same intersection test. A spatial hash ⇄ ANN / LSH: you bucket vectors and only compare within a bucket. Broad→narrow ⇄ coarse-to-fine retrieval: a cheap ANN candidate generator, then an exact rerank — the same two-phase structure as broad/narrow-phase.
Systems / databases: spatial indices (R-tree, geohash, quadtree) for geo queries; "only check the neighboring cells" = bucketing/sharding by a range key.
Graphics / geometry: BVH and frustum culling in rendering and ray tracing are the same AABBs in a tree; collision and visibility get solved with one structure.
The principle: never run the expensive exact test on every pair. First a cheap conservative filter (one that can only err toward "maybe"), then the exact test on the survivors.
move_and_slide per axis and watch the resolution. Then break it: crank the object's speed and remove CCD / the small step — you'll catch tunneling through a thin tile. Bring the small fixed step back and the tunnel disappears.dt during a hitch provokes it.Why are X and Y resolved separately rather than both at once?
AABB overlap and IoU in object detection — really the same formula?
max(0, minMaxX−maxMinX) · max(0, …Y…). IoU is that intersection divided by the union. So the "did they overlap?" test from collision is IoU's numerator. NMS in detectors discards boxes with a high IoU against an already-accepted one — literally the same geometric primitive as push-out in physics. Different domains, one geometry of axis-aligned boxes.Is tunneling about collision or about the game loop?
dt the step balloons and the tunnel returns — which is why fixed step and collision are one conversation.Rotate an object by 30° — why does the AABB suddenly lie?
When is a tile grid worse than a tree (BVH/quadtree)?
- Christer Ericson, "Real-Time Collision Detection" — the industry bible: AABB, SAT, swept tests, BVH.
- Rodrigo Monteiro, "Higher-Order Fun: 2D collision with tile maps" — the classic treatment of tile collision and per-axis resolution.
- Maddy Thorson, "Celeste & TowerFall Physics" — per-pixel integer collision in practice.
- Module 1, "Sprite Rendering / Collision" (
01-foundations-1970-1985.md).