glemy/physics/collision

Values

pub const genuine_impact_velocity: Float

Reference closing speed (velocity_along_normal) at/above which a collision’s impulse uses full restitution. Below it, the effective restitution scales down proportionally (restitution *. min(1.0, velocity_along_normal /. genuine_impact_velocity)) — not a hard “below X, absorb to 0; at/above X, full elastic” cutoff. Same value as glemy/physics/bounds’s genuine_impact_velocity, for consistency, and same reasoning, in full there: a hard cutoff was tried and shipped first, then directly measured (a real, repeated, multi-second headless-browser trace, not just gleam test passing) to let two different-mass, stacked entities settle into a perfectly periodic, never-decaying bounce with its impact speed sitting just above the cutoff forever — a hard threshold is a discontinuity the dynamics can balance on regardless of its exact value (both 3.0 and a Box2D-derived 10.0 exhibited this). A smooth ramp can’t be balanced on: below genuine_impact_velocity, every bounce returns strictly less energy than it received, forcing any resonance to shrink every cycle instead of holding steady. See docs/decisions.jsonl, decisions 0027 and 0028.

pub fn overlap(a: entity.Entity, b: entity.Entity) -> Float

How far two circular entities are overlapping — positive means they overlap by that many world units, zero or negative means they aren’t touching at all. Exposed on its own (not just inlined in resolve) since a caller-supplied physics/collision_sweep interact function often needs the same overlap test to decide whether two entities are even touching, independent of whether they end up being collision-resolved.

pub fn resolve(
  a: entity.Entity,
  b: entity.Entity,
) -> #(entity.Entity, entity.Entity)

Detects and resolves a collision between two circular entities: separates them if they’re overlapping, and reflects their velocities along the collision normal if they’re moving toward each other — at full (elastic) strength for a genuine, higher-speed impact, or proportionally softened for a gentle one (see genuine_impact_velocity). If they aren’t overlapping, both are returned unchanged.

Mass-aware (proportional to area — see mass) since decision 0018: entities of very different sizes now collide realistically (a large entity barely moves when struck by a tiny one) rather than assuming equal mass regardless of radius (the original, now-superseded decision 0007). The general unequal-mass elastic-impulse formula this uses reduces exactly to the old equal-mass formula when mass_a == mass_b — the textbook sanity check below still holds unchanged: two equal circles meeting head-on with equal and opposite velocities simply swap velocities. (Velocities of magnitude 20.0 here, not 1.0: velocity_along_normal needs to be at/above genuine_impact_velocity for a full, undiminished elastic swap.)

resolve(
  Entity(position: Vector2(0.0, 0.0), velocity: Vector2(20.0, 0.0), radius: 1.0, kind: 0),
  Entity(position: Vector2(1.5, 0.0), velocity: Vector2(-20.0, 0.0), radius: 1.0, kind: 0),
)
// -> #(
//   Entity(position: Vector2(-0.25, 0.0), velocity: Vector2(-20.0, 0.0), radius: 1.0, kind: 0),
//   Entity(position: Vector2(1.75, 0.0), velocity: Vector2(20.0, 0.0), radius: 1.0, kind: 0),
// )
pub const restitution: Float

How much of a genuine, higher-speed impact’s closing velocity is returned as bounce-back (see genuine_impact_velocity for how a gentler closing speed gets proportionally less than even this). Not 1.0 (a fully elastic, energy-preserving bounce) deliberately: a real headless-browser trace of a full-height drop with 1.0 showed the entity rebounding off the floor by ~16 world units (16% of the 100-unit play area) on its very first bounce, then bouncing again – nothing like the near-dead “plop” a merge-puzzle piece should have on landing. 0.1 matches the restitution value used by a real, working open-source clone of the reference game (TomboFry/suika-game, Matter.js-based) rather than being guessed at – unlike a raw gravity/velocity magnitude, a restitution coefficient is dimensionless (a fraction of energy returned) and so is meaningfully comparable across engines and world-unit scales. See decision 0029.

Search Document