glemy/physics/entity

Types

A single physical entity: a circular body tracked by position and velocity, with a radius for collision detection and rendering, and a kind — a plain per-entity classification tag with no built-in meaning at this level. Core physics never inspects kind itself (see glemy/physics/collision_sweep’s interact callback, which is where any kind-dependent behavior a specific game wants actually lives); a game built on glemy/physics is free to use it however it needs – glemy’s own current game uses it as a discrete size/merge- progression identity (glemy/games/tiers), but nothing at this layer assumes that. resting_time is settle’s own bookkeeping — see there.

pub type Entity {
  Entity(
    position: vector2.Vector2,
    velocity: vector2.Vector2,
    radius: Float,
    kind: Int,
    resting_time: Float,
  )
}

Constructors

Values

pub fn integrate(
  entity: Entity,
  acceleration: vector2.Vector2,
  dt: Float,
) -> Entity

Advances an entity by dt seconds under a constant acceleration, using semi-implicit (symplectic) Euler integration: velocity is updated from the acceleration first, then position is updated using the new velocity. This ordering is what makes it symplectic — unlike plain (explicit) Euler, it nearly conserves energy over many steps, which is why it’s the standard choice for game physics.

integrate(
  Entity(vector2.zero, vector2.zero, radius: 1.0, kind: 0, resting_time: 0.0),
  Vector2(0.0, -9.8),
  1.0,
)
// -> Entity(Vector2(0.0, -9.8), Vector2(0.0, -9.8), radius: 1.0, kind: 0, resting_time: 0.0)
pub fn loosely_equals(
  a: Entity,
  b: Entity,
  tolerance: Float,
) -> Bool

Compares two entities for equality within a tolerance, component-wise. See vector2.loosely_equals — same purpose, extended to an Entity. kind is compared exactly (==, not tolerance-based) since it’s a discrete Int identity, not a continuous measurement.

pub const rest_velocity_threshold: Float

Velocity magnitude below which an entity counts as at rest and is snapped to exactly zero on the spot – floating-point cleanup only, far smaller than any real in-flight velocity.

pub fn settle(entity: Entity, dt: Float) -> Entity

Applies velocity_damping, tracks Entity.resting_time (seconds continuously spent below sleep_disturbance_velocity, reset to 0.0 the instant it’s exceeded, capped at sleep_duration), and force-stops the entity (velocity snapped to exactly zero) once either rest_velocity_threshold (immediate floating-point cleanup) or sleep_duration (the sustained-quiet backstop) says it should stop. Called once per entity per frame, after integration/bounce/collision (see glemy/physics’s update).

The sustained-quiet backstop is necessary on top of bounds/collision’s own per-contact resting-contact handling, not redundant with it: two entities resting on each other (not just one entity against a static wall) form a coupled system under continuous gravity, and that coupling can find a genuine, stable non-zero equilibrium a purely reactive, single-frame velocity check can’t rule out for every mass/radius combination — confirmed directly, twice, by long (10-20 second), real, deterministic simulation traces: a hard resting-contact velocity cutoff (both 3.0 and a Box2D-ratio-derived 10.0) let two different-mass stacked entities settle into an exact, perfectly periodic bounce forever, and even replacing that cutoff with a smooth restitution ramp (glemy/physics/bounds’s genuine_impact_velocity) still converged to a different stable fixed point rather than genuine rest. resting_time is the second, independent mechanism real physics engines use for exactly this reason (Box2D’s own sleeping-body system, b2_timeToSleep/b2_linearSleepTolerance) — a time-based backstop that doesn’t care why an entity’s velocity has stayed small, only that it has, for long enough that continuing to “correctly” simulate it is pointless. See docs/decisions.jsonl, decisions 0027 and 0028.

pub const sleep_disturbance_velocity: Float

Velocity magnitude below which an entity counts as “quiet” for the purposes of resting_time – see that field and sleep_duration. Deliberately more generous than rest_velocity_threshold: this only has to distinguish “roughly settling” from “a genuine impact,” not “essentially exactly zero.”

pub const sleep_duration: Float

How long (seconds) an entity must stay continuously below sleep_disturbance_velocity before settle force-stops it outright, regardless of its exact velocity at that moment. Matches Box2D’s own b2_timeToSleep (0.5) — see resting_time’s own doc comment for why this exists at all.

pub const velocity_damping: Float

Fraction of its velocity an entity keeps every frame – a small, constant “drag,” not modelling any specific physical force, and not on its own what makes resting entities actually stop (see glemy/physics/bounds’s genuine_impact_velocity and glemy/physics/collision’s equivalent for that, plus resting_time below for the backstop even those don’t fully cover) – real gameplay motion (falling, sliding) visibly loses a little energy every frame rather than looking like a frictionless vacuum.

0.997, not a much smaller fraction: at 60fps a per-frame factor compounds fast (factor^60 over one second), so what actually matters is the per-second effect, not the per-frame number’s face value. The previous 0.98 compounded to keeping only ~30% of velocity after one second of continuous freefall – a real, measured drag strong enough to impose its own implicit terminal velocity (~80 units/s) well below what game.gleam’s gravity alone would produce, which is what made drops feel too slow (user report, decision 0032) despite this constant’s own doc comment describing only “a little” loss. 0.997 compounds to keeping ~83% of velocity after one second – genuinely a little, letting gravity actually dominate free-fall the way the doc comment always intended. See decision 0032 for the real, measured before/after fall-time numbers.

Search Document