Solo Short-Form Video Monetization: Deterministic Stickman Animation with a Reusable Skill Package

    This article explains how solo creators can turn short-form video production into a deterministic, code-driven pipeline using a reusable stickman animation skill package. It starts with a failed baseline without conventions, defines a fixed skeleton specification and rendering engine, separates animation logic from rendering, and uses configuration files to control scenes, captions, and export parameters. The workflow covers environment checks, headless frame rendering, ffmpeg encoding, audio mixing, quality assertions, troubleshooting, and practical ways to increase output while keeping marginal cost close to rendering time.

    Solo Short-Form Video Monetization: Deterministic Stickman Animation with a Reusable Skill Package

    “Can one person already making short videos actually make money from them?” This is the real situation for many independent creators and small teams publishing short-form videos to global audiences: topic selection, scripting, voiceover, and editing are all handled by one person. The first thing that discourages them is often not editing, but AI video generation: a monthly budget of tens of dollars cannot survive repeated trial and error. Characters break or look inconsistent across shots, and changing one line of narration may force an entire segment to be regenerated.

    But if you examine several popular explainer animations, you will notice that the main character is often a stickman or a simple geometric figure. The narration is dense, and the visuals explain logic through text and graphics rather than photorealistic lighting. This type of content is especially suitable for code-based rendering.

    This article uses the pain points of a solo creator running a short-video business as an example. It starts from a failure baseline without any conventions, then gradually builds a reusable skeleton-engine skill package, turning animation production into a deterministic pipeline.

    1. Problem: Why sampling-based generation cannot support high-frequency updates

    Generative video works by sampling frames from a probability distribution. With the same prompt, each output may produce a different composition, character detail, or motion path. This creates three costs.

    • High marginal cost. Billing is usually based on generation duration or number of generations. When hit rate is low, one usable clip may require many attempts, so output volume and cost rise together.
    • Poor consistency. There is no explicit geometric constraint for the same character across shots. The model can only rely on prompts and reference images, so face shape and body proportions easily drift.
    • Rigid iteration cost. If one line of narration changes, related shots must be regenerated, and previous work is wasted.

    To judge whether a content route is worth pursuing, four indicators are useful: whether the result is reproducible, whether unit content cost is controllable, whether iteration changes are local, and whether the character remains consistent across shots. By these standards, explainer short videos whose visuals are mainly lines, color blocks, and text are better suited to procedural rendering.

    2. Comparing three technical routes

    Dimension Sampling-based video generation Manual template editing Code-based rendering (this approach)
    Visual realism High Medium Medium, stylized
    Reproducibility Low Medium High, deterministic output
    Character consistency Unstable Depends on assets Guaranteed by skeleton parameters
    Marginal cost Rises linearly with output High labor cost Mainly time cost; unit cost stays low as output increases
    Iterating narration Regenerate related shots Rearrange timeline Change configuration and re-render only affected shots
    Best-fit content Realistic narrative, live-action feel Talking-head editing, mixed text and images Knowledge explanation, process demonstration, data visualization

    Code-based rendering is not without weaknesses. Expressiveness is limited by modeling capability; complex motions such as turning around or detailed hand movements require dedicated modeling. Voice quality depends on the chosen text-to-speech capability. However, these weaknesses match the resource constraints of a solo creator: win through stable topic selection and update frequency rather than competing with film-grade visuals.

    3. Environment self-check

    Code-based rendering does not require heavy 3D software. It only needs three local capabilities: a scripting runtime for executing rendering and export scripts, a video processing toolchain for encoding frame sequences and mixing audio with video, and a headless browser for rendering animation pages. After switching machines or reinstalling the operating system, check each item one by one:

    1. Run node -v in the command line to confirm that the scripting runtime is installed and that its version meets the syntax requirements of the rendering library.
    2. Run ffmpeg -version to confirm that the video processing toolchain is available and check whether it includes the libx264 encoder, which handles H.264 encoding.
    3. Open a blank page in headless mode and take a screenshot to confirm that the browser can load correctly.
    4. Reserve enough disk space. For a 15-second, 60fps video, there are about 900 frames in total. At roughly 200 KB per frame, the intermediate frames for one short video alone can occupy nearly 180 MB.

    It is also recommended to maintain the entire project directory with a version-control tool so that every adjustment to skeleton parameters and copy can be traced.

    4. Step 1: Write without conventions to establish a failure baseline

    Before making engineering improvements, use a controlled experiment to show why conventions are needed. Enter the project directory in the terminal, start an AI coding command-line tool, and enter the first prompt:

    Check whether the current directory contains a skill-convention folder, such as .claude/skills, with any stickman animation skill. Check only the current directory. If none exists, write a single-page 15-second stickman explainer animation in stickman-demo/v1-no-skill.html. The theme is “pain points of running a solo short-video business.” Use three shots, 16:9 landscape, no unrelated decorations. After completion, report the result and elapsed time.

    From the original hands-on record, after about two minutes the terminal confirmed that no related skill existed in the current directory. It then used 64 seconds to write a single-file web page of about 11.6 KB. Opening it in a browser showed cards for topic selection, filming, and editing, but several problems were obvious:

    • Rigid head-to-body proportions. Arm lines were slanted, and there was no unified skeleton baseline.
    • Structural coupling. Animation logic, styles, and page content were mixed in one file, making reuse difficult.
    • Uncontrollable results. Every time the model rewrote the page, the output changed, so a stable production standard could not form.

    In one sentence: without conventions, the model keeps “reinventing” skeleton proportions, and the output depends on luck.

    5. Step 2: Build a stickman animator skill to turn conventions into executable assets

    To keep character motion stable, skeleton baselines and motion rules must be packaged into a skill. The core of a skill package is a specification document plus a set of executable scripts. The specification tells the model when to use it, how to use it, and what to verify after completion. The scripts handle deterministic computation that the model is not good at.

    Return to the terminal and enter the second prompt:

    Help me create a stickman-animator skill in the skill-convention folder of the project directory. It will be used to produce 60fps stickman explainer short videos. The skill directory should contain: SKILL.md (describing trigger scenarios, invocation method, parameter conventions, and acceptance checklist), engine.js (rendering library that fixes the main character’s skeleton proportions and supports smooth joint motion), export.js (export script that calls the local video toolchain to encode frame sequences into MP4 and mix in voiceover), and template.html (demo page that loads engine.js and renders according to the shot timeline). All skeleton sizes should be based on “head height,” and action durations and easing curves must be configurable.

    The recommended directory structure after generating the skill package is as follows:

    1. .claude/skills/stickman-animator/
    2. ├── SKILL.md # Skill description and acceptance checklist
    3. ├── engine.js # Skeleton specification + rendering engine
    4. ├── export.js # Frame rendering → encoding → audio mixing
    5. └── template.html # Demo page; can be replaced with another frontend framework

    After using the skill, the model no longer “freely creates” visuals. It assembles content through a fixed interface: write shot configurations, set the timeline, and finally call the export script. The key implementation is broken down below.

    5.1 Skeleton specification: eliminating proportion drift with a parameter table

    The main character is a stickman programmer wearing black-framed glasses. All sizes are based on head height (u), so proportions will not drift in any shot. Joint limits and line width ensure natural motion and clear output.

    Parameter Example value Purpose
    Head height (u) Base unit of rendering All sizes are converted proportionally
    Neck length 0.28 u Connects head and torso
    Torso length 2.40 u Hip to neck
    Shoulder width 1.60 u Distance between shoulder endpoints
    Upper arm / forearm 1.35 u / 1.25 u Two arm segments
    Thigh / shin 1.60 u / 1.50 u Two leg segments
    Elbow / knee bend range 0°–145° / 0°–150° Prevents joints from bending backward
    Line width 0.08 u Uniform stroke thickness

    The rendering library outputs skeleton key points in a fixed structure. The drawing layer only connects lines, so the two are decoupled. The core code skeleton is as follows:

    1. // engine.js (excerpt): skeleton specification and deterministic rendering
    2. const FPS = 60;
    3. const BONES = {
    4. head: 1.0, neck: 0.28, torso: 2.4, shoulder: 1.6,
    5. upperArm: 1.35, foreArm: 1.25, thigh: 1.6, shin: 1.5,
    6. lineWidth: 0.08,
    7. jointLimit: { elbow: [0, 145], knee: [0, 150] } // degrees, prevents backward bending
    8. };
    9. // Easing curves: motion rhythm is determined only by curves and time
    10. const Ease = {
    11. linear: t => t,
    12. outCubic: t => 1 - Math.pow(1 - t, 3),
    13. inOutQuad: t => (t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2),
    14. clamp: (v, a, b) => Math.min(b, Math.max(a, v))
    15. };
    16. // Forward kinematics from hip position and limb angles
    17. // Local coordinate system, y axis up, unit = head height u
    18. function solveSkeleton(pose) {
    19. const { hipY, lean = 0, lArm, rArm, lLeg, rLeg } = pose;
    20. const dir = a => ({ x: Math.sin(a * Math.PI / 180), y: -Math.cos(a * Math.PI / 180) });
    21. const add = (p, d, len) => ({ x: p.x + d.x * len, y: p.y + d.y * len });
    22. const shoulder = a => {
    23. const d = dir(lean + 90);
    24. return { x: d.x * BONES.shoulder / 2, y: hipY + BONES.torso + d.y * BONES.shoulder / 2 };
    25. };
    26. const arm = (s, a) => {
    27. const elbow = add(add(s, dir(a), BONES.upperArm), dir(lean), BONES.neck);
    28. return [elbow, add(elbow, dir(a), BONES.foreArm)];
    29. };
    30. const hip = { x: 0, y: hipY };
    31. const leg = a => {
    32. const knee = add(hip, dir(a), BONES.thigh);
    33. return [knee, add(knee, dir(a), BONES.shin)];
    34. };
    35. return {
    36. head: add(add(hip, dir(lean), BONES.torso + BONES.neck), dir(lean), BONES.head / 2),
    37. neck: add(hip, dir(lean), BONES.torso + BONES.neck),
    38. hip, lShoulder: shoulder(-1), rShoulder: shoulder(1),
    39. lArm: arm(shoulder(-1), lArm), rArm: arm(shoulder(1), rArm),
    40. lLeg: leg(lLeg), rLeg: leg(rLeg)
    41. };
    42. }
    43. // Absolute time t (seconds) → pose: keyframes + easing + breathing micro-motion
    44. function poseAt(t, track) {
    45. const kf = track.keyframes; // [{t, pose, ease}]
    46. let i = 0;
    47. while (i < kf.length - 1 && t >= kf[i + 1].t) i++;
    48. const a = kf[i], b = kf[Math.min(i + 1, kf.length - 1)];
    49. const span = Math.max(b.t - a.t, 1e-6);
    50. const p = Ease[b.ease || 'inOutQuad'](Ease.clamp((t - a.t) / span, 0, 1));
    51. const mix = {};
    52. for (const k of Object.keys(a.pose)) mix[k] = a.pose[k] + ((b.pose[k] ?? a.pose[k]) - a.pose[k]) * p;
    53. // Breathing micro-motion: phase based on absolute time to keep each frame uniquely determined
    54. mix.hipY += 0.02 * Math.sin(2 * Math.PI * t / 2.4);
    55. return mix;
    56. }
    57. // Draw a single frame: depends only on t and configuration
    58. // Same input always produces identical pixels
    59. function renderFrame(ctx, W, H, timeline, t) {
    60. const u = H / 9; // screen height = 9 head units; use H / 11 for vertical
    61. ctx.clearRect(0, 0, W, H);
    62. const scene = timeline.scenes.find(s => t >= s.start && t < s.start + s.dur) || timeline.scenes[0];
    63. const local = t - scene.start;
    64. const pose = solveSkeleton({ ...scene.basePose, ...poseAt(local, scene.track), hipY: scene.basePose.hipY + u * 0 });
    65. // ...multiply key points by u, map them to screen coordinates, connect them,
    66. // and draw scene text and captions
    67. return { sceneId: scene.id, localTime: local }; // helps the export layer detect scene switching
    68. }

    5.2 Rendering engine: deterministic output and motion design

    Export quality depends on one principle: the same timestamp and the same configuration must render exactly the same frame. Based on this principle, there are four engineering conventions:

    1. Time is a function. All motion is calculated from absolute time. Do not accumulate increments between frames, which avoids floating-point errors building up over frames.
    2. Separate animation from rendering. The data layer only produces skeleton key points and scene state. The rendering layer only draws lines, fills colors, and arranges text. The same specification can be reused with another frontend framework in the future.
    3. Easing curves are explicitly configured. Whether an action is fast or slow is determined by easing functions rather than hard-coded in the code, making it easy to adjust rhythm uniformly.
    4. Motion stays close to the narration context. Common actions can be summarized as a few types, implemented with keyframes and easing:
      • Pointing: raise the arm and point at a card.
      • Emphasis: a slight nod combined with enlarging a card.
      • Breakdown: both hands on the head, shoulders lowered.
      • Transition: overlap the exit of the previous shot and the entrance of the next shot by about 0.2 seconds. Use cross-fading to hide the seam and avoid abrupt cuts.

    5.3 Export pipeline: frame-by-frame rendering, video encoding, and audio-video synchronization

    The export script can directly use a mature headless-browser automation library from the runtime ecosystem. The process has four steps:

    1. // export.js (illustrative): frame rendering → video encoding → voice mixing
    2. // Dependencies: scripting runtime, headless browser automation library, video processing toolchain (ffmpeg and libx264)
    3. const { chromium } = require('playwright'); // any library supporting headless screenshots works
    4. const { spawnSync } = require('child_process');
    5. const fs = require('fs');
    6. const path = require('path');
    7. async function exportVideo({ pageUrl, outDir, fps = 60, duration, width = 1920, height = 1080, voice, crf = 20, preset = 'veryfast' }) {
    8. fs.mkdirSync(outDir, { recursive: true });
    9. const browser = await chromium.launch({ args: ['--force-device-scale-factor=1'] });
    10. const page = await browser.newPage({ viewport: { width, height }, deviceScaleFactor: 1 });
    11. await page.goto(pageUrl);
    12. await page.evaluate(() => document.fonts.ready); // wait for fonts to avoid fallback glyphs
    13. const total = Math.round(duration * fps);
    14. for (let i = 0; i < total; i++) {
    15. const t = i / fps;
    16. const state = await page.evaluate((t) => window.__renderFrame(t), t);
    17. // Wait two consecutive frames before screenshot:
    18. // the first frame submits the drawing, and the second ensures it is complete
    19. await page.evaluate(() => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))));
    20. await page.screenshot({ path: path.join(outDir, `frame_${String(i).padStart(6, '0')}.png`), type: 'png' });
    21. if (i % 60 === 0) console.log(`Render progress ${i}/${total}, current scene ${state.sceneId}`);
    22. }
    23. await browser.close();
    24. // Video stream: H.264 / yuv420p / CRF 20; audio stream: AAC 192 kbps
    25. const silent = path.join(outDir, 'video_silent.mp4');
    26. const final = path.join(outDir, 'final.mp4');
    27. const run = (cmd, args) => {
    28. const r = spawnSync(cmd, args, { stdio: 'inherit' });
    29. if (r.status !== 0) throw new Error(`Encoding failed: ${cmd} ${args.join(' ')}`);
    30. };
    31. run('ffmpeg', ['-y', '-framerate', String(fps), '-i', path.join(outDir, 'frame_%06d.png'),
    32. '-c:v', 'libx264', '-preset', preset, '-crf', String(crf), '-pix_fmt', 'yuv420p', silent]);
    33. if (voice) {
    34. run('ffmpeg', ['-y', '-i', silent, '-i', voice, '-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k',
    35. '-map', '0:v:0', '-map', '1:a:0', '-shortest', final]);
    36. } else {
    37. fs.renameSync(silent, final);
    38. }
    39. console.log(`Export complete: ${final}`);
    40. }

    In practice, the workflow behaves as follows:

    • Canvas settings. Use a 1920×1080 viewport and set deviceScaleFactor to 1 to avoid screenshot size mismatches caused by high-DPI scaling.
    • Screenshot timing. Page animation is driven by absolute time. Waiting two consecutive frames before taking a screenshot ensures that the drawing for the current time has been submitted to the screen buffer.
    • Encoding and audio mixing. First encode a silent video, then mix it with the voiceover. If the voiceover is shorter than the video, pad it with silence. If the voiceover is longer, tighten the copy during the script-generation stage rather than letting the video passively become longer.

    In daily use, the entry point can be simplified to a single command, with parameters defined in the skill specification:

    1. node export.js --config scenes.json --duration 15 --fps 60 --voice voiceover.mp3 --out dist/ep001

    5.4 Configuration-driven iteration: “zero re-rolls when changing lines”

    After shot configuration is separated from rendering logic, changing a line of narration only requires modifying the configuration. No model inference is involved:

    1. {
    2. "title": "Pain points of running a solo short-video business",
    3. "fps": 60,
    4. "scenes": [
    5. { "id": "s1", "start": 0.0, "dur": 5.0, "caption": "Topic: Don't know what to film",
    6. "track": { "keyframes": [ { "t": 0.0, "pose": { "lArm": -150, "rArm": 150 }, "ease": "outCubic" },
    7. { "t": 1.2, "pose": { "lArm": -60, "rArm": 150 }, "ease": "inOutQuad" } ] } },
    8. { "id": "s2", "start": 5.0, "dur": 5.0, "caption": "Iteration: Changing narration requires re-rolling",
    9. "track": { "keyframes": [ { "t": 0.0, "pose": { "lArm": -170, "rArm": 170 }, "ease": "outCubic" } ] } },
    10. { "id": "s3", "start": 10.0, "dur": 5.0, "caption": "Solution: Standards first, programmatic rendering",
    11. "track": { "keyframes": [ { "t": 0.0, "pose": { "lArm": -40, "rArm": 40 }, "ease": "inOutQuad" } ] } }
    12. ]
    13. }

    For example, changing the caption of the second shot from “Editing: Change one line and re-roll everything” to “Iteration: Changing narration requires re-rolling” only requires updating the configuration and running the export again.

    Two details are worth noting:

    • Be honest about “zero re-rolls.” “Zero re-rolls” means no longer using sampling-based generation for visuals. Rendering and encoding still take time. If a command-line tool asks the model to modify the configuration, model usage cost may also not be zero.
    • Shot timing follows the voiceover. Voiceover is generated from text, so changing the copy changes its duration. The safer approach is: first synthesize or obtain the voiceover, measure the duration of each sentence, determine the start and dur of each shot accordingly, and then render the visuals. This avoids audio-video mismatch.

    After export completes, ask the model to run a self-check:

    Use the stickman-animator skill to change the configuration to a “zero re-roll for changed lines” version. Export a 15-second 60fps short video. Verify resolution, frame rate, duration, and captions. Report rendering time and file size to me.

    6. Quality checks: use assertions instead of visual inspection

    The biggest advantage of procedural rendering is that quality can be verified automatically. It is recommended to add a check script after the export flow, for example:

    1. # check.py (illustrative): basic quality assertions for rendered artifacts
    2. import json, subprocess, sys
    3. def probe(path):
    4. out = subprocess.check_output([
    5. "ffprobe", "-v", "error", "-select_streams", "v:0",
    6. "-show_entries", "stream=width,height,r_frame_rate,nb_frames",
    7. "-of", "json", path]).decode()
    8. return json.loads(out)["streams"][0]
    9. s = probe("dist/ep001/final.mp4")
    10. assert s["width"] == 1920 and s["height"] == 1080, "Resolution mismatch"
    11. assert s["r_frame_rate"] == "60/1", "Frame rate mismatch"
    12. assert abs(int(s["nb_frames"]) - 900) <= 2, "Frame count does not match duration"
    13. print("Basic checks passed:", s)

    In addition to resolution, frame rate, and frame count, the following checks can be performed in order:

    1. Sample single frames. Extract a middle frame from each shot and check whether the character is centered, whether key points and lines are complete, and whether captions overflow or are truncated.
    2. Check black frames and blank frames. Calculate the average brightness of sampled frames. If it falls below a threshold, mark the frame as abnormal. This usually means no shot matched the time interval.
    3. Check motion plausibility. Check whether joint angles exceed limits and whether feet clearly go through the ground. Stickman style is relatively tolerant of clipping, but limbs should still avoid clearly passing through text cards.
    4. Check audio-video synchronization. Compare total voiceover duration with video duration. If the deviation exceeds 0.3 seconds, return to the voiceover-duration measurement stage and adjust shot timing.

    From the original hands-on record, after using the skill, the model built the skill package in about two minutes. Exporting a short video segment took 52 seconds, and cumulative usage for a single project was about $0.54, depending on actual project billing. More importantly, this cost occurred mainly during the setup stage. After that, each short video only incurs rendering and encoding time. Output can increase without the linear cost growth seen in sampling-based generation.

    7. Troubleshooting common issues

    Symptom Possible cause Solution
    Screenshot is occasionally blank or missing elements Only one frame was awaited, and the drawing had not been submitted yet Wait two consecutive frames before taking a screenshot
    Captions fall back to the default font Rendering starts before fonts are loaded Wait for fonts to be ready before exporting
    Screenshot size does not match expectations Device pixel ratio does not match viewport Fix the scale factor at 1
    Video is shorter or longer than audio Shot duration does not match voiceover duration Measure voiceover duration first, then set shot timing
    Disk space is filled by frame sequences Intermediate frames were not cleaned up Cache by shot and clean up or compress archives immediately after rendering
    Visuals flicker or seams are abrupt Hard cuts and uneven rhythm Use cross-fading transitions and unify easing curves

    8. Practical recommendations: from “it works” to “it makes money”

    For solo short-video monetization, success depends more on topic selection and update rhythm than on the polish of a single visual. Based on a deterministic rendering pipeline, focus on four things:

    • Engineeringize topic selection. Maintain a topic library and label target market, pain-point type, and reference structure. Each episode is produced from a fixed template based on the library.
    • Assetize templates. Turn openings, pain-point cards, solutions, and closing calls-to-action into reusable shot templates. A new video only needs new copy and a few pose adjustments.
    • Move capacity to the cloud. When update frequency increases, containerize rendering tasks and run them concurrently on a cloud container platform. Store rendered artifacts in object storage, collect logs through a logging service, and use monitoring alerts to track render queue backlog and failure rates. This frees local machines from becoming a production bottleneck.
    • Dataficate performance. Record completion rate, engagement data, and production time for each video. Regularly review which templates convert best and continuously remove low-performing topics.

    Conclusion

    Whether short videos can make money depends on whether you can continuously produce them in a stable, low-cost way. The stability of output depends first on whether the production tool is controllable. Sampling-based video generation is good at “from nothing to something” creative exploration. Procedural rendering is good at “from something to stable” batch delivery. By solidifying skeleton specifications, a rendering engine, and an export script into a reusable skill package, and using configuration to drive content iteration, a solo creator can push the marginal cost of each short video down to the time-cost level. Stabilize the production line first, then scale output. That is the more realistic path for making money with short videos as one person.