{"slug":"motion-pipeline","title":"motion-pipeline","summary":"CPU-only motion data processing pipeline for game animation: BVH import, contact detection, root decomposition, motion blending, FABRIK IK. No GPU required.","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-20T08:04:47.455688Z","repo":{"url":"https://github.com/notque/vexjoy-agent","stars":425,"forks":48,"license":"MIT","updatedAt":"2026-09-26T21:45:15Z"},"bodyHtml":"<hr>\n<p>name: motion-pipeline\npromoted_to: game-dev\nuser-invocable: false\ndescription: \"CPU-only motion data processing pipeline for game animation: BVH import, contact detection, root decomposition, motion blending, FABRIK IK. No GPU required.\"\nallowed-tools:</p>\n<ul>\n<li>Read</li>\n<li>Bash</li>\n<li>Write</li>\n<li>Edit</li>\n<li>Glob</li>\n<li>Grep\nrouting:\ntriggers:\n<ul>\n<li>\"mocap\"</li>\n<li>\"motion data\"</li>\n<li>\"animation pipeline\"</li>\n<li>\"BVH import\"</li>\n<li>\"contact detection\"</li>\n<li>\"IK solve\"</li>\n<li>\"motion blend\"</li>\n<li>\"bone trajectory\"</li>\n<li>\"root extraction\"</li>\n<li>\"FABRIK\"</li>\n<li>\"skeletal animation data\"\ncategory: game-animation\npairs_with:</li>\n<li>game-dev\nagents:</li>\n<li>rive-skeletal-animator</li>\n<li>pixijs-combat-renderer</li>\n<li>game-asset-generator</li>\n</ul>\n</li>\n</ul>\n<hr>\n<h1>Motion Pipeline Skill</h1>\n<p>CPU-only motion data processing pipeline for game animation, inspired by Meta's\nai4animationpy framework (CC BY-NC 4.0). All operations run on numpy and scipy\nwith no GPU or PyTorch required.</p>\n<h2>Why standalone implementations?</h2>\n<p>ai4animationpy's <code>Math/Tensor.py</code> imports <code>torch</code> unconditionally at the top\nlevel, which propagates through every module (Animation, Import, IK, Math).\nThis means zero ai4animationpy modules are importable without PyTorch installed.\nThe standalone implementations in <code>scripts/motion-pipeline.py</code> replicate the\nkey algorithms from their source code using only numpy + scipy.</p>\n<h2>Environment setup</h2>\n<pre><code># Create venv (one-time)\npython3 -m venv /home/feedgen/vexjoy-agent/motion-pipeline-env/\n\n# Install CPU-only deps\nmotion-pipeline-env/bin/pip install numpy scipy pygltflib Pillow\n\n# Verify\nmotion-pipeline-env/bin/python -c \"import numpy; import scipy; import pygltflib; print('OK')\"\n</code></pre>\n<p>The venv is gitignored. The skill documents setup; it does not commit the venv.</p>\n<h2>Commands</h2>\n<p>All commands output JSON to stdout. Errors go to stderr with exit code 1.</p>\n<h3>import-bvh</h3>\n<p>Parse a BVH mocap file and print a motion summary.</p>\n<pre><code>motion-pipeline-env/bin/python scripts/motion-pipeline.py import-bvh FILE \\\n  [--scale 0.01]   # scale cm-&gt;m for CMU/Mixamo files\n</code></pre>\n<p>Output fields: <code>name</code>, <code>num_frames</code>, <code>num_joints</code>, <code>framerate</code>,\n<code>total_time_seconds</code>, <code>bones[]</code>, <code>root_trajectory</code> (x/y/z range).</p>\n<h3>extract-contacts</h3>\n<p>Detect ground contact frames per bone (foot, hand) using height + velocity\nthresholds. Replicates <code>ContactModule.GetContacts()</code> from ai4animationpy.</p>\n<pre><code>motion-pipeline-env/bin/python scripts/motion-pipeline.py extract-contacts FILE \\\n  --bones LeftFoot RightFoot \\\n  --height 0.1 \\\n  --vel 0.5\n</code></pre>\n<p>Output: <code>{ \"bones\": { \"&lt;name&gt;\": { \"contact_frames\": [...] } }, \"total_frames\": N }</code>.</p>\n<h3>decompose</h3>\n<p>Split motion into root trajectory (WHERE + HOW) and per-joint local Euler\nangles (POSE). Implements the RootModule / MotionModule decomposition pattern.</p>\n<pre><code>motion-pipeline-env/bin/python scripts/motion-pipeline.py decompose FILE \\\n  --hip Hips\n</code></pre>\n<p>Output: <code>root_trajectory.positions[]</code>, <code>root_trajectory.velocities[]</code>,\n<code>root_trajectory.facing_directions[]</code>, <code>per_joint_euler_zyx_degrees{}</code>.</p>\n<p>First 5 frames shown in stdout; full data requires piping to a file.</p>\n<h3>blend</h3>\n<p>Blend two BVH clips at a fixed alpha using SLERP rotations and LERP positions.\nClips must share the same bone hierarchy.</p>\n<pre><code>motion-pipeline-env/bin/python scripts/motion-pipeline.py blend FILE_A FILE_B \\\n  --alpha 0.5\n</code></pre>\n<p>Output: summary of the blended motion.</p>\n<h3>solve-ik</h3>\n<p>Run FABRIK inverse kinematics on a bone chain at a single frame.</p>\n<pre><code>motion-pipeline-env/bin/python scripts/motion-pipeline.py solve-ik FILE \\\n  --chain Hips:LeftFoot \\\n  --target 0.2,0.05,0.3 \\\n  --frame 10\n</code></pre>\n<p>Output: <code>chain[]</code>, <code>target[]</code>, <code>initial_positions[]</code>, <code>solved_positions[]</code>,\n<code>end_effector_error</code> (metres).</p>\n<h3>generate-move-ts</h3>\n<p>Convert a BVH mocap file into a TypeScript <code>MoveFrame</code> function compatible with\nroad-to-aew's <code>wrestlingMoves.ts</code> interface. Outputs keyframe-interpolated\nTypeScript to stdout (and optionally a file).</p>\n<pre><code>motion-pipeline-env/bin/python scripts/generate-move-ts.py BVH MOVE_NAME \\\n  [--scale 0.01] \\\n  [--contact-bones LeftToeBase RightToeBase LeftHand RightHand] \\\n  [--num-keyframes 12] \\\n  [--hip-bone Hips] \\\n  [--output path/to/output.ts]\n</code></pre>\n<table>\n<thead>\n<tr>\n<th>Argument</th>\n<th>Default</th>\n<th>Purpose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>BVH</code></td>\n<td>—</td>\n<td>Path to .bvh mocap file</td>\n</tr>\n<tr>\n<td><code>MOVE_NAME</code></td>\n<td>—</td>\n<td>Kebab-case name (e.g. <code>roundhouse-kick</code>) used in TS identifiers</td>\n</tr>\n<tr>\n<td><code>--scale</code></td>\n<td><code>0.01</code></td>\n<td>Position scale; 0.01 converts cm→m for CMU/Mixamo files</td>\n</tr>\n<tr>\n<td><code>--contact-bones</code></td>\n<td><code>LeftToeBase RightToeBase LeftHand RightHand</code></td>\n<td>Bones used to detect the impact window</td>\n</tr>\n<tr>\n<td><code>--num-keyframes</code></td>\n<td><code>12</code></td>\n<td>Keyframe count in the output array (min 2)</td>\n</tr>\n<tr>\n<td><code>--hip-bone</code></td>\n<td><code>Hips</code></td>\n<td>Root bone name for trajectory extraction</td>\n</tr>\n<tr>\n<td><code>--output</code></td>\n<td>stdout only</td>\n<td>Write TS to this file path in addition to stdout</td>\n</tr>\n</tbody>\n</table>\n<p><strong>Implementation note:</strong> The script imports <code>motion-pipeline.py</code> as a module\nvia <code>importlib</code> rather than calling it as a subprocess. This bypasses the 5-frame\ntruncation applied by the <code>decompose</code> CLI command, giving access to all frames.</p>\n<p><strong>Output structure:</strong></p>\n<pre><code>// Generated from roundhouse-kick.bvh on 2026-04-13\n// Keyframes: 12, Impact window: 0.45-0.55\nconst ROUNDHOUSE_KICK_KEYFRAMES = [...] as const;\n\nexport function getRoundhouseKick(progress: number): MoveFrame {\n  // keyframe lookup + linear interpolation\n  // isImpact based on detected contact window\n  return { attacker, defender, isImpact };\n}\n</code></pre>\n<p>The attacker's <code>offsetX/Y/Z</code> are root trajectory positions normalized to\nstart at origin. Rotations are in radians (converted from the BVH's Euler\nZYX degrees). The defender reaction is computed procedurally: pushed backward\nat impact, eases to mat post-impact.</p>\n<p><strong>Impact detection:</strong> The script finds the first run of 3+ consecutive contact\nframes across the specified bones. For strike moves, this captures the moment\nof hit. For walking/idle clips (feet always down), the window will be frame-0\nand <code>isImpact</code> will be nearly never true — this is correct behavior.</p>\n<p><strong>Validation:</strong> The script prints a summary to stderr including trajectory\nrange, impact window, and a structural syntax check. Exit code 1 if validation\nfails.</p>\n<h2>Data architecture pattern</h2>\n<p>The decomposition from ai4animationpy becomes a design contract for all\ngame animation work:</p>\n<pre><code>Animation State\n  root_trajectory   -- WHERE (position, velocity, facing direction)\n  per_joint_euler   -- HOW (local pose in ZYX Euler degrees)\n  contact_frames    -- WHAT (contact states for feet, hands)\n  [guidance]        -- WHY (intent; handled at game engine layer)\n</code></pre>\n<p>This separation enables:</p>\n<ul>\n<li>Different movement speeds without distorting body pose</li>\n<li>Contact-driven game events (damage triggers, sound, VFX)</li>\n<li>AI/input guidance independent of motion playback</li>\n</ul>\n<h2>Source reference: ai4animationpy modules adopted</h2>\n<table>\n<thead>\n<tr>\n<th>ai4animationpy module</th>\n<th>This script equivalent</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Import/BVHImporter.BVH</code></td>\n<td><code>load_bvh()</code></td>\n<td>Same parsing logic; scipy replaces torch</td>\n</tr>\n<tr>\n<td><code>Animation/Motion</code></td>\n<td><code>Motion</code> dataclass</td>\n<td>numpy-only; no torch backend</td>\n</tr>\n<tr>\n<td><code>Animation/ContactModule</code></td>\n<td><code>extract_contacts()</code></td>\n<td>Height + velocity criterion identical</td>\n</tr>\n<tr>\n<td><code>Animation/RootModule</code></td>\n<td><code>decompose()</code> root section</td>\n<td>FK decomposition via matrix inverse</td>\n</tr>\n<tr>\n<td><code>Animation/MotionModule</code></td>\n<td><code>decompose()</code> joint section</td>\n<td>Local Euler extraction via scipy</td>\n</tr>\n<tr>\n<td><code>IK/FABRIK</code></td>\n<td><code>solve_ik_fabrik()</code></td>\n<td>Algorithm identical; no Actor dependency</td>\n</tr>\n</tbody>\n</table>\n<h2>Integration points</h2>\n<table>\n<thead>\n<tr>\n<th>Downstream agent</th>\n<th>Data consumed</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>rive-skeletal-animator</code></td>\n<td><code>per_joint_euler_zyx_degrees</code> from decompose</td>\n</tr>\n<tr>\n<td><code>pixijs-combat-renderer</code></td>\n<td><code>contact_frames</code> from extract-contacts</td>\n</tr>\n<tr>\n<td><code>combat-effects-upgrade</code></td>\n<td><code>contact_frames</code> (impact timing)</td>\n</tr>\n<tr>\n<td><code>game-asset-generator</code></td>\n<td>Produces source BVH files for this pipeline</td>\n</tr>\n</tbody>\n</table>\n<h2>Sample BVH for testing</h2>\n<p>A walking cycle from ai4animationpy demos is available at:</p>\n<pre><code>/tmp/ai4animationpy/Demos/BVHLoading/WalkingStickLeft_BR.bvh\n</code></pre>\n<p>This is a full-body biped walking clip from the Geno character rig.</p>\n<h2>Reference: ai4animationpy</h2>\n<ul>\n<li>Source: <code>/tmp/ai4animationpy</code> (cloned locally)</li>\n<li>License: CC BY-NC 4.0 (non-commercial; aligned with hobby game projects)</li>\n<li>GitHub: <a href=\"https://github.com/facebookresearch/ai4animationpy\">https://github.com/facebookresearch/ai4animationpy</a></li>\n<li>Key finding: ALL modules require torch at import time via <code>Math/Tensor.py</code> line 5.\nNo conditional import path exists. Standalone implementations are the correct approach.</li>\n</ul>\n","files":[{"path":"SKILL.md","sizeBytes":8425,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-20T08:05:30.124851Z","sha256":"1E094B055D7B4944CFFCCB36ABE5D176B9C5DFFF8BE52229A06C3440030FBF16","sizeBytes":3759},"review":null,"source":{"repositoryUrl":"https://github.com/notque/vexjoy-agent","path":"skills/game/motion-pipeline","license":"MIT","commit":"da9d20e59e671820e3562285cc1f4ec2d8666aa1","subtreeSha":"B731FE2E19739A325E4A64775551A6914EDB5F61FD952D3E886524F9DE81CFEB","lastSyncedAt":"2026-09-27T20:55:45.609916Z"},"reviewedAt":"2026-09-20T08:15:29.958598Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/notque/vexjoy-agent/tree/main/skills/game/motion-pipeline"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install notque-vexjoy-agent@llmmart"},{"target":"git","command":"git clone https://github.com/notque/vexjoy-agent.git"}]}