Physics · type

FrictionModel

How the two tangent rows are bounded against each other.

Explained in Your first game, Rigid bodies.

type FrictionModel = 'box' | 'elliptical'
import type { FrictionModel } from '@driftengine/physics';

In depth

box is two independent clamps and is what this solver has always done. Each axis is bounded at mu * N on its own, so along the diagonal between them the pair reaches sqrt(2) * mu * N — forty-one per cent more friction than the same surface offers along an axis. The tangent basis is constructed from the contact normal, so which headings are cheap is an artefact of that construction rather than a fact about the world, and a box measurably slides further in some directions than others.

elliptical couples them and friction becomes isotropic, which is what it physically is. It is a correctness fix and it is not the default, because every stacking result moves under it: fingerprintBodies, both baselines, and any golden fingerprint or stored replay a consumer has kept. A production engine does not silently invalidate those. What would reverse that: a major version, where the honest thing is to make the ellipse the default and say so in the porting guide.

One coefficient, not two, and that is a decision rather than an omission. Anisotropic friction is the natural API for an ellipse — different mu along each tangent — and it needs a tangent frame anchored to a material, which this solver does not have: prepareContact builds the tangents from the normal, so they rotate arbitrarily as a contact moves and a per-axis coefficient would mean something different from one tick to the next. That is a silent wrong answer, so the ellipse here is a circle. What would reverse it: a tangent frame carried by the body, which is a larger change than the cone; vehicle.ts already needs that and answers it its own way, with a grip table rather than a coefficient.