Building a Low-Cost Stick-Figure Animation Pipeline with a Code-Defined Skeleton Spec

    Explainer videos built from stick figures, cards, captions, and simple camera moves can be rendered with deterministic vector drawing and keyframe interpolation instead of per-second video generation. This article explains why a reusable skeleton specification is more stable than asking a model to write animation code directly, how to split work so the model only chooses actions and fills shot parameters, and how to set up Node.js, FFmpeg, and headless browser capture for a 60 fps export pipeline. It also covers bone proportions, joint limits, forward kinematics, and pose storage. The method is not meant for realistic characters, fine facial acting, cloth, physical collisions, or lighting-driven scenes.

    1. Are stick-figure animations a good fit for code generation?

    Teams making explainer short videos often hit three problems. First, video generation services bill by the second, so repeated sampling and tweaking burn through quota quickly. Second, character scale and color drift between shots, which makes multi-shot storytelling hard to keep consistent. Third, changing one line of dialogue often means regenerating an entire segment.

    The visual elements in this kind of video are usually simple: stick figures, cards, captions, and basic camera motion. Those can be produced with deterministic vector drawing plus keyframe interpolation. A large language model outputs a structured animation description, and code does the rendering. Splitting the work that way improves both cost and stability.

    The boundary should be clear up front. This approach fits stick figures, cards, captions, simple charts, and camera moves such as pans and zooms. It does not fit realistic characters, subtle expressions, cloth and physical collisions, or shots that depend on real lighting. Projects in the latter group should still use a video generation service or professional animation software.

    2. How to implement a code-defined skeleton spec: lock the spec first, and let the model emit parameters

    Asking a model to write animation code from scratch is unstable. In one comparison, a model was asked to produce a runnable web page for a 15-second, three-shot stick-figure explainer in a single pass. A working file appeared in about two minutes, but the head-to-body ratio was off, arm lines were skewed, and animation logic was fully coupled to page structure. Each generation is random, there is no mathematical baseline for bone proportions, and changing one pose or shot changes the body shape.

    A more reliable approach is to turn the specification into a reusable asset and package it. Many agent coding tools can load a specification as a skill or plugin. The package has three layers:

    • Skeleton baseline: proportions are defined in units of head height, every limb length is derived from those parameters, and joint angles have explicit limits.
    • Motion library and interpolation rules: actions such as walking, pointing, thinking, and surprise are keyframe pose sequences. Frames are interpolated with easing functions, and the timeline is fixed at 60 fps.
    • Export toolchain: render and capture frames in the browser, then hand the image sequence to FFmpeg to composite the video.

    Inside this structure, the model does only two things: choose actions and fill shot parameters. Bone proportions and kinematic calculations stay in the specification package.

    3. Environment setup and version requirements

    Before you start, check that the local machine meets the following conditions.

    Component Suggested version Purpose How to check
    Node.js 18 or later LTS Run the agent tool and build scripts node -v
    FFmpeg 5.1 or later, with libx264 Composite an image sequence into video `ffmpeg -encoders \ Select-String libx264`
    Chrome or Edge Latest stable local build Headless rendering and frame capture Check the version in browser settings

    Headless rendering needs permission to launch a browser process on the machine. On a desktop, keep the Chromium sandbox enabled. Do not turn it off for convenience. Only in container or CI Linux environments might you need --no-sandbox, and only after confirming those environments are already isolated.

    In PowerShell, enter the working directory and start a terminal session in the agent coding tool.

    4. How to package the specification

    4.1 Directory layout and field table

    Place the package in a skills folder under the working directory:

    1. skills/stickman-animator/
    2. ├── SKILL.md # Spec entry: trigger conditions, how to call it, output contract
    3. ├── skeleton.js # Bone parameters, joint limits, and forward kinematics
    4. ├── poses.js # Keyframe pose library
    5. ├── easing.js # Easing and interpolation
    6. └── export.js # Browser screenshots and the FFmpeg call

    Bone parameter fields are below. Lengths are example values and can be adjusted for character style.

    Field Meaning Example value
    headHeight Head height, the baseline for every length, in pixels 48
    height Body height 7.5 head heights
    upperArm Upper arm 1.5 head heights
    forearm Forearm 1.2 head heights
    thigh Thigh 2 head heights
    shin Shin 1.8 head heights
    limits.elbow Elbow bend range 0° to 150°
    limits.knee Knee bend range 0° to 140°
    fps Timeline frame rate 60

    4.2 Angle reference frame and forward kinematics

    The angle convention must be written into the package. Otherwise a sample implementation can easily flip limbs. The convention is:

    • Angles are in radians. Convert with a DEG constant in code.
    • The coordinate system matches the screen: x increases to the right, y increases downward.
    • Angle 0 points to the right. Increasing angle rotates clockwise.
    • The upper-arm angle is a global angle. The forearm angle stores only the bend relative to the upper arm, the elbow’s relative angle. Forearm global angle = upper-arm global angle + relative angle.
    • Compute from the character’s own right arm. For the left arm, compute coordinates as if it were the right side, then mirror across the body’s centerline x coordinate.

    Under this convention, a positive elbow relative angle bends the right arm toward the inside of the body. The core of skeleton.js is:

    1. // skeleton.js
    2. const DEG = Math.PI / 180;
    3. const PARAMS = {
    4. headHeight: 48, // Head height in pixels; every length is based on it
    5. upperArm: 1.5 * 48,
    6. forearm: 1.2 * 48,
    7. thigh: 2 * 48,
    8. shin: 1.8 * 48,
    9. limits: {
    10. elbow: [0, 150 * DEG], // Forearm bend relative to the upper arm; 0 is straight
    11. knee: [0, 140 * DEG] // Shin bend relative to the thigh
    12. }
    13. };
    14. // Forward kinematics: child joint from parent position, bone length, and global angle
    15. function joint(parent, length, angle) {
    16. return {
    17. x: parent.x + length * Math.cos(angle),
    18. y: parent.y + length * Math.sin(angle)
    19. };
    20. }
    21. // Limit check: throw on out-of-range values; do not clamp silently
    22. function checkLimit(name, value, [min, max]) {
    23. if (value < min || value > max) {
    24. throw new RangeError(
    25. `${name} out of limits: ${(value / DEG).toFixed(1)}°, allowed ${min / DEG}° to ${max / DEG}°`
    26. );
    27. }
    28. return value;
    29. }
    30. // Right arm: upper arm uses a global angle; forearm uses global angle plus elbow relative angle
    31. function buildRightArm(shoulder, pose) {
    32. const elbowRel = checkLimit('elbow', pose.elbow, PARAMS.limits.elbow);
    33. const elbow = joint(shoulder, PARAMS.upperArm, pose.shoulder);
    34. const hand = joint(elbow, PARAMS.forearm, pose.shoulder + elbowRel);
    35. return { elbow, hand };
    36. }
    37. // Mirror the left arm across the body centerline x coordinate
    38. function mirrorX(point, axisX) {
    39. return { x: 2 * axisX - point.x, y: point.y };
    40. }
    41. // Bone-length self-check: catch edited parameters or mixed units
    42. function assertBoneLength(a, b, expected) {
    43. const d = Math.hypot(b.x - a.x, b.y - a.y);
    44. if (Math.abs(d - expected) > 0.5) {
    45. throw new Error(`Bone length mismatch: expected ${expected.toFixed(1)}, actual ${d.toFixed(1)}`);
    46. }
    47. }
    48. module.exports = { DEG, PARAMS, joint, checkLimit, buildRightArm, mirrorX, assertBoneLength };

    joint follows the formula, so bone length is correct by construction. assertBoneLength exists to catch manual parameter edits, mixed units, and similar mistakes. Character proportions stay consistent only when bone parameters and limit settings are correct. Extreme angles still have to pass the limit check. If a check fails, correct the pose by hand.

    4.3 Keyframe pose example

    Poses are stored as tables of joint angles. Editing an action means editing a few angle values. poses.js