Shifts every tracked node's pose one step forward: previous ← current,
then current ← node.transform. Allocates nothing.
Call it once per fixed step, after every simulation system has run —
§39 step 10, "state snapshot". createSnapshotSystem wraps it in a
SimulationSystem that does exactly that (plan D5). Calling it twice in
one step would throw the previous pose away and flatten the interpolation;
calling it from a render frame would do the same and couple render rate
to simulation state, which §42 forbids.
On a node's first capture both poses are set to the transform's pose,
so the pair is previous === current and interpolation is constant for
that step. Anything else would interpolate from the pose the node happened
to have when it was tracked, which is not a simulation step and would
render as a jump.
root restricts the capture to the tracked nodes in that subtree (root
itself included), depth-first in insertion order; tracked nodes outside it
keep the poses they had. Omit it to capture every tracked node, which is
the normal case — pass a root only when two subtrees are stepped by
different schedulers.
Optionalroot: NodeUntracks every node.
Writes node's interpolated render pose at alpha into outPosition and
outRotation — §43's lerp for position, slerp for rotation — and
returns true. Allocates nothing.
Returns false and leaves both outputs untouched when node is not
tracked, so a caller may interpolate optimistically and fall back to
node.transform on false without first clearing its scratch objects.
alpha is the fraction of a fixed step already elapsed —
TimeState.interpolationAlpha (§10). It is clamped to [0, 1]:
extrapolating past the current pose would render a position the simulation
has not produced. A non-finite alpha clamps to 0 rather than throwing;
this runs per node per frame on the presentation path, where the previous
pose is always a safe answer and an exception is not (decision, WP-2.6).
The outputs must be scratch objects owned by the caller, never a
Transform's position/rotation — see this module's header. Passing a
transform's members would write a presentation value into simulation state
(§42, §43) and bump that transform's version every frame.
Starts interpolating node, seeding both stored poses from its current
transform.position/transform.rotation. Returns this.
Seeding matters: a tracked node is renderable immediately, at any alpha, and there is never a window in which the buffer holds an uninitialized — or worse, a stale-from-a-previous-tracking — pose to interpolate from. The node's first PoseBuffer.capture then re-seeds both poses from the transform again (see that method), so whatever the node did between being tracked and the first capture is not mistaken for one step of motion.
Tracking an already-tracked node is a no-op and keeps its stored poses —
re-tracking is not a way to reset them (decision, WP-2.6: track is called
from component attach paths that may run more than once, and silently
discarding a step of motion there would show up as a one-frame jump).
Untrack and track again to reset.
Stops interpolating node and drops its stored poses. Returns whether it
was tracked; untracking an untracked node is a no-op, so teardown paths may
call it unconditionally.
The engine's previous/current pose store (§37, §43).
Lifecycle of one node:
The buffer holds strong references to the nodes it tracks (it has to iterate them, which a
WeakMapcannot do), so a tracked node is kept alive until it is untracked or PoseBuffer.clear is called. Whoever tracks a node owns that call — normally the same component that made the node move.