npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@driftengine/animation

v4.3.0

Published

Skeletons, clips, poses and the graphs over them: sampling as a pure function of a caller-supplied time

Readme

@driftengine/animation

Skeletons, clips, poses, and the graphs over them.

Cost: 6.2 KB gzipped on top of core. Measured by scripts/size-gate.test.mjs, which fails if it drifts more than 3% — the number is derived from the same floors that gate asserts, so a README quoting a stale one is a red suite rather than a thing somebody notices.

What it is

A Skeleton resolves a Pose into a skinning palette. A clip is sampled as a pure function of a time you supplysampleClip(clip, seconds, out) reads no clock of its own, which is what lets an animation be part of a replay rather than something layered on top of one. Above that: blendPoses and additive layers, a BlendTree, a crossfading state machine over parameters you name, two-bone IK, morph targets, retargeting by joint name, root motion, and secondary motionsampleSpring(settings, anchorAt, seconds, out) for one damped spring and sampleSpringChain for a chain of them, which is hair, a coat, an antenna or a chain reacting to what the subject is doing.

Skinning and morphing run on both backends as vertex-shader permutations, so a mesh with no rig carries none of their instructions. glTF's skins, animations and morph targets import, and .drft carries them.

Things that cost time before they were written down

A blend needs a bind pose and does not take one. blendPoses(a, b, t, out) has no bind-pose parameter — the bind pose belongs to BlendTree, which allocates its own scratch poses. Reaching for one on blendPoses is reading a paraphrase rather than the signature.

Nothing here allocates per frame, and out is why. Every sampler and every blend fills a caller-owned target. A version returning a fresh Pose reads better and allocates one per joint per frame, which is the shape AGENTS.md forbids in a hot path.

Root motion is two calls and it is a mistake to make only one of them. extractRootMotion(clip, rootJoint, fromSec, toSec, motion) answers how far the root travelled, in the root's own frame at fromSec, so you apply it as position += worldRotation * motion.translation and worldRotation = worldRotation * motion.rotation. Then stripRootMotion(clip, rootJoint, pose) pins the pose's root to the clip's value at time zero. Extract without stripping and the character moves twice, at exactly double speed, with nothing failing.

Loops are accumulated rather than subtracted, which is the part you would otherwise have to write yourself and get wrong: sampling the root at both times and subtracting answers a full stride backwards every time the clip wraps. What it gives up is a vertical bob authored on the root — that leaves the pose with everything else and becomes motion you apply, so if your height is owned by a physics controller and you ignore translation[1], the bob is in neither.

Secondary motion is sampled, not advanced, and that is what it is for. sampleSpring takes a time and an anchorAt(t, out) that must itself be a pure function of the time it is handed. It never carries state between calls, so dragging a playhead backwards gives the same pose the forward pass gave. The cost is arithmetic per sample instead of per frame: about ninety substeps of a four-multiply 2x2 for one spring, paid in whichever direction the caller is moving.

anchorAt is called at times before the one you asked for, going back springSettleSec(settings) — which is how long the spring takes to forget, -ln(1e-3) / (ζω). An anchor that reads a clock, or that answers differently on a second call for one time, breaks the property silently. If yours comes from a clip, sample the clip at the time it is handed.

Damping of exactly 0 throws, and that is not a validation nicety. An undamped spring rings forever, so no lookback is long enough to sample it purely and there is no honest answer to return. Anything above zero forgets. 1 is critical damping, which never overshoots; above 1 sags in.

A chain is sampleSpringChain, and it is not the same as calling sampleSpring per link. Each link's anchor is where the link above it actually is — lagging and overshooting — so a chain remembers longer than any of its links and needs springChainSettleSec, which is longer than springSettleSec and grows with depth. It also has to march on one grid: evaluating a link on its own would need its parent at every substep and the grandparent at every substep of each of those, which is steps^depth anchor calls for the same answer.

Where to start

sampleClip into a Pose, Skeleton.palette out of it, and applyPoseToNode for a rig you want to drive without skinning at all.

Part of DriftEngine. See docs/ARCHITECTURE.md for how the packages divide, and why some features are compiled into core on demand instead.