motion-3d
Import from @pierre/ecs/modules/motion-3d.
3D velocity integrator — the 3D sibling of @pierre/ecs/modules/motion. Each
tick, pos += vel · dt. Optional boundary handling (wrap / clamp) and an
onMove hook so games that keep a separate spatial index or dirty-flag queue
can stay in sync without the motion module owning either.
Not a full physics module — for gravity + collision + grounded, see
@pierre/ecs/modules/kinematics-3d.
interface Bounds3D { width: number; height: number; depth: number }
type VelocityIntegration3DBoundary = | { mode: 'wrap'; bounds: Bounds3D } // toroidal, range [0, w) × [0, h) × [0, d) | { mode: 'clamp'; bounds: Bounds3D }; // pinned, range [0, w] × [0, h] × [0, d]
interface VelocityIntegration3DTickCtx { dtMs: number; world: EcsWorld;}
interface VelocityIntegration3DOptions<TCtx extends VelocityIntegration3DTickCtx> { name?: string; runAfter?: string[]; boundary?: VelocityIntegration3DBoundary; tag?: TagDef; // integrate only entities carrying this tag onMove?: ( ctx: TCtx, id: EntityId, prev: Readonly<Position3D>, next: Readonly<Position3D>, ) => void;}
function makeVelocityIntegration3DSystem<TCtx extends VelocityIntegration3DTickCtx>( options?: VelocityIntegration3DOptions<TCtx>,): SchedulableSystem<TCtx>;Iterates every entity that has both Velocity3DDef and Position3DDef. Skips
entities whose (vx, vy, vz) is (0, 0, 0) and entities whose final position
equals their prior position (after boundary handling) to avoid waking
downstream observers for no-op updates.
import { makeVelocityIntegration3DSystem } from '@pierre/ecs/modules/motion-3d';import { Position3DDef, Velocity3DDef } from '@pierre/ecs/modules/transform-3d';
const motion = makeVelocityIntegration3DSystem<GameTickCtx>({ boundary: { mode: 'wrap', bounds: { width: 200, height: 200, depth: 200 } },});
scheduler.add(motion);Dependencies: consumes Position3DDef / Velocity3DDef from
@pierre/ecs/modules/transform-3d; no vector helpers are duplicated here —
Vec3, vec3Normalize, and vec3ScaleToLength live in
@pierre/ecs/modules/math.
Plain velocity integration only, mirroring the 2D module: no acceleration or
forces (a rigid-body concern), no rotation integration, no gravity/collision.
Free-flight attitude control (rate-steered orientation + throttle) and
non-box (spherical) world bounds are deferred — see the motion-3d V2 entry in
the backlog; they are
game-specific shapes, not general motion primitives.
A game that uses modules/kinematics-3d should not also run this integrator
over the same bodies — kinematics already integrates its velocity, so a global
motion pass would double-integrate. Pass tag to scope the integrator to a
disjoint marker set (e.g. tag: ProjectileTag) so both can coexist in one game
— the same marker/query-filter idiom as kinematics-3d’s dynamicTag.