Claude Skill

motion-pipeline

CPU-only motion data processing pipeline for game animation: BVH import, contact detection, root decomposition, motion blending, FABRIK IK. No GPU required.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download notque-vexjoy-agent-skills_game_motion-pipeline-8ad6845.zip · 3 KB
Part of notque/vexjoy-agent — 69 skills

Install

skills CLI npx skills add https://github.com/notque/vexjoy-agent/tree/main/skills/game/motion-pipeline
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install notque-vexjoy-agent@llmmart
Git git clone https://github.com/notque/vexjoy-agent.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole notque/vexjoy-agent collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Motion Pipeline Skill

CPU-only motion data processing pipeline for game animation, inspired by Meta's ai4animationpy framework (CC BY-NC 4.0). All operations run on numpy and scipy with no GPU or PyTorch required.

Why standalone implementations?

ai4animationpy's Math/Tensor.py imports torch unconditionally at the top level, which propagates through every module (Animation, Import, IK, Math). This means zero ai4animationpy modules are importable without PyTorch installed. The standalone implementations in scripts/motion-pipeline.py replicate the key algorithms from their source code using only numpy + scipy.

Environment setup

# Create venv (one-time)
python3 -m venv /home/feedgen/vexjoy-agent/motion-pipeline-env/

# Install CPU-only deps
motion-pipeline-env/bin/pip install numpy scipy pygltflib Pillow

# Verify
motion-pipeline-env/bin/python -c "import numpy; import scipy; import pygltflib; print('OK')"

The venv is gitignored. The skill documents setup; it does not commit the venv.

Commands

All commands output JSON to stdout. Errors go to stderr with exit code 1.

import-bvh

Parse a BVH mocap file and print a motion summary.

motion-pipeline-env/bin/python scripts/motion-pipeline.py import-bvh FILE \
  [--scale 0.01]   # scale cm->m for CMU/Mixamo files

Output fields: name, num_frames, num_joints, framerate, total_time_seconds, bones[], root_trajectory (x/y/z range).

extract-contacts

Detect ground contact frames per bone (foot, hand) using height + velocity thresholds. Replicates ContactModule.GetContacts() from ai4animationpy.

motion-pipeline-env/bin/python scripts/motion-pipeline.py extract-contacts FILE \
  --bones LeftFoot RightFoot \
  --height 0.1 \
  --vel 0.5

Output: { "bones": { "<name>": { "contact_frames": [...] } }, "total_frames": N }.

decompose

Split motion into root trajectory (WHERE + HOW) and per-joint local Euler angles (POSE). Implements the RootModule / MotionModule decomposition pattern.

motion-pipeline-env/bin/python scripts/motion-pipeline.py decompose FILE \
  --hip Hips

Output: root_trajectory.positions[], root_trajectory.velocities[], root_trajectory.facing_directions[], per_joint_euler_zyx_degrees{}.

First 5 frames shown in stdout; full data requires piping to a file.

blend

Blend two BVH clips at a fixed alpha using SLERP rotations and LERP positions. Clips must share the same bone hierarchy.

motion-pipeline-env/bin/python scripts/motion-pipeline.py blend FILE_A FILE_B \
  --alpha 0.5

Output: summary of the blended motion.

solve-ik

Run FABRIK inverse kinematics on a bone chain at a single frame.

motion-pipeline-env/bin/python scripts/motion-pipeline.py solve-ik FILE \
  --chain Hips:LeftFoot \
  --target 0.2,0.05,0.3 \
  --frame 10

Output: chain[], target[], initial_positions[], solved_positions[], end_effector_error (metres).

generate-move-ts

Convert a BVH mocap file into a TypeScript MoveFrame function compatible with road-to-aew's wrestlingMoves.ts interface. Outputs keyframe-interpolated TypeScript to stdout (and optionally a file).

motion-pipeline-env/bin/python scripts/generate-move-ts.py BVH MOVE_NAME \
  [--scale 0.01] \
  [--contact-bones LeftToeBase RightToeBase LeftHand RightHand] \
  [--num-keyframes 12] \
  [--hip-bone Hips] \
  [--output path/to/output.ts]
Argument Default Purpose
BVH — Path to .bvh mocap file
MOVE_NAME — Kebab-case name (e.g. roundhouse-kick) used in TS identifiers
--scale 0.01 Position scale; 0.01 converts cm→m for CMU/Mixamo files
--contact-bones LeftToeBase RightToeBase LeftHand RightHand Bones used to detect the impact window
--num-keyframes 12 Keyframe count in the output array (min 2)
--hip-bone Hips Root bone name for trajectory extraction
--output stdout only Write TS to this file path in addition to stdout

Implementation note: The script imports motion-pipeline.py as a module via importlib rather than calling it as a subprocess. This bypasses the 5-frame truncation applied by the decompose CLI command, giving access to all frames.

Output structure:

// Generated from roundhouse-kick.bvh on 2026-04-13
// Keyframes: 12, Impact window: 0.45-0.55
const ROUNDHOUSE_KICK_KEYFRAMES = [...] as const;

export function getRoundhouseKick(progress: number): MoveFrame {
  // keyframe lookup + linear interpolation
  // isImpact based on detected contact window
  return { attacker, defender, isImpact };
}

The attacker's offsetX/Y/Z are root trajectory positions normalized to start at origin. Rotations are in radians (converted from the BVH's Euler ZYX degrees). The defender reaction is computed procedurally: pushed backward at impact, eases to mat post-impact.

Impact detection: The script finds the first run of 3+ consecutive contact frames across the specified bones. For strike moves, this captures the moment of hit. For walking/idle clips (feet always down), the window will be frame-0 and isImpact will be nearly never true — this is correct behavior.

Validation: The script prints a summary to stderr including trajectory range, impact window, and a structural syntax check. Exit code 1 if validation fails.

Data architecture pattern

The decomposition from ai4animationpy becomes a design contract for all game animation work:

Animation State
  root_trajectory   -- WHERE (position, velocity, facing direction)
  per_joint_euler   -- HOW (local pose in ZYX Euler degrees)
  contact_frames    -- WHAT (contact states for feet, hands)
  [guidance]        -- WHY (intent; handled at game engine layer)

This separation enables:

  • Different movement speeds without distorting body pose
  • Contact-driven game events (damage triggers, sound, VFX)
  • AI/input guidance independent of motion playback

Source reference: ai4animationpy modules adopted

ai4animationpy module This script equivalent Notes
Import/BVHImporter.BVH load_bvh() Same parsing logic; scipy replaces torch
Animation/Motion Motion dataclass numpy-only; no torch backend
Animation/ContactModule extract_contacts() Height + velocity criterion identical
Animation/RootModule decompose() root section FK decomposition via matrix inverse
Animation/MotionModule decompose() joint section Local Euler extraction via scipy
IK/FABRIK solve_ik_fabrik() Algorithm identical; no Actor dependency

Integration points

Downstream agent Data consumed
rive-skeletal-animator per_joint_euler_zyx_degrees from decompose
pixijs-combat-renderer contact_frames from extract-contacts
combat-effects-upgrade contact_frames (impact timing)
game-asset-generator Produces source BVH files for this pipeline

Sample BVH for testing

A walking cycle from ai4animationpy demos is available at:

/tmp/ai4animationpy/Demos/BVHLoading/WalkingStickLeft_BR.bvh

This is a full-body biped walking clip from the Geno character rig.

Reference: ai4animationpy

  • Source: /tmp/ai4animationpy (cloned locally)
  • License: CC BY-NC 4.0 (non-commercial; aligned with hobby game projects)
  • GitHub: https://github.com/facebookresearch/ai4animationpy
  • Key finding: ALL modules require torch at import time via Math/Tensor.py line 5. No conditional import path exists. Standalone implementations are the correct approach.
Files (vexjoy-agent)
  • SKILL.md 8.2 KB
    ---
    name: motion-pipeline
    promoted_to: game-dev
    user-invocable: false
    description: "CPU-only motion data processing pipeline for game animation: BVH import, contact detection, root decomposition, motion blending, FABRIK IK. No GPU required."
    allowed-tools:
      - Read
      - Bash
      - Write
      - Edit
      - Glob
      - Grep
    routing:
      triggers:
        - "mocap"
        - "motion data"
        - "animation pipeline"
        - "BVH import"
        - "contact detection"
        - "IK solve"
        - "motion blend"
        - "bone trajectory"
        - "root extraction"
        - "FABRIK"
        - "skeletal animation data"
      category: game-animation
      pairs_with:
        - game-dev
      agents:
        - rive-skeletal-animator
        - pixijs-combat-renderer
        - game-asset-generator
    ---
    
    # Motion Pipeline Skill
    
    CPU-only motion data processing pipeline for game animation, inspired by Meta's
    ai4animationpy framework (CC BY-NC 4.0). All operations run on numpy and scipy
    with no GPU or PyTorch required.
    
    ## Why standalone implementations?
    
    ai4animationpy's `Math/Tensor.py` imports `torch` unconditionally at the top
    level, which propagates through every module (Animation, Import, IK, Math).
    This means zero ai4animationpy modules are importable without PyTorch installed.
    The standalone implementations in `scripts/motion-pipeline.py` replicate the
    key algorithms from their source code using only numpy + scipy.
    
    ## Environment setup
    
    ```bash
    # Create venv (one-time)
    python3 -m venv /home/feedgen/vexjoy-agent/motion-pipeline-env/
    
    # Install CPU-only deps
    motion-pipeline-env/bin/pip install numpy scipy pygltflib Pillow
    
    # Verify
    motion-pipeline-env/bin/python -c "import numpy; import scipy; import pygltflib; print('OK')"
    ```
    
    The venv is gitignored. The skill documents setup; it does not commit the venv.
    
    ## Commands
    
    All commands output JSON to stdout. Errors go to stderr with exit code 1.
    
    ### import-bvh
    
    Parse a BVH mocap file and print a motion summary.
    
    ```bash
    motion-pipeline-env/bin/python scripts/motion-pipeline.py import-bvh FILE \
      [--scale 0.01]   # scale cm->m for CMU/Mixamo files
    ```
    
    Output fields: `name`, `num_frames`, `num_joints`, `framerate`,
    `total_time_seconds`, `bones[]`, `root_trajectory` (x/y/z range).
    
    ### extract-contacts
    
    Detect ground contact frames per bone (foot, hand) using height + velocity
    thresholds. Replicates `ContactModule.GetContacts()` from ai4animationpy.
    
    ```bash
    motion-pipeline-env/bin/python scripts/motion-pipeline.py extract-contacts FILE \
      --bones LeftFoot RightFoot \
      --height 0.1 \
      --vel 0.5
    ```
    
    Output: `{ "bones": { "<name>": { "contact_frames": [...] } }, "total_frames": N }`.
    
    ### decompose
    
    Split motion into root trajectory (WHERE + HOW) and per-joint local Euler
    angles (POSE). Implements the RootModule / MotionModule decomposition pattern.
    
    ```bash
    motion-pipeline-env/bin/python scripts/motion-pipeline.py decompose FILE \
      --hip Hips
    ```
    
    Output: `root_trajectory.positions[]`, `root_trajectory.velocities[]`,
    `root_trajectory.facing_directions[]`, `per_joint_euler_zyx_degrees{}`.
    
    First 5 frames shown in stdout; full data requires piping to a file.
    
    ### blend
    
    Blend two BVH clips at a fixed alpha using SLERP rotations and LERP positions.
    Clips must share the same bone hierarchy.
    
    ```bash
    motion-pipeline-env/bin/python scripts/motion-pipeline.py blend FILE_A FILE_B \
      --alpha 0.5
    ```
    
    Output: summary of the blended motion.
    
    ### solve-ik
    
    Run FABRIK inverse kinematics on a bone chain at a single frame.
    
    ```bash
    motion-pipeline-env/bin/python scripts/motion-pipeline.py solve-ik FILE \
      --chain Hips:LeftFoot \
      --target 0.2,0.05,0.3 \
      --frame 10
    ```
    
    Output: `chain[]`, `target[]`, `initial_positions[]`, `solved_positions[]`,
    `end_effector_error` (metres).
    
    ### generate-move-ts
    
    Convert a BVH mocap file into a TypeScript `MoveFrame` function compatible with
    road-to-aew's `wrestlingMoves.ts` interface. Outputs keyframe-interpolated
    TypeScript to stdout (and optionally a file).
    
    ```bash
    motion-pipeline-env/bin/python scripts/generate-move-ts.py BVH MOVE_NAME \
      [--scale 0.01] \
      [--contact-bones LeftToeBase RightToeBase LeftHand RightHand] \
      [--num-keyframes 12] \
      [--hip-bone Hips] \
      [--output path/to/output.ts]
    ```
    
    | Argument | Default | Purpose |
    |---|---|---|
    | `BVH` | — | Path to .bvh mocap file |
    | `MOVE_NAME` | — | Kebab-case name (e.g. `roundhouse-kick`) used in TS identifiers |
    | `--scale` | `0.01` | Position scale; 0.01 converts cm→m for CMU/Mixamo files |
    | `--contact-bones` | `LeftToeBase RightToeBase LeftHand RightHand` | Bones used to detect the impact window |
    | `--num-keyframes` | `12` | Keyframe count in the output array (min 2) |
    | `--hip-bone` | `Hips` | Root bone name for trajectory extraction |
    | `--output` | stdout only | Write TS to this file path in addition to stdout |
    
    **Implementation note:** The script imports `motion-pipeline.py` as a module
    via `importlib` rather than calling it as a subprocess. This bypasses the 5-frame
    truncation applied by the `decompose` CLI command, giving access to all frames.
    
    **Output structure:**
    
    ```typescript
    // Generated from roundhouse-kick.bvh on 2026-04-13
    // Keyframes: 12, Impact window: 0.45-0.55
    const ROUNDHOUSE_KICK_KEYFRAMES = [...] as const;
    
    export function getRoundhouseKick(progress: number): MoveFrame {
      // keyframe lookup + linear interpolation
      // isImpact based on detected contact window
      return { attacker, defender, isImpact };
    }
    ```
    
    The attacker's `offsetX/Y/Z` are root trajectory positions normalized to
    start at origin. Rotations are in radians (converted from the BVH's Euler
    ZYX degrees). The defender reaction is computed procedurally: pushed backward
    at impact, eases to mat post-impact.
    
    **Impact detection:** The script finds the first run of 3+ consecutive contact
    frames across the specified bones. For strike moves, this captures the moment
    of hit. For walking/idle clips (feet always down), the window will be frame-0
    and `isImpact` will be nearly never true — this is correct behavior.
    
    **Validation:** The script prints a summary to stderr including trajectory
    range, impact window, and a structural syntax check. Exit code 1 if validation
    fails.
    
    ## Data architecture pattern
    
    The decomposition from ai4animationpy becomes a design contract for all
    game animation work:
    
    ```
    Animation State
      root_trajectory   -- WHERE (position, velocity, facing direction)
      per_joint_euler   -- HOW (local pose in ZYX Euler degrees)
      contact_frames    -- WHAT (contact states for feet, hands)
      [guidance]        -- WHY (intent; handled at game engine layer)
    ```
    
    This separation enables:
    - Different movement speeds without distorting body pose
    - Contact-driven game events (damage triggers, sound, VFX)
    - AI/input guidance independent of motion playback
    
    ## Source reference: ai4animationpy modules adopted
    
    | ai4animationpy module | This script equivalent | Notes |
    |-----------------------|------------------------|-------|
    | `Import/BVHImporter.BVH` | `load_bvh()` | Same parsing logic; scipy replaces torch |
    | `Animation/Motion` | `Motion` dataclass | numpy-only; no torch backend |
    | `Animation/ContactModule` | `extract_contacts()` | Height + velocity criterion identical |
    | `Animation/RootModule` | `decompose()` root section | FK decomposition via matrix inverse |
    | `Animation/MotionModule` | `decompose()` joint section | Local Euler extraction via scipy |
    | `IK/FABRIK` | `solve_ik_fabrik()` | Algorithm identical; no Actor dependency |
    
    ## Integration points
    
    | Downstream agent | Data consumed |
    |------------------|---------------|
    | `rive-skeletal-animator` | `per_joint_euler_zyx_degrees` from decompose |
    | `pixijs-combat-renderer` | `contact_frames` from extract-contacts |
    | `combat-effects-upgrade` | `contact_frames` (impact timing) |
    | `game-asset-generator` | Produces source BVH files for this pipeline |
    
    ## Sample BVH for testing
    
    A walking cycle from ai4animationpy demos is available at:
    ```
    /tmp/ai4animationpy/Demos/BVHLoading/WalkingStickLeft_BR.bvh
    ```
    
    This is a full-body biped walking clip from the Geno character rig.
    
    ## Reference: ai4animationpy
    
    - Source: `/tmp/ai4animationpy` (cloned locally)
    - License: CC BY-NC 4.0 (non-commercial; aligned with hobby game projects)
    - GitHub: https://github.com/facebookresearch/ai4animationpy
    - Key finding: ALL modules require torch at import time via `Math/Tensor.py` line 5.
      No conditional import path exists. Standalone implementations are the correct approach.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related