Gessa Docs
Product · Reference

Reference

Reference: Physics Contract

The v1 physics contract surface over Rapier 3D and the shared character controller - body types, colliders, contact defaults, force commands, layers, queries, joints, and the named proof tests - pinned to symbols and linking down to the generated component reference.
engine v1.0.234since v1Copy for LLM

Last verified: 2026-09-02 against engine v1.0.232.

engine v1.0.232

Gessa physics is an integration contract over Rapier 3D for the rigid-body world plus a shared deterministic character controller for player movement. This page is the contract surface: what the physics components accept, what the runtime enforces, and which named tests prove each guarantee. It is a thin pointer over the generated component reference; the exact field shapes are generated facts and live there, not here. For the model behind the contract (the timestep derivation, the mass model, the transaction and epoch, and the controller equations), read Physics Simulation.

Canonical source

The physics component fields are a Tier-2 generated artifact. Read them directly; do not hand-edit them.

ReferenceECS component inventory (physics components)Resolved signature, schema and example

The Tier-1 source of truth is code: COMPONENT_TYPE_DEFINITIONS and ComponentSchema in packages/ecs/src/index.ts, the runtime physics modules under server/src/modules/runtime/physics/, and the shared controller under packages/runtime-shared/src/. The Rapier version is pinned as @dimforge/rapier3d-compat in server/package.json.

Body types

A body's type is the bodyType field of componentRigidBodyComponent RigidBodyComponent, which also carries mass, linearDamping, angularDamping, gravityScale, lockTranslations, lockRotations, and ccdEnabled. The runtime lowers each type to a Rapier descriptor in componentSync.ts:

  • static becomes a fixed descriptor.
  • kinematic becomes a position-based kinematic descriptor driven by setNextKinematicTranslation; Rapier derives its velocity from the per-tick delta.
  • dynamic becomes a dynamic descriptor.

Continuous collision detection defaults on for dynamic bodies and is opt-in for kinematic and static, resolved by ccdEnabledForBody; an authored ccdEnabled: false is honored. Changing bodyType re-fingerprints the body (bodyFingerprint), so the sync recreates it with the new authority, which is how a grab hands a body between dynamic and kinematic while it is held.

Colliders and contact defaults

Collider geometry is the shape field of componentColliderComponent ColliderComponent, one of box, sphere, disc, capsule, or mesh (a string form or an object form), with size, collision (solid, trigger, or none), layers, friction, and restitution. A string disc lowers to a flat Rapier cylinder. Contact material defaults come from the schema: friction defaults to 0.5 and restitution to 0, both clamped to [0, 1] by ColliderComponentSchema. No friction or restitution combine rule is configured, so Rapier's own default combine rule applies (the default value is a property of the pinned Rapier build, not this contract).

Mass is scalar: mass is explicit (default 1), angular inertia is isotropic and equal to the mass scalar via angularInertiaForMass, and collider density is forced to 0 by setDensity(0), so geometry never contributes mass.

Aggregate colliders for an componentInstanceSetComponent InstanceSetComponent lower through instanceSetCollider.ts to a single static Rapier collider: a grid becomes voxels, a free set becomes a trimesh.

Force commands and preflight

Runtime forces are the per-tick pending queue of componentForceComponent ForceComponent. Its authored entry schema, ForceEntrySchema, defines force, impulse, torque, torqueImpulse, applyAtPoint, teleport, and reset. Persistent user forces are cleared each tick before this tick's commands are applied, so a one-shot force does not integrate forever.

The whole command batch is preflighted for count and magnitude before any native mutation, and is rejected atomically on violation (physicsCommandMagnitudeViolation plus the batch count check in componentSync.ts): nothing partial is applied. The magnitude ceilings are safety bounds in RUNTIME_PHYSICS_POLICY: maxForceMagnitude 1e6, maxImpulseMagnitude 1e5, maxTorqueMagnitude 1e6, and maxLaunchVelocityMagnitude 1e3 m/s. The per-room budgets maxRigidBodies, maxColliders, and maxActiveForces are 1000 each.

A launch command (a velocity set) is not an authored ForceEntrySchema field: it is injected by runtime command translation (physicsPendingEntryFromBodyCommand in runtimeSystems.ts). For a character that carries the movement velocity lane (componentVelocityComponent VelocityComponent), a launch is redirected to a VelocityComponent patch (preflighted against maxLaunchVelocityMagnitude, emitting a RuntimeCharacterLaunched effect); a body without that lane falls through to the ForceComponent queue, where componentSync.ts applies it as a Rapier setLinvel.

Collision layers

Interaction layers are the layers field of ColliderComponent. The runtime caps interaction layers at MAX_RAPIER_INTERACTION_LAYERS (16) in layers.ts; layers beyond the cap are sliced off the Rapier interaction groups and a physics.collision_layer_budget_exceeded correction is pushed from componentSync.ts.

Queries

Physics queries are raycast, shapecast, and overlap, exposed to scripts through the runtime physics query host and resolved in queries.ts and rapierQueries.ts. Two limits are part of the contract: shapecast and overlap shapes are limited to box, sphere, and capsule (mesh query shapes are unsupported), and historical (lag-compensated) rewind is supported only for raycast; historical shapecast and overlap are not offered.

Joints

Joints are componentJointComponent JointComponent, lowered to Rapier impulse joints in componentSync.ts. The supported types are fixed, revolute, prismatic, spherical, spring, and rope. Motors and limits are honored only on revolute and prismatic joints and fail closed elsewhere; a generic six-degree joint is deferred.

Character controller surface

Player movement is the KCC. Its authored inputs are componentCharacterMovementComponent CharacterMovementComponent (maxSpeed, gravity, jumpSpeed, and the enableKcc governance flag) and the live velocity on VelocityComponent. deriveMovementParams (movementParams.ts) turns those components into controller parameters over the named defaults in RUNTIME_KCC_DEFAULTS, and both the server tick and the client predictor derive from the same components with the same function.

Two governance facts are part of the contract:

  • The KCC steps only a character whose CharacterMovementComponent.enableKcc is true; the schema default is false, resolved by the shared ownsKccGovernance predicate (kccGovernance.ts). For which character motor the runtime calls on each path, see the traced conclusion in Physics Simulation.
  • deriveMovementParams reads gravity as a non-negative field defaulting to 0, so an unauthored character does not fall under the KCC (vertical motion is opt-in).

The static-collision snapshot the KCC sweeps is derived by deriveCollisionSnapshot and is bounded at RUNTIME_COLLISION_SNAPSHOT_MAX_TRIANGLES (50000) triangles. It ignores collider rotation for box solids only (a world axis-aligned box cannot represent an oriented box); mesh-collider triangles carry the full authored rotation. It excludes movers (a VelocityComponent, a CharacterMovementComponent, or a dynamic RigidBodyComponent), so a kinematic or rigidbody-less solid is swept as static world geometry and character-versus-character stays server-authoritative.

Proof surfaces

Each guarantee is bound to a named conformance test or check:

  • Golden corpus (check:physics-golden-corpus, in the root check chain): per-tick observable output stays byte-identical across refactors at 6-decimal canon precision.
  • Native isolation (physicsKernelArchitecture.test.ts): every native Rapier dependency stays inside the adapter family, with exactly one world.step() owner.
  • Tick transaction (physicsTransaction.test.ts): a prepared step does not advance the coordinate, commit is monotonic, abort cold-rebuilds, and a config change is an observable epoch restart.
  • Character parity (runtimeNetcodeKccParity.test.ts): server authority and client predictor produce bit-identical {position, velocity, onFloor} over randomized tapes.
  • Body-type authority (physicsBodyTypeAuthority.test.ts): flipping bodyType changes the fingerprint and recreates the body with the new authority.
  • Governance (kccGovernance.test.ts): KCC governance is owned only by the explicit enableKcc flag.
  • Backend registry (physicsBackendRegistry.test.ts): the registry fails closed on an uninstalled backend and validates adapter output at the boundary.
  • Command atomicity (physicsFoundationFuzz.test.ts, physicsFoundationDefects.test.ts): an over-budget or over-magnitude command batch applies nothing.

Limits

  • No first-party integrator or solver; the engine drives Rapier world.step() (rapierAdapter.ts).
  • Angular inertia is isotropic and equal to the mass scalar, not shape-derived (angularInertiaForMass).
  • Mesh colliders require a static body; on a dynamic body they emit physics.mesh_collider_requires_static_body and are skipped.
  • Interaction layers are capped at MAX_RAPIER_INTERACTION_LAYERS; overflow is dropped with a correction.
  • shapecast and overlap shapes are limited to box, sphere, and capsule; historical rewind is raycast-only.
  • The backend selection object accepts only backendId; per-room tuning (gravity, solver and friction iterations, substep bound, CCD substeps) is the runtime-budget profile, hashed into physicsEpochKey.
  • Motors and limits apply only to revolute and prismatic joints; a generic joint is deferred.
  • No friction or restitution combine rule is configured; Rapier's default applies.
  • Sleep state is kept internal and never patched onto the authored component.
Was this helpful?Report an issueContact support

On this page