# Understanding the Ink Puppet Mocap System: How to Use InkPuppet.choreograph()

> Discover the Ink Puppet mocap system and learn to animate SVG stick figures using InkPuppet.choreograph() with GSAP timelines. A deterministic motion capture solution.

- Repository: [Calesthio/OpenMontage](https://github.com/calesthio/OpenMontage)
- Tags: how-to-guide
- Published: 2026-08-30

---

**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`](https://github.com/calesthio/OpenMontage/blob/main/ink-theater/ink-puppet.js)**: The main runtime implementing `InkPuppet.create()` for instantiation and `InkPuppet.choreograph()` for timeline sequencing.

- **[`ink-theater/mocap/catalog.json`](https://github.com/calesthio/OpenMontage/blob/main/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`](https://github.com/calesthio/OpenMontage/blob/main/ink-theater/README.md)**: Documentation detailing the deterministic constraints, seek-safe requirements, and the intended `create → drawIn → choreograph` workflow.

- **[`skills/pipelines/character-animation/character-design-director.md`](https://github.com/calesthio/OpenMontage/blob/main/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`](https://github.com/calesthio/OpenMontage/blob/main/ink-theater/ink-puppet.js), the function accepts the following signature:

```javascript
InkPuppet.choreograph(tl, pup, segments, options)

```

- **`tl`**: A GSAP timeline instance that controls the animation playback.
- **`pup`**: The puppet object returned by `InkPuppet.create()`, exposing methods like `setPose()` and `place`.
- **`segments`**: An array of objects, each containing `clip` (string name from [`catalog.json`](https://github.com/calesthio/OpenMontage/blob/main/catalog.json)) and `dur` (duration in seconds).
- **`options`**: An optional object containing `start`, the timeline position (in seconds) where choreography begins.

### The Choreography Pipeline

When executed, `InkPuppet.choreograph()` performs the following operations for each segment:

1. **Clip Lookup**: Resolves the named clip (e.g., `{clip: 'walk'}`) against `window.INK_CLIPS` to retrieve the baked 2D pose sequence.

2. **Timeline Integration**: Creates a GSAP tween (`tl.to(proxy, ...)`) on a proxy object with property `u`, tweening from 0 to 1 over the segment's duration.

3. **Frame Application**: On every `onUpdate` event, the system calculates local time, selects the appropriate frame from the clip array, and invokes `pup.setPose(frame)` to update joint angles.

4. **Vertical Positioning**: Updates `pup.place` to 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:

```javascript
// 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`](https://github.com/calesthio/OpenMontage/blob/main//.cursor/commands/ink-art.md):

```javascript
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:

```bash
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`](https://github.com/calesthio/OpenMontage/blob/main/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`](https://github.com/calesthio/OpenMontage/blob/main/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`](https://github.com/calesthio/OpenMontage/blob/main/ink-puppet.js).