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).
The solver this world drives (§37).
The §33 tier this world asked for and the adapter accepted.
The §21 dimension, fixed at construction.
Whether PhysicsWorld.dispose has run.
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.
Whether PhysicsWorld.initialize has completed.
How many joints are registered (§28).
The registered joints, in registration order (§28, §33).
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.
The registered nodes, in registration order (§33).
The exact PhysicsWorldOptions record handed to adapter.initialize (§37).
The events drained from the last PhysicsWorld.step and not yet dispatched, in adapter order. Emptied by PhysicsWorld.dispatchEvents.
How many bodies are registered.
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.
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.
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.
§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.
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.
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.
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
addBody uses, not a second oneThere 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.
refreshCollider and for the same reasonA 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).
the registered collider, for chaining
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 },
}),
);
joints.ts for the convention and its two consequences).spherical in a "2d"
world, or an axis pointing out of the plane is rejected — and only then
handed to createJoint.Nothing is created in the solver until every check has passed, so a rejected registration leaves no half-built constraint behind (§85).
the registered joint, for chaining
Shifts every registered body's PoseTarget history one fixed step forward
— PoseTarget.capturePrevious for each, in registration order (§19, plan
P7-4).
stepThe 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.
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.
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.
Emits every event queued by the last PhysicsWorld.step on the node emitters §29 names, then empties the queue (§6b, §39 step 9).
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.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.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.RigidBody.sleeping
was already refreshed during the step, so a listener sees the state the
event announces."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.
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.
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:
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.
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.
Authored length from metres. Identity when units are omitted.
Authored mass from kilograms. Identity when units are omitted.
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:
removeBody, a re-registration, or dispose() invalidates it, and using
a stale handle is the adapter's error to raise;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;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.
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.
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.
Everything overlapping a box centred at center (§30 overlapBox).
A rectangle in a "2d" world — where only halfExtents.x and
halfExtents.y are read, since there is no third axis — and a box in a
"3d" world (§21).
Optionalrotation: RotationInputOptionaloptions: QueryOptionsEverything 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.
Optionaloptions: QueryOptionsEverything containing point (§30 world.pointQuery).
Optionaloptions: QueryOptionsCasts 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.
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.
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.
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.
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.
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.
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.
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.
SolverBodyAccess.setBodyType;RigidBody component's type, so body.type and inverseMass
report what is actually being simulated;"kinematic-position" starts being fed the node's transform as a target,
and one switched to "dynamic" starts publishing the solved pose;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.
Optionaloptions: BodyControlModeOptionsthe RigidBody now running under type
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.
whether the solver was written
Sweeps a shape through the world (§30). See PhysicsWorld.raycast.
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.
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.
Optionalrotation: RotationInput
the new orientation, or undefined to keep the current one
Optionalwake: boolean
whether to wake a sleeping body (§32), default true
Metres from an authored length. Identity when PhysicsWorld.units is omitted.
Kilograms from an authored mass. Identity when units are omitted.
Builds a world for
init.adapter— or for the solverinit.solvernames (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 whatinitializehands the adapter, so the solver and the engine agree on every value.