Documentation
    Preparing search index...

    Class PhysicsWorld

    Index
    • Builds a world for init.adapter — or for the solver init.solver names (PH-19) — and validates that the adapter can actually simulate it (§21, §33, §37).

      The options are checked by validatePhysicsWorldOptions (§85) and then resolved: gravity is widened to the engine's 3D form (Appendix A's (0, -9.81, 0) when omitted, in both dimensions — §7a puts +Y up in both), sleeping is merged over Appendix A's defaults, and determinism defaults to "same-runtime". The resolved record is what initialize hands the adapter, so the solver and the engine agree on every value.

      Parameters

      Returns PhysicsWorld

      FourError if the options are invalid (§85), if neither or both of adapter and solver are given, if solver names nothing registered (§37), if the adapter does not declare dimension among its capabilities.dimensions, or if the requested determinism tier is stronger than the adapter declares (§33, §37).

    • get determinism(): DeterminismLevel

      The §33 tier this world asked for and the adapter accepted.

      Returns DeterminismLevel

    • get disposed(): boolean

      Whether PhysicsWorld.dispose has run.

      Returns boolean

    • get gravity(): Vector3

      Resolved world gravity in m/s² (§21). The world's own vector — read it, copy it, but do not write into it: the solver received it at initialize and this object no longer decides what the solver does.

      Returns Vector3

    • get initialized(): boolean

      Whether PhysicsWorld.initialize has completed.

      Returns boolean

    • get jointCount(): number

      How many joints are registered (§28).

      Returns number

    • get joints(): IterableIterator<Joint>

      The registered joints, in registration order (§28, §33).

      Returns IterableIterator<Joint>

    • get localPlane(): ResolvedLocalPlane

      The resolved §21 simulation plane. Default is the world XY plane (origin 0, normal +Z, xAxis +X). Bodies with space: "local-plane" author their 2D pose in this frame.

      Returns ResolvedLocalPlane

    • get nodes(): IterableIterator<Node>

      The registered nodes, in registration order (§33).

      Returns IterableIterator<Node>

    • get size(): number

      How many bodies are registered.

      Returns number

    • get sleeping(): SleepingConfig

      The resolved §32 sleeping configuration, kept accessible because an adapter maps only what its solver exposes — both Rapier adapters honour enabled (setCanSleep) and apply none of the three thresholds, which have no binding at 0.19.3 — so a caller that needs to know what was asked for can still read it here.

      A threshold authored against an adapter that declares it cannot apply one warns once at construction; see PhysicsCapabilities.tuning.

      Returns SleepingConfig

    • get supportsJoints(): boolean

      Whether this world's adapter implements the joint seam at all (SolverJointAccess, WP-6.1).

      false means PhysicsWorld.addJoint will throw: the adapter can create and destroy a joint handle (§37) but cannot be asked for a joint's id, its reaction, or its reconfiguration, so the engine has no way to register one. Detected structurally — see supportsSolverJointAccess for why that rather than a capability flag.

      Returns boolean

    • get supportsLiveProperties(): boolean

      Whether this world's adapter can carry a property change into its solver after createBody — the §37 seam SolverBodyTuningAccess (PH-1 stage 2, 2026-08-07).

      Declared, not guessed, and answerable before anything is registered: a caller building a tuning UI can ask once and disable the sliders rather than discovering the limitation from a console.warn on the first drag. false does not make the writes illegal — RigidBody's setters keep accepting them and keep warning once per body per field, and removeBody + addBody still rebuilds the solver body from the changed descriptor.

      The properties this covers are rigid-body.ts's truth table plus PhysicsWorld.refreshCollider's §24/§25 fields. It is deliberately one boolean and not six: see supportsSolverBodyTuning.

      Returns boolean

    • get units(): PhysicsWorldUnits | undefined

      §40 scale factors this world converts through, or undefined when omitted (identity — internal solver numbers equal authored numbers).

      Internal solver state is SI. Use PhysicsWorld.toSiLength and friends at the authoring boundary.

      Returns PhysicsWorldUnits | undefined

    • Registers node's body and its colliders with the solver (§23, §24, §37).

      node.addComponent(new RigidBody({ type: "dynamic" }));
      node.addComponent(new Collider({ shape: { type: "circle", radius: 0.5 } }));
      world.addBody(node);

      The whole subtree rooted at node is scanned, depth-first in child insertion order and to unbounded depth, and a collider joins this body when its own §24 ancestor walk (Collider.body: the nearest RigidBody at or above its node) names this body.

      So the scan is the exact dual of the resolution a collider already performs, and it needs no second rule: an intermediate node with no body is transparent — a visual grouping node between a body and its collider changes nothing — while a descendant that carries its own RigidBody claims its own colliders, which are skipped here and registered by their own addBody(childNode) call. That is why several colliders on one body are expressed as child nodes (WP-5.2).

      The scan happens once, here. A Collider attached after this call is handed over with PhysicsWorld.addCollider, which applies the same §24 resolution to one component (PH-5); one attached and never handed over simply is not simulated.

      An authored position/rotation on the RigidBody descriptor wins; when the descriptor carried neither, the body starts at the node's local transform — which is where the author put it, and refusing to read it would drop a body at the origin the first time it is registered. Either way the solved pose is written back onto the node after the first step (§42), so the node and the solver converge immediately.

      Phase 5 reads and writes the local transform, so a body node is expected to sit at the scene root or under an identity ancestor chain; a hierarchy that scales or rotates above a body is out of scope until the §19 pipeline of Phase 7.

      Mass-from-density is the solver's derivation, so after the colliders exist the world reads getBodyMass back onto the component: a dynamic body whose mass was never authored stops reporting inverseMass === NaN. A solver that reports a non-positive mass — a body with no collider, or a static body — leaves the component alone, because §23 forbids expressing "does not simulate" as a zero mass.

      The value lands on RigidBody.derivedMass and does not author RigidBody.mass (2026-08-06): mass still reports it, toDescriptor() still omits it, and a body registered twice derives twice instead of freezing the first solver's answer. See #refreshMassProperties.

      Assigning body.type after registration changes the component and not the solver — the two would then disagree about what is being simulated. Use PhysicsWorld.setBodyControlMode to change a registered body's §22 model: it re-types the solver body in place and moves the component with it (plan P7-3), and it is what §19's control-mode transitions go through.

      Parameters

      Returns RigidBody

      the registered RigidBody component

      "world" — the frame every body is solved in unless its RigidBody declares "local-plane", which this world maps through PhysicsWorld.localPlane. Presentation frames ("screen", "viewport", "camera", "billboard") stay refused (§8). See #requireSimulationSpace.

      FourError if the world is not initialized, if node has no RigidBody, if it is already registered, if the body declares a §8 space mode this world cannot simulate, if a scanned collider has no body (§23), or if the body or a collider is invalid for this dimension (§21, §85)

    • Registers one Collider on a body this world already holds (§24, §37, §83; PH-5, 2026-08-07).

      const shield = new Node();
      shield.addComponent(new Collider({ shape: { type: "circle", radius: 1 } }));
      player.addChild(shield);
      world.addCollider(shield.getComponent(Collider)!); // …live from here on

      There is no node parameter and no body parameter: the collider's own §24 ancestor walk (Collider.requireBody) names its body, exactly as #collectColliders tests it during addBody. One source of truth means a collider cannot be attached to a body it does not belong to, and moving the node under a different body before calling this changes the answer in the one place §24 says it lives.

      The body must already be registered here. Attaching to an unregistered node is refused rather than quietly registering the body too, because addBody is the call that decides a body's pose, tracking, and mass, and inferring all three from a collider would be inventing them.

      A Collider reaching a node is a plain addComponent the world cannot observe (§6a components have no world edge), so the alternative was to re-scan every registered body's subtree every step and diff it — real per-step cost, in a world where colliders almost never appear at runtime. This says so instead. See PhysicsWorld.refreshCollider, whose explicit-by-design note is the precedent.

      The new registration is appended to its body's collider list, which keeps that list in ascending adapter collider id — the order the per-step drain and every checksum-visible iteration depend on — because adapter collider ids are monotonic and never reused, so a collider created now outranks every collider created before it.

      The body's mass is then re-read from the solver, since §23/§25 derive a mass from collider density: a derived-mass body gains this collider's contribution, and an authored-mass body keeps the mass it authored (the adapter is what decides which collider carries it — see PhysicsSolverAdapter.createCollider). Nothing is created in the solver until every check has passed, so a rejected call leaves the world exactly as it found it (§85).

      Parameters

      Returns Collider

      the registered collider, for chaining

      FourError if the world is not initialized, if the collider is already registered here, if it has no RigidBody above it (§23), if that body is not registered with this world, or if the collider is invalid for this dimension (§21, §85)

    • Registers a §28 joint with the solver (plan P6-3).

      world.addBody(wall);
      world.addBody(door);
      world.addJoint(
      new HingeJoint({
      bodyA: wall.getComponent(RigidBody)!,
      bodyB: door.getComponent(RigidBody)!,
      anchor: new Vector3(0, 1, 0),
      axis: new Vector3(0, 1, 0),
      limits: { min: 0, max: Math.PI / 2 },
      }),
      );
      1. The adapter is checked for the joint seam, and the joint for the things only a world can check: both bodies registered with this world, the joint not already registered, not broken, and — when it carries a break threshold — an adapter that can actually report reactions (plan P6-2).
      2. The joint's world-space anchors and axis are converted into each body's local frame, from the pose the solver holds right now (§28; see joints.ts for the convention and its two consequences).
      3. The resulting descriptor is validated against §28 and the world's dimension — which is where a staged type, a spherical in a "2d" world, or an axis pointing out of the plane is rejected — and only then handed to createJoint.
      4. The joint is bound to the adapter's monotonic id and appended to the registry, whose iteration order is registration order (§33).

      Nothing is created in the solver until every check has passed, so a rejected registration leaves no half-built constraint behind (§85).

      Parameters

      Returns Joint

      the registered joint, for chaining

      FourError if the world is not initialized, if the adapter has no joint seam, if either body is not registered here, if the joint is already registered or has broken, if a break threshold cannot be enforced, or if the descriptor is invalid for this dimension (§21, §28, §85)

    • Shifts every registered body's PoseTarget history one fixed step forward — PoseTarget.capturePrevious for each, in registration order (§19, plan P7-4).

      The history is what (position − previousPosition) / dt differences into the velocity an activated ragdoll inherits (§19, PhysicsWorld.setBodyControlMode), so "previous" has to mean the target one fixed step ago. Capturing inside the step would run at §39 step 6, after step 3 has written this step's target: previous would become this step's pose and every difference would be zero. The capture therefore belongs before animation, which is a different slot in the §39 order and so a different system — createPoseTargetCaptureSystem, which is the call site and which an application must register (see the module header).

      A target's history is read by velocity inheritance as well as by the blend, and inheritance is precisely what a body that is not yet blended or dynamic uses at the moment it is switched (§19's ragdoll activation). A body whose node carries no PoseTarget is skipped; nothing here validates the §19 trio, because a target with no blend is a perfectly ordinary thing to animate and the blend's own error is raised by the step that blends.

      Capturing bumps no version and touches no solver, so this is safe to call on a world that has not been initialized (a PhysicsSystem may well be registered before initialize resolves). Calling it twice in one fixed step collapses every difference to zero — see PoseTarget.capturePrevious. Allocates nothing.

      Returns void

    • The §33 per-step determinism checksum, as a uint32.

      §33 defines it exactly: "FNV-1a over each existing body's transform and velocities (sleeping bodies included), quantized to 1e-6, visited in ascending engine-assigned monotonic body id". So, per body, thirteen floats in this order — position x, y, z, rotation x, y, z, w, linear velocity x, y, z, angular velocity x, y, z — each quantized by Math.round(v * 1e6) and absorbed as its low then high uint32 word in little-endian bytes. Sleeping bodies are visited like any other; the sleep flag is not hashed, because §33 hashes transforms and velocities.

      The visit order is SolverBodyAccess.forEachBody, which is creation order and therefore ascending id: destroying a body removes it from the sequence and creating one appends, so no destruction can permute the order, and a §34 snapshot restore preserves it. Bodies that belong to the solver but not to this world's registry — there should be none — are still hashed, because §33 says "each existing body".

      The algorithm is D6's, byte for byte identical to @fourjs/diagnostics's hashFloats; it is re-implemented here because the frozen dependency matrix gives @fourjs/physics core, math, scene, and motion only. tests/world.test.ts pins the digest of a known world so the two cannot drift apart silently.

      Allocates nothing.

      Returns number

      RangeError if a body's state contains a non-finite value, which has no meaningful quantization and would silently poison a golden hash

    • Captures the solver's world with §34's validity metadata.

      The bytes are the adapter's and opaque; the envelope records the adapter's name and version so PhysicsWorld.restoreSnapshot can refuse a buffer from a different solver instead of handing it to a deserializer that would either throw obscurely or, worse, succeed.

      Returns PhysicsSnapshot

      FourError if the adapter does not implement snapshots (capabilities.snapshots === false)

    • Emits every event queued by the last PhysicsWorld.step on the node emitters §29 names, then empties the queue (§6b, §39 step 9).

      1. Events are dispatched in the adapter's drainEvents order, which §37 requires to be deterministic for a given step. The world neither sorts nor derives events — an adapter that reports collisionstay reports it itself.
      2. A collision event is emitted on bodyA's emitter first, then bodyB's, with the same payload object both times: §29 fixes no meaning to the A/B order beyond internal consistency, so re-labelling the pair per listener would be inventing information. A pair whose colliders share one body is emitted once.
      3. A trigger event is emitted on the sensor collider's emitter only — §29's sensor.on("triggerenter", …). Two overlapping sensors are reported by the adapter as two events, once from each side, so each sensor's listeners still fire.
      4. A sleep or wake event is emitted on the body. RigidBody.sleeping was already refreshed during the step, so a listener sees the state the event announces.
      5. A "jointbreak" is emitted on the joint (§28, plan P6-2), which is already destroyed and unregistered by then — a listener sees the joint in its final state, and joint.broken is true. It is emitted on the joint alone and not on the two bodies: §29's body event map is the collision and sleep vocabulary, and a joint has its own emitter to subscribe to.

      The queue is handed over before the first callback runs, so a listener may create bodies, remove bodies, or step another world without re-entering this dispatch or losing events; anything a re-entrant step queues is dispatched by that step's own dispatchEvents.

      Returns void

    • Destroys every registered joint, then every registered body — colliders first, all in reverse registration order — and finally disposes the adapter (§83).

      Joints before bodies, because a constraint refers to two bodies and nothing should be left pointing at a destroyed one; within each kind, reverse registration order, which is the mirror of how they were built.

      Idempotent and terminal: the world cannot be stepped or registered with afterwards. Nodes are untracked from the pose buffer but their components and transforms are left exactly as they were; the world owns the solver objects, not the scene. Joints are unregistered, not broken — disposal is not a §28 break.

      Returns void

    • Visits every registered body that is dynamic and awake, in registration order (§33), with its node and its world-space centre of mass (§25).

      world.forEachActiveBody((body, node, centerOfMass) => {
      body.applyForce(wind.sample(centerOfMass, body.linearVelocity, t));
      });

      This is the iteration a §39 step-5 force generator walks — ForceFieldSystem is the one this package ships (§26, §27) — and it is public because "the bodies a per-step force can actually move" is a question every such generator asks and none of them should answer by re-deriving it:

      • dynamic (§22): a force on a static or kinematic body does nothing by definition, so visiting one would be work whose result is discarded;
      • awake (§32): a non-zero force wakes a body — which is why step skips zero-magnitude accumulations — so a generator that visited sleeping bodies would wake every body every step and §32 would stop meaning anything. Waking a settled body is RigidBody.wake(), an explicit command, and deliberately not a side effect of ambient wind.

      The centre of mass, not the transform origin, because it is where §26's applyForce acts: for an off-centre or compound body (§24) the origin can be anywhere, including outside the shape, and a generator that sampled there would describe a different place from the one it pushes. It is read from the solver (SolverBodyAccess.getBodyCenterOfMass) after mass-property resolution, into one shared vector that is overwritten on every visit — copy it if you need it after the callback returns (§7b).

      Allocates nothing. The visitor may apply forces and impulses freely; adding or removing bodies during the walk is not supported — do it afterwards, as with any iteration over the registration order.

      Parameters

      Returns void

    • Visits every registered body that is dynamic and sleeping, in registration order (§33), with the same shared centre-of-mass vector PhysicsWorld.forEachActiveBody uses.

      The default force-generator walk skips sleepers so ambient wind cannot defeat §32. This is the other half of that filter: a field that opts into wakesSleepingBodies walks only the bodies the default pass hid. Static and kinematic bodies are still skipped — a force on those types is discarded by definition (§22).

      Allocates nothing. Adding or removing bodies during the walk is not supported.

      Parameters

      Returns void

    • Authored length from metres. Identity when units are omitted.

      Parameters

      • meters: number

      Returns number

    • Authored mass from kilograms. Identity when units are omitted.

      Parameters

      • kilograms: number

      Returns number

    • The RigidBody registered for node, or undefined.

      Parameters

      Returns RigidBody | undefined

    • The solver handle of node's body, or undefined when it is not registered here — below the stable API (§20, §37).

      const handle = world.getBodyHandle(node);
      if (handle !== undefined) {
      // `world.adapter` is a SolverBodyAccess on both Rapier adapters:
      world.adapter.setBodyVelocities(handle, velocity, spin, true);
      }

      §20's promise is that common tasks need no solver-specific code, not that uncommon ones are forbidden. A RigidBody carries authored state that reaches the solver once, at createBody (see rigid-body.ts's module header): re-tuning damping, gravity scale, or mass on a body that is already simulating has no route through the component. Until the §37 seam widens, the honest answer is a handle plus a documented warning, not a setter that quietly does nothing.

      A handle taken from here is opaque and unforgeable (types.ts), and everything below this line is the adapter's contract rather than this package's:

      • it is valid only while the body is registered with this world — a removeBody, a re-registration, or dispose() invalidates it, and using a stale handle is the adapter's error to raise;
      • what a write does is the solver's business: PhysicsWorld does not see it, does not mirror it onto the component, and cannot keep §33's checksums comparable across runs that did it differently;
      • a write that changes the simulation makes the component's mirrors (mass, damping, gravityScale) stale in the other direction. The component is not re-read from the solver except for velocities and the sleep flag.

      Prefer the supported routes where they exist: §26's force and impulse commands, PhysicsWorld.setBodyControlMode for §22 types, and removeBody + addBody to rebuild a body from a changed descriptor.

      Parameters

      Returns PhysicsBodyHandle | undefined

    • The solver handle of a registered Collider component, or undefined — below the stable API, with every caveat PhysicsWorld.getBodyHandle states (§24, §37).

      The collider must have been registered by this world, which happens when addBody scans the body's subtree or when PhysicsWorld.addCollider hands one over afterwards: a Collider on a node this world does not hold has no handle to give. §25's rolling and spinning friction are the motivating case — no shipped solver applies them (see PhysicsCapabilities.tuning), and a caller who needs a solver-specific equivalent needs the handle to reach it.

      Parameters

      Returns PhysicsColliderHandle | undefined

    • Whether node is registered with this world.

      Parameters

      Returns boolean

    • Whether joint is registered with this world.

      Parameters

      Returns boolean

    • Creates the solver's world (§37), awaiting the adapter's initialize.

      A WebAssembly-backed solver loads its module here (plan P5-1), which is why this is asynchronous even though nothing in the physics package is. Nothing may be registered or stepped before it resolves.

      Returns Promise<void>

      FourError if called twice or after dispose.

    • Everything overlapping a ball centred at center (§30 overlapSphere).

      §21's parallel naming: in a "2d" world this is a circle overlap and in a "3d" world a sphere overlap — one call, the shape the dimension implies.

      Parameters

      Returns WorldQueryHit[]

    • Casts a ray and returns the hits with component references (§30).

      const hits = world.raycast({ origin: new Vector2(0, 0), direction: new Vector2(1, 0) });
      

      §30's filter semantics — groups, masks, ignored bodies, first/all, sorting, sensor inclusion, custom predicates — live in the query record and are applied by the adapter through the package's own passesQueryFilter, so every solver answers a query identically. Hits on colliders this world has not registered are dropped: the world can only report components it knows.

      Parameters

      Returns WorldRaycastHit[]

    • Re-reads a registered Collider's §25 material and §24 filter at the next fixed step (PH-1 stage 2).

      ice.friction = 0.02;
      world.refreshCollider(ice); // …in force from the next step on

      RigidBody.mass and its neighbours are accessors, so the component sees every write and queues it by itself. Collider.friction, restitution, density, sensor, collisionGroups, and collisionMask are plain public fields — §24 and §25 describe them as data — and a plain field assignment is unobservable. The alternatives were to shadow-copy six values per collider and diff them every step, which is real per-step cost for something that changes almost never, or to say so. This says so.

      Marking is idempotent, costs no allocation, and is served in ascending collider id within ascending body id (§33). A collider this world does not hold is refused, rather than silently doing nothing: passing the wrong one is the mistake this call exists to make visible.

      On an adapter without the §37 property seam (PhysicsWorld.supportsLiveProperties false) the request is accepted and consumed but reaches no solver; the collider's values still take effect on the next removeBody + addBody. Nothing is warned here — the world-level answer is a property a caller can read, and warning per collider would print once per collider in a scene-wide misconfiguration.

      Parameters

      Returns void

      FourError if collider is not registered with this world (§24, §85)

    • Removes node's body and its colliders from the solver, in reverse creation order (§37, §83).

      Returns whether the node was registered, so teardown paths may call it unconditionally. The components are left intact and can be registered with another world; only the solver objects and this world's bookkeeping go away. Events already queued for the removed body are still dispatched — they describe a step that happened.

      Joints go first. Any joint this world holds that names the removed body is destroyed before the body is (§83's ordering, and the only order that leaves no constraint pointing at a dead body). Those joints are not broken: Joint.broken means "exceeded its break threshold", and a body that was unregistered broke nothing — they are simply unregistered and may be added again once their bodies are.

      Parameters

      Returns boolean

    • Removes one registered Collider from the solver, leaving its body simulating (§24, §37, §83; PH-5, 2026-08-07).

      Returns whether it was registered, so teardown paths may call it unconditionally — the shape removeBody and removeJoint already have. It is deliberately not the throwing refusal PhysicsWorld.refreshCollider uses: a refresh that silently did nothing would be invisible, whereas this call hands the answer back.

      The Collider component survives and may be registered again, here or elsewhere; only the solver object and this world's bookkeeping go away. A pending PhysicsWorld.refreshCollider request on it is dropped with it, so no later step pays for a scan that can find nothing.

      The adapter re-establishes the body's mass properties as it destroys the collider — that is precisely the "body survives" case PhysicsSolverAdapter.destroyCollider exists for — and the body's mass is then re-read onto the component. A body whose mass was authored keeps it, whatever the adapter had to do internally to hold it; a body whose mass was derived loses this collider's contribution, and one left with no collider at all loses its derived mass entirely (RigidBody.mass reports undefined again), because there is nothing left to derive one from.

      Parameters

      Returns boolean

    • Removes a joint from the solver (§28, §83).

      Returns whether it was registered, so teardown paths may call it unconditionally. The joint object survives and can be registered again — removal is not breakage, and nothing about it is marked broken.

      Parameters

      Returns boolean

    • Restores a snapshot taken from this adapter and this adapter version (§34).

      This world's node ↔ body maps survive the round trip untouched, and they may: §34 requires body identity and ordering to survive a restore, and §37's ids are monotonic, so the id a registration recorded still names the same body afterwards. What a restore does change is the state of those bodies — poses, velocities, sleep — which the next step publishes onto the nodes.

      Parameters

      Returns void

      FourError if the adapter does not implement snapshots, or if the snapshot's adapterName/adapterVersion do not match this adapter's — §34: "a replay refuses to run against a different solver"

    • Writes node's angular velocity into the solver immediately (PH-1).

      Same §42 gate as PhysicsWorld.setLinearVelocity: "physics" authority, "dynamic" or "kinematic-velocity" only. A number is the scalar rate about +Z (plan P5-3). Angles stay radians and times stay seconds — only the linear half of a unit scale applies, and not here.

      Parameters

      Returns boolean

      whether the solver was written

    • Switches a registered body between §22's simulation models in place — §19's "move between animated, kinematic, and physical control" (§110, plan P7-3).

      // A door the animation drove, handed over to the solver when it is hit:
      world.setBodyControlMode(door, "dynamic", { inheritVelocityFrom: target });

      The body is not destroyed and re-created. It keeps its solver handle, its monotonic id, its position in the checksum's iteration order, its colliders, its mass properties, and its pose — all that changes is which of §22's models the solver runs it under, plus (optionally) its velocities. Two otherwise identical runs whose only difference is a mid-run switch therefore produce the same body set in the same order, which is what lets a control-mode change appear inside a §33 replay at all. Removing and re-adding the body would mint a fresh id and re-key every registry against it.

      1. the solver, through SolverBodyAccess.setBodyType;
      2. the RigidBody component's type, so body.type and inverseMass report what is actually being simulated;
      3. the registration's own record of the type, which is what the fixed-step pipeline branches on — so the §22 kinematic feed and the §42 transform write follow the new type from the very next step: a body switched to "kinematic-position" starts being fed the node's transform as a target, and one switched to "dynamic" starts publishing the solved pose;
      4. §43 pose tracking, which follows dynamic-ness exactly as PhysicsWorld.addBody sets it.

      A "dynamic" body needs a mass, so a switch to "dynamic" is refused unless one is authored (RigidBody.mass) or derivable — that is, unless the solver already reports a positive mass for the body, which it does whenever the body carries a collider with a density (§23, §25). Activating a ragdoll limb that has no collider would otherwise produce a massless dynamic body: Rapier leaves such a body motionless (measured at 0.19.3), which is a silently wrong simulation rather than an error. An authored mass is re-validated against the new type as well, by the same validateMass the component's setter uses.

      Nothing is written to the solver until every check has passed (§85).

      With inheritVelocityFrom, the body's velocities are seeded from one fixed step of the target's own motion, so an activated body continues the animation's trajectory instead of dropping from rest:

      dt      = the fixed step (see BodyControlModeOptions.fixedDeltaSeconds)
      linear  = (position − previousPosition) / dt                     [m/s]
      
      qDelta  = rotation · previousRotation⁻¹        (world-frame delta, unit)
      qDelta  ← −qDelta   when qDelta.w < 0          (shortest arc, plan D8)
      s       = |(qDelta.x, qDelta.y, qDelta.z)|     = |sin(θ/2)|
      θ       = 2 · atan2(s, qDelta.w)                                 [rad]
      angular = (qDelta.xyz / s) · (θ / dt)                            [rad/s]
                = 0 when s = 0 (no rotation to differentiate)
      

      atan2 and not 2·asin(s): the arcsine form loses the difference between a rotation and its supplement past a quarter turn per step, and atan2 stays accurate for the small deltas one fixed step actually produces. The sign flip is the same shortest-arc rule Quaternion.slerp documents — q and −q are the same rotation, and only one of them is the short way round, so without it a target that crossed the quaternion sign boundary would inherit a spin of nearly 2π/dt in the wrong direction.

      Left-multiplying by the inverse of the previous rotation puts the delta in the world frame, which is the frame §23's angularVelocity is stated in (qDelta · previous = current). In a "2d" world a pure-Z target produces an exactly pure-Z result — the Hamilton product's x and y terms cancel to zero — so §21's planar check passes without a fudge; a target carrying off-plane rotation is rejected by that check, as it should be.

      Inheritance is accepted only for "dynamic" and "kinematic-velocity", the two types that have a velocity: a "static" or "kinematic-position" body ignores velocity entirely, so seeding one would be a command that silently does nothing.

      The seeded velocities are written through setBodyVelocities, so they are solver state immediately and are published back onto the component by the next step (§23). Allocates nothing.

      Parameters

      Returns RigidBody

      the RigidBody now running under type

      FourError if the world is not initialized, if node is not registered here, if §23's mass rule forbids the new type, if inheritance was asked for on a type that has no velocity, or if it was asked for with no usable fixed delta

    • Writes node's linear velocity into the solver immediately (PH-1).

      §42-style who-wins: allowed only when node.transformAuthority === "physics" (the solver owns the body). Any other authority refuses the write and warns once via warnAuthorityConflict from @fourjs/motion. "static" bodies refuse — they have no velocity. "dynamic" and "kinematic-velocity" accept; "kinematic-position" refuses the same way as static (velocity is not an input of that model).

      v is in authored world units per second. When PhysicsWorld.units is set it is converted to SI for the solver; the component stores the authored value and the publish pass converts back.

      Parameters

      Returns boolean

      whether the solver was written

    • Advances the simulation by exactly deltaSeconds (§10, §37, §39).

      Runs steps 1–6 of the pipeline in the module header and leaves the step's events queued; call PhysicsWorld.dispatchEvents afterwards, which is what PhysicsSystem does once every world has stepped (§39 step 9).

      deltaSeconds is seconds (§7a) and comes from the application clock's fixed delta — the accumulator, the sub-step clamp, and droppedTime are §10's business, not the world's. Nothing here reads a clock (§33).

      The scene→solver pass now opens with PhysicsWorld.refreshCollider's and RigidBody's pending property writes, ahead of §26's forces and the kinematic feed, so that a force applied in the same frame as a mass change acts on the new mass. The drain walks #bodiesByNode, which is registration order and therefore ascending solver id (ids are monotonic and a re-added body is appended), and each body's colliders in ascending id within it — §33's ordering rule, applied to a second stream of writes.

      A body nobody wrote to costs one integer comparison and no solver call, and a world in which refreshCollider was never called skips the collider scan entirely: a quiet world therefore issues exactly the solver-call sequence it issued before this seam existed, which is why no §33 golden moved.

      Allocates nothing in steady state: the per-body loops write into the components' and nodes' own vectors, and the event queue is reused unless the step actually produced events.

      Parameters

      • deltaSeconds: number

      Returns void

    • Moves a registered body to a new pose without deriving the motion that got it there — §37's "teleports", which had no stable-API route until now (PH-1 stage 2).

      world.teleport(player, spawnPoint); // rotation unchanged
      

      A teleport sets the pose and nothing else: velocities are untouched, and no contact response is derived for the swept volume, so a body teleported into a wall resolves the overlap from rest rather than arriving with the momentum of the jump. To push things on the way, drive a "kinematic-position" body by writing its node transform, which the kinematic feed turns into a target pose the solver interpolates towards (§22).

      The write reaches the solver at the moment of the call, exactly as PhysicsWorld.setBodyControlMode's does, because a teleport is a discrete authoring act with no accumulation to buffer and no meaning for "twice in one step" beyond "the last one wins". §6b's rule about not doing physics work during event dispatch is unaffected: this is not dispatch.

      The node's own transform is not written here. Under "physics" authority (§42) the publish pass writes it from the solver after the next step, which is the one writer §42 permits; writing it here as well would make this method a second author of the same transform.

      Parameters

      • node: Node
      • position: Vector3Input
      • Optionalrotation: RotationInput

        the new orientation, or undefined to keep the current one

      • Optionalwake: boolean

        whether to wake a sleeping body (§32), default true

      Returns void

      FourError if node is not registered with this world (§85)

    • Metres from an authored length. Identity when PhysicsWorld.units is omitted.

      Parameters

      • value: number

      Returns number

    • Kilograms from an authored mass. Identity when units are omitted.

      Parameters

      • value: number

      Returns number