Understanding the Ink Puppet Mocap System: How to Use InkPuppet.choreograph()
The Ink Puppet mocap system is a deterministic motion-capture engine that animates SVG stick-figure characters using pre-converted BVH clips sequenced through the InkPuppet.choreograph() GSAP timeline API.
The Ink Puppet mocap system drives character animation in the OpenMontage project's Ink Theater engine, providing a declarative JavaScript API for choreographing hand-drawn stick figures with real-world motion data. By abstracting complex joint mathematics into named clip references, developers and AI agents can create repeatable, seek-safe animations without manual pose tuning according to the calesthio/OpenMontage source code.
What Is the Ink Puppet Mocap System?
Ink Puppet is a lightweight runtime living inside the Ink Theater sub-project that specializes in "doodle" character animation. Unlike complex 3D rigging systems, it operates on 2D SVG stick figures composed of limbs, heads, and spines, applying per-frame joint poses derived from motion-capture data. The system enforces a deterministic architecture where animations are pure functions of time, ensuring that scrubbing any GSAP timeline reproduces the exact same character pose.
Core Architecture and File Structure
The implementation spans several key files in the ink-theater/ directory:
-
ink-theater/ink-puppet.js: The main runtime implementingInkPuppet.create()for instantiation andInkPuppet.choreograph()for timeline sequencing. -
ink-theater/mocap/catalog.json: A JSON registry of 12 built-in motion-capture clips (e.g.,walk,wave,jump,dance_spin) that serve as the only valid identifiers for the choreograph API. -
ink-theater/README.md: Documentation detailing the deterministic constraints, seek-safe requirements, and the intendedcreate → drawIn → choreographworkflow. -
skills/pipelines/character-animation/character-design-director.md: Agent-level instructions defining when AI assistants should invoke Ink Puppet for hand-drawn character scenes.
All motion data originates from BVH files processed by mocap/bvh2clip.mjs and stored in the global window.INK_CLIPS object at runtime.
How InkPuppet.choreograph() Works
The InkPuppet.choreograph() method serves as the primary sequencing interface, binding pre-converted motion-capture data to a GSAP timeline. This declarative approach prevents direct joint manipulation, ensuring motion fidelity and timeline seek-safety.
Method Signature and Parameters
As defined in ink-theater/ink-puppet.js, the function accepts the following signature:
InkPuppet.choreograph(tl, pup, segments, options)
tl: A GSAP timeline instance that controls the animation playback.pup: The puppet object returned byInkPuppet.create(), exposing methods likesetPose()andplace.segments: An array of objects, each containingclip(string name fromcatalog.json) anddur(duration in seconds).options: An optional object containingstart, the timeline position (in seconds) where choreography begins.
The Choreography Pipeline
When executed, InkPuppet.choreograph() performs the following operations for each segment:
-
Clip Lookup: Resolves the named clip (e.g.,
{clip: 'walk'}) againstwindow.INK_CLIPSto retrieve the baked 2D pose sequence. -
Timeline Integration: Creates a GSAP tween (
tl.to(proxy, ...)) on a proxy object with propertyu, tweening from 0 to 1 over the segment's duration. -
Frame Application: On every
onUpdateevent, the system calculates local time, selects the appropriate frame from the clip array, and invokespup.setPose(frame)to update joint angles. -
Vertical Positioning: Updates
pup.placeto adjust the figure's vertical offset, maintaining ground contact or simulating jump arcs based on the motion data.
Because pose calculation depends solely on the proxy value u, the animation remains deterministic and seek-safe—dragging the timeline scrubber immediately updates the character to the correct pose without requiring playback from the start.
Practical Implementation Examples
Creating and Choreographing a Stick Figure
The standard workflow involves three stages: instantiation, self-drawing reveal, and motion choreography:
// 1. Mount the puppet to an SVG group
var p = InkPuppet.create(document.getElementById('scene'), {
cx: 960, // Horizontal center on canvas
ground: 902, // Y-coordinate for ground plane
boil: 'boil' // Optional hand-drawn texture filter
});
// 2. Initialize the master timeline
var timeline = gsap.timeline({paused: true});
// 3. Animate the drawing of limbs (self-reveal)
p.drawIn(timeline, { start: 0.4 });
// 4. Sequence multiple mocap clips
InkPuppet.choreograph(timeline, p, [
{ clip: 'walk', dur: 4 }, // Walk for 4 seconds
{ clip: 'wave', dur: 3 }, // Wave for 3 seconds
{ clip: 'dance_spin', dur: 5 } // Spin dance for 5 seconds
], { start: 3.7 }); // Begin after draw-in completes
timeline.play();
Agent-Driven Animation Workflows
AI coding assistants can generate Ink Puppet code through template commands. When processing a request like "make a doodle character that walks and waves," the agent expands the /ink-art command into the following concrete API calls as specified in /.cursor/commands/ink-art.md:
var p = InkPuppet.create(mount, { cx: 960, ground: 902 });
p.drawIn(tl, { start: 0.4 });
InkPuppet.choreograph(tl, p, [
{ clip: 'walk' },
{ clip: 'wave' }
], { start: 2.6 });
This template ensures consistent implementation across different AI agents (Cursor, Claude, Codex) while adhering to the declarative constraints of the mocap system.
Extending the Motion Library
To add custom motion beyond the 12 built-in clips, convert BVH files using the utility script:
node ink-theater/mocap/add-motion.mjs backflip 05_20 dance "a backflip"
This command processes the BVH data, exports a compact 2D pose sequence, and registers the new clip name in window.INK_CLIPS, making it immediately available for InkPuppet.choreograph() without modifying the core library.
Summary
- Ink Puppet is a deterministic 2D mocap system in OpenMontage that animates SVG stick figures using pre-converted BVH clips stored in
window.INK_CLIPS. InkPuppet.choreograph()accepts a GSAP timeline, puppet instance, and segment array to sequence named motion clips (e.g.,walk,jump) declaratively.- The system forbids manual joint tuning; agents must reference only clip names from
ink-theater/mocap/catalog.json. - All animations are seek-safe because poses are pure functions of time computed via proxy tweening in GSAP.
- New motions are added via
mocap/add-motion.mjs, immediately extending the available repertoire for choreographing.
Frequently Asked Questions
How does InkPuppet.choreograph() maintain timeline synchronization?
The method leverages GSAP's proxy tweening mechanism, animating a normalized value u from 0 to 1 across each segment duration. On every onUpdate event, it calculates the exact frame index from window.INK_CLIPS and calls pup.setPose(), ensuring the character pose is always a deterministic function of the timeline's current time. This architecture allows for instantaneous timeline scrubbing without drift or accumulated errors.
What motion-capture clips are available by default?
The system ships with 12 pre-converted clips defined in ink-theater/mocap/catalog.json, including walk, wave, jump, and dance_spin. These are the only valid identifiers for the clip property in choreograph segments; the system explicitly forbids hand-tuning of motion to guarantee repeatable animation quality.
Can I manually adjust individual joint angles during a choreographed sequence?
No. The Ink Puppet architecture is intentionally declarative and restricts direct joint manipulation to the internal setPose() method. Animators and agents must work exclusively through named clip references in InkPuppet.choreograph(), which guarantees that all motion adheres to the pre-baked, physically consistent BVH data and maintains the system's deterministic guarantees.
How do I add custom BVH motion files to the system?
Use the ink-theater/mocap/add-motion.mjs Node.js script to convert BVH files into the compact 2D format required by the runtime. Executing node add-motion.mjs [clipName] [bvhFile] [category] [description] automatically registers the new motion in the global INK_CLIPS registry, making the name available immediately in InkPuppet.choreograph() calls without requiring changes to ink-puppet.js.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →