# @tiptap/collide2d@0.1.0

<!-- Generated by scripts/gen-package-reference.mts from the package source.
     Do not edit: §15.4 — package docs are generated, nobody writes them. -->

**Spec §7.1: `@tiptap/collide2d`** — the 2D collision primitives.

## Where this came from, and what the extraction found

This file is `turnphysics/src/geom.rs`, moved. Not rewritten — §7 says the
physics is "not rewritten, it is unwrapped", and the golden checksums in
`sim-wasm/tests/golden.rs` are what holds that to it.

`arena2d` had its own copy of the same primitives, and comparing them before
the move is the reason to do it at all. Most of the overlap was identical;
`sweep_point_circle` was not:

```text
let r = radius.raw() as i64;
+   if r < 0 {
+       return None;
+   }
```

turnphysics grew that guard and arena2d never did. It happens to be
unreachable in `arena2d` — `validate_config` refuses every radius at or below
zero, three separate times — so the copies were *behaviourally* the same and
the divergence cost nothing. This time. That is what a duplicated primitive
looks like right up until it doesn't, and the whole argument for §7 is that
there should be one of these rather than three.

## What it depends on

`sim_core::fixed`, and nothing else. No module types, no config, no room, no
wire. That was true of the source file before it moved, which is why this is
the first extraction in the catalogue: the coupling that would have made it
hard was never there.

## The rules that come with it

**Fixed-point only.** `sim-core`'s float ban applies here for §13.6's reason,
and this crate is where it bites hardest — a collision routine is the most
tempting place in a codebase to reach for a hardware square root. Use
[`rsqrt`]. (`sim-core/tests/determinism.rs` scans every workspace member's
source for float type *names*, so it caught an earlier draft of this very
paragraph. Blunt, and correctly so — it also means a new crate is covered the
moment it joins the workspace, with nothing to remember to add.)

**No panics, bounded time.** §11.5: these run on a phone against geometry a
stranger's config chose. Every routine here returns `Option` rather than
asserting, and every loop is bounded by a caller-supplied slice.

## Types

```rust
pub struct Aabb
```

An axis-aligned box.

Kept as a distinct shape rather than folded into [`Convex`] even though a box
*is* a four-vertex convex polygon, and the reason is exactness rather than
speed. The slab test below produces normals that are exactly `±1` on one axis
and exactly zero on the other; the polygon path produces them through
`normalize`, which floor-divides and lands within an ulp. A pool cushion is
the case where that matters — a ball bounced off an axis-aligned rail a
thousand times should come back on exactly the reflected heading, not on one
that has drifted an ulp per bounce.

`tests::a_box_and_the_same_box_as_a_polygon_agree` is the control that keeps
the two paths from diverging in anything that matters.

```rust
pub struct Circle
```

A circle. Static round geometry, a target region, or a dynamic body's shape.

```rust
pub struct Convex<'a>
```

A convex polygon: vertices in **counter-clockwise** order, with one
precomputed outward unit normal per edge.

Edge `i` runs from `verts[i]` to `verts[(i + 1) % n]` and its outward normal
is `normals[i]`. The two slices must be the same length and the winding must
be counter-clockwise; [`validate_polygon`] is what enforces both, once,
at config validation, and nothing here re-checks — a sweep that revalidated
its geometry on every call would be paying the config's cost sixty times a
second on somebody's phone (§11.5).

```rust
pub struct Hit
```

Where and how a sweep stopped.

```rust
pub struct Penetration
```

A static overlap: how to get out, and how far.

The sweep answers "when do these touch". This answers "they already do, now
what" — which is the separation pass's question, and the one that keeps a
rack of balls from ending a tick fused when the contact iteration budget runs
out. See `step::separate`.

## Constants

```rust
pub const MAX_POLY_VERTS: usize = 12
```

The most vertices a convex polygon may have.

Twelve, moved with `validate_polygon` from `turnphysics::types`. It is a
bound rather than a preference: every convex routine here is `O(verts)` and
runs inside a rollback that may re-simulate it sixty times in a frame, and
the vertices arrive from a config a stranger wrote (§11.5).

```rust
pub const POS_QUANTUM_RAW: i32 = 1 << 9
```

The position quantum every genre module snaps to — `1 << 9` in raw units.

It arrived with the tests that came from `turnphysics`, where it lived in
`types.rs` as a module tuning constant.

**An earlier version of this comment said the value was the same in all four
modules. It is not** — `racer` uses `POS_SHIFT = 10`, because a circuit is
bigger than an arena and the wire's 16-bit positions have to reach the far
end of it. The claim was wrong when written and is corrected here rather than
deleted, because it is exactly the assumption that would make a shared
quantiser silently re-scale one module.

It stays as a constant because what it is used for is comparison tolerances
in this crate's own tests, where a tolerance that varied by caller would make
the same geometry pass in one crate and fail in another. Code that needs to
snap to a *module's* lattice uses [`sim_core::quantise`] and passes the shift.

```rust
pub const SKIN_RAW: i32 = 1024
```

Back-off distance, in raw units: 1/64 of a world unit.

A sweep that stops exactly on a surface starts the next sweep exactly on it,
and the next test then produces `t = 0` with a normal decided by a comparison
at the boundary. That is the classic stuck-in-geometry bug and it is a
rounding artefact rather than a physics one.

**It must be larger than half a position quantum.** `types::snap_position`
rounds to the nearest lattice point at the end of every tick, moving a body
by up to `POS_QUANTUM_RAW / 2` = 256 raw. A back-off below that gets rounded
straight back onto the surface it was meant to escape. 1024 raw is four times
the worst rounding, and it is also what [`SUPPORT_RANGE`] is derived from.

```rust
pub const SUPPORT_RANGE_RAW: i32 = SKIN_RAW * 4
```

How close a surface has to be to hold a body up — 4 × [`SKIN`], 1/16 unit.

# This constant is the whole reason a ball can sit still under gravity

A body that comes to rest on the floor is left `SKIN` above it, because the
sweep backed it off. Gravity then accelerates it for a tick or two, it falls
the `SKIN`, contacts, has its normal velocity zeroed, and is backed off
again — forever, at a speed of roughly `|g| · dt` per cycle. With a gravity
of 20 that is 0.33 units/s of permanent jitter, which is well above any
settle threshold worth having, so **the shot never ends.**

The fix is not a smaller skin. It is to notice, *before* integrating forces,
that a surface is within reach, and to remove the component of gravity
pointing into it — see `step::supported_gravity`. This is the reach.

It is derived rather than chosen: it has to exceed `SKIN` (or a body resting
exactly at the back-off distance is not seen), and it has to exceed half a
position quantum on top of that (or the end-of-tick snap can push a resting
body out of its own support). `SKIN + SKIN/2` would satisfy both; 4 × `SKIN`
takes the same argument with three times the margin, and the cost of being
generous is that a body one sixteenth of a unit above the floor is treated as
standing on it. Against a default ball radius of 1 that is 6% of a radius.

## Functions

```rust
pub fn expanded(&self, r: Fixed) -> Aabb
```

Grown by `r` on every side — the Minkowski sum with a circle's bounding
box, which is what the slab test actually intersects.

```rust
pub fn shrunk(&self, r: Fixed) -> Aabb
```

Shrunk by `r` on every side. May become invalid; callers check.

```rust
pub fn center(&self) -> Vec2
```

The midpoint, computed in `i64` so a box spanning most of the coordinate
range does not overflow on the way to its own centre.

```rust
pub fn is_solid(&self) -> bool
```

Non-empty: a box with zero width or height is a line, which nothing can
stand on and which a creator typed by accident.

Distinct from [`Aabb::is_valid`], which only asks that `min <= max`. A
degenerate box is *valid* and not *solid*, and the two callers want
different questions answered.

```rust
pub fn overlaps(&self, other: &Aabb) -> bool
```

**Strictly** overlapping: two boxes sharing exactly an edge do not.

That strictness is the rule an axis-at-a-time movement resolution depends
on. A body resting on a floor has `body.min.y == floor.max.y`; if that
counted as an overlap, the horizontal pass would treat the floor it is
standing on as a wall in front of it and the player could not walk.

```rust
pub fn overlaps_x(&self, other: &Aabb) -> bool
```

Whether the two boxes' X ranges overlap strictly.

```rust
pub fn overlaps_y(&self, other: &Aabb) -> bool
```

Whether the two boxes' Y ranges overlap strictly.

```rust
pub const fn rmul(a: i64, b: i64) -> i64
```

Raw Q16.16 multiply in `i64`, **saturating**.

`a * b` alone overflows an `i64` here and not only for absurd inputs: a
`Fixed::MAX` coordinate is 2.1 × 10⁹ raw and the broadphase squares a sum of
two of them — 1.8 × 10¹⁹ against an `i64` ceiling of 9.2 × 10¹⁸, which is a
debug-build panic and a release-build wrap, from a config a creator could
type. Saturating is right rather than merely safe: every use is a
*comparison*, and a saturated bound compares the way the true one would.

```rust
pub const fn rdiv(a: i64, b: i64) -> i64
```

Raw Q16.16 divide in `i64`, flooring — [`floor_div`]'s rule one layer up.

`sim_core::fixed` floors every division in the crate specifically so that
there is one rounding rule. A module that reached for `/` here would have
reintroduced the second rule where it is harder to see, and the symptom would
be a desync rather than an off-by-one.

```rust
pub const fn narrow(v: i64) -> Fixed
```

Narrow a raw `i64` to a `Fixed`, saturating.

```rust
pub fn rsqrt(v: i64) -> i64
```

Integer square root of a raw Q16.16 value, returning raw Q16.16.

`Fixed::sqrt` takes and returns a `Fixed` and the discriminant here does not
fit in one. Same bit-by-bit method, same fixed 32 iterations, same reason:
its cost does not depend on the input, which matters inside a function
adversarial bytes reach (§11.5).

```rust
pub const fn cross_raw(a: Vec2, b: Vec2) -> i64
```

The 2D cross product, raw Q16.16 in `i64`.

Scalar in two dimensions. Used for winding checks and for the angular half of
a friction impulse.

```rust
pub fn safe_normal(v: Vec2) -> Vec2
```

A unit vector with a defined answer for the zero vector.

Two bodies at *exactly* the same position have no separating direction, and
`+X` is arbitrary — but it is arbitrary and **deterministic**, which is the
property that matters. Returning `ZERO` would leave them fused forever, and
every client would agree about that too, which is a worse kind of consistent.

```rust
pub fn sweep_point_circle(start: Vec2, delta: Vec2, center: Vec2, radius: Fixed) -> Option<Hit>
```

Sweep a point along `delta` against a circle. The kernel of everything else.

Solves `|p + t·d|² = r²` in raw `i64` Q16.16 and returns the **entry** root
only. A sweep that starts inside is reported at `t = 0` with the escape
direction, which is what the caller needs to push out of geometry rather than
sweep from an impossible position.

```rust
pub fn sweep_circle_circle(start: Vec2, delta: Vec2, radius: Fixed, other: Circle) -> Option<Hit>
```

Sweep a moving circle against a static circle: the same solve with the radii
summed, which is the Minkowski sum of two discs.

```rust
pub fn sweep_circle_aabb(start: Vec2, delta: Vec2, radius: Fixed, box_: Aabb) -> Option<Hit>
```

Sweep a circle of `radius` from `start` along `delta` against a static box.

The circle is reduced to a point and the box is grown by `radius` — the
Minkowski sum. That sum is a *rounded* rectangle, and this tests the
rectangle first (a slab test) and then repairs the four corners, because the
slab test's answer is wrong exactly in the corner regions: it reports contact
on the square corner of the expanded box, up to `radius × (√2 − 1)` outside
the true rounded one.

Skipping the repair is the standard shortcut and on a pool table it is
visible — a ball clipping the end of a cushion near a pocket jaw catches on
nothing, a fraction of a radius early, which is precisely the shot a player
was aiming at.

```rust
pub fn sweep_circle_convex(
```

Sweep a circle of `radius` from `start` along `delta` against a convex polygon.

The Minkowski sum of a convex polygon and a disc is the polygon offset
outward by `radius`, with a circular arc at every vertex. This tests the
offset faces and the vertex arcs separately and takes the earliest, which
covers the sum exactly and needs no polygon clipping.

**Ties break on the earlier feature index**, faces before vertices. Two
contacts at exactly the same `t` happen at a vertex — the face and its
endpoint arc meet there — and the tie has to resolve the same way on every
machine or the §11.7 verifier reports a disagreement that is really an
unstable comparison.

```rust
pub fn sweep_circle_inside_aabb(
```

Sweep a circle that is **inside** a box against the box's inner faces.

The container case, and it is not [`sweep_circle_aabb`] with the sign
flipped. That function treats a box as solid and asks when a circle outside
it arrives; this one treats the box as a room and asks when a circle inside
it reaches a wall. A pool table's cushions are the second thing, and writing
them as four solid boxes around the outside works until a ball is placed a
hair outside one and is then trapped behind it.

The circle's centre is confined to `bounds` shrunk by `radius`. A `bounds`
smaller than two radii has no interior at all and reports contact at `t = 0`
with an arbitrary but deterministic normal, rather than reporting nothing and
letting the body leave — `validate_config` refuses such a config, and this is
the second line for one that arrives anyway.

```rust
pub fn overlap_inside_aabb(center: Vec2, radius: Fixed, bounds: Aabb) -> Option<Penetration>
```

How far a circle inside a box has strayed past its inner faces, and the way
back. The [`sweep_circle_inside_aabb`] counterpart for the separation pass.

```rust
pub fn closest_point_on_segment(p: Vec2, a: Vec2, b: Vec2) -> Vec2
```

The nearest point to `p` on the segment `a`–`b`.

```rust
pub fn ray_aabb(origin: Vec2, dir: Vec2, box_: Aabb) -> Option<Fixed>
```

Where a ray first meets a box, as a fraction of `dir`.

`dir` is the whole ray rather than a unit direction, so a caller controls
range by scaling it.

```rust
pub fn reflect(v: Vec2, normal: Vec2, restitution: Fixed) -> Vec2
```

Reflect `v` about `normal` with a coefficient of restitution.

`restitution = 0` is a body that stops dead against the surface in the normal
direction and keeps its tangential motion — which is what a resting contact
needs. `restitution = 1` is a perfectly elastic bounce. Anything above one is
an energy source and `validate_config` refuses it.

A `v` already moving away from the surface is returned unchanged rather than
reflected back into it. That case is reached constantly: a body backed off by
`SKIN` and re-tested next tick is separating, and reflecting it would be a
second bounce off a surface it already left.

```rust
pub fn slide(v: Vec2, normal: Vec2) -> Vec2
```

Remove the component of `v` pointing into `normal`, leaving the rest.

The zero-restitution half of [`reflect`], and the operation that makes a body
slide along a surface instead of stopping dead on it.

```rust
pub fn validate_polygon(verts: &[Vec2]) -> bool
```

Whether a vertex list is a usable convex polygon.

Three rules, all of which the sweep in [`sweep_circle_convex`] assumes
and none of which it re-checks at 60 Hz:

1. Between three and [`MAX_POLY_VERTS`] vertices.
2. No zero-length edge — a repeated vertex has no normal, and
[`safe_normal`] would give it an arbitrary one that points into the
shape as often as out of it.
3. **Strictly convex and counter-clockwise**: every consecutive edge pair
turns left. A concave polygon does not crash the sweep; it produces
contacts on faces that are not there, which is worse, because it looks
like physics.

```rust
pub fn outward_normals(verts: &[Vec2]) -> Vec<Vec2>
```

The outward unit normals of a **counter-clockwise** convex polygon.

Edge `i` runs from `verts[i]` to `verts[(i + 1) % n]`. For counter-clockwise
winding the interior is to the left of each edge, so the outward normal is
the edge rotated *clockwise*: `(e.y, −e.x)`, normalized.

Allocates, and is config-time only — [`TurnPhysics::new`] calls it once per
polygon and copies the result into a fixed array.
