What does VRM Character Animation do?
This skill comes from a desktop pet that supports both Live2D and VRM characters (see "Source & Maintenance" at the end). Models used include official samples, VTube Studio models, and VRM models converted from VRChat. The approaches below have all been tested, measured, or reviewed by users.
Three Principles That Apply to Both Character Types
- Procedural layers and animation clips overwrite each other. Which value is calculated first and written last per frame determines what the user sees. Almost all "snapping," "hand dropping," and "head not moving" issues originate here:
- Live2D: Values procedurally overlaid inside
beforeModelUpdatedo not enter physics – hair and body will not follow. Wrap aroundphysics.evaluateand overlay before physics. - VRM: When the three.js
AnimationMixerblends values that are the same as the previous frame, it does not write to the bone. The procedural standing pose and breathing set each frame will leak through during motion clips that "hold" a pose. Skip bones that are being written by the currently playing clip (by node name fromclip.tracks). The same applies when paused – stop the procedural re-write of the standing pose.
- Live2D: Values procedurally overlaid inside
- Do not modify the motion files; add a procedural layer on top. Hand-keyed and AI-generated motions often only move a few bones (a wave only moves the arm) and feel puppet-like. Add a procedural "follow-through" layer after the clip – shoulders rise with the arm, torso yields, wrist lags, head trails half a beat – without layering on bones already written by the clip. Do it once, and all motions benefit, with the ability to toggle for comparison.
- Verify by frame-by-frame numerical logging, not by eye. Animation glitches last only one or two frames, making them nearly impossible to confirm visually. Log the value of the problematic bone (parameter) at the moment it is drawn (after render, or in Live2D's
beforeModelUpdate), record it before and after the fix, then compare; also capture screenshots on two different models.
Where to Read What
| Task | Reference |
|---|---|
| Live2D: loading, writing parameters, physics, idle, motion, hold, expressions | references/live2d.md |
| VRM: procedural poses vs. clips, follow-through, motion-capture clips, expressions, spring bones, transparent windows | references/vrm.md |
| VRM: motion + expression, hand shapes and wrist, hand-to-body IK, no hand drop on interrupt, leg limits, eyes follow head, keyframe interpolation and segmentation | references/vrm.md items 19–31 |
| AI-generated or modified expressions and motions | references/ai-motion.md |
| Nod to music, head motion while speaking | references/live2d.md item 16, references/vrm.md "follow the beat" |
Most Common Pitfalls (Check These First)
| Symptom | Cause | Fix |
|---|---|---|
| Live2D reports "Could not find Cubism runtime" or fails to render | Wrong loading order | Cubism Core → register render pipe module → PixiJS Application.init() → model |
| Live2D parameter writes read 58, null, etc. strange values | String IDs passed directly | Convert to handle first with internalModel.getIdSafe(name) |
| Live2D head does not move (VTube Studio model) | ParamAngle* is physics output; written over | Read physics3.json, redirect writes to its input parameters |
| Live2D only head sways; hair and body don't follow | Overlay placed after physics | Wrap physics.evaluate and add overlay before it |
| Live2D stands completely still | VTube Studio model lacks a proper Idle animation | Programmatic idle: three slow sine waves on head + body + breathing, only write standard parameters |
Live2D restarts moving after stopAllMotions | Engine starts playing Idle when no motion is playing | Point motionManager.groups.idle to a non-existent group, then stop breath |
| VRM hand-keyed motion: hand drops during held pose; snap at start/end | Mixer does not write unchanged values, procedural standing pose leaks through | Skip bones written by the clip when applying the procedural pose |
| VRM one-shot motion jumps back to standing pose at end | With clampWhenFinished=false, the mixer closes the action before finished fires | Set clampWhenFinished = true, then fadeOut |
| VRM motion-capture pose is too wide; legs shift at start; skirt flies up | Clip includes hip and leg tracks | Delete hip and leg tracks during conversion; use only upper body |
| VRM skirt clips through thighs; collider helper shows only one or two spheres | Colliders not registered in the spring chain's collision group (three-vrm only recognizes registered ones) | Add leg capsules to the chain under the hip using standard bones; do not jump the hip in one frame (references/vrm.md item 15) |
| VRM expressions in the clip are invisible | Per-frame state expression, blink, and lip-sync overwrite them | Write only bones in the clip |
| Expression invisible at small screen size | Expression file only changes a small area | Add a layer of parameters per emotion (squint, blush, tilt, mouth corner) |
| VRM motion has no facial expression | Expression only follows state, not motion | Write mood + beats alongside the motion, blend by motion weight (references/vrm.md item 25) |
| Motion requires closed eyes, open mouth, but model's "sleepy" or "smile" blocks it | Expression's overrideBlink / overrideMouth block it | Suppress those expressions during the motion (item 26) |
| VRM single-eye blink invisible | blinkLeft/blinkRight not bound to morph targets | Squint both eyes together instead (item 27) |
| VRM one motion interrupts another; hand drops then raises | New motion starts from standing pose; crossfade passes through standing pose | Start from the most similar pose in the new motion + transition from the interruption point (item 19) |
| VRM motion-capture fingers: flat or clenched; hand-keyed fingers half-closed | Retargeted fingers / motion does not write fingers | Remove captured finger data; write hand shapes in the motion (items 22, 23) |
| Hand-to-chin, hand-over-mouth, heart shape miss the target when model changes shape | Only angles written | Calculate target per bone and mesh in real-time; two-joint IK (item 24) |
| Dance ends; leg snaps back in one frame | Leg IK takes over instantly when fade-out begins | Gradually transition IK weight based on the leg motion weight (item 20) |
| Knee semi-bent; relaxed leg bends too much | Multiple hip offsets stack together | Apply knee limit; halve hip compression; lifting heel increases bend (item 21) |
| VRM eyes always stare at camera | lookAt targets camera; eyes rotate opposite to head turn | When head turns, eyes follow head, blink during turn, micro saccades (item 28) |
| Hand-keyed motion jerky / discontinuous | Each keyframe segment slows at both ends; each keyframe velocity is 0 | Monotone cubic spline + stagger keyframes across segments (item 29) |
| Stretch / raise arms: arms sweep up from sides at 90° / extend sideways | No fist-pull, forearm-fold segments; twist and raise not separated | Segment and rebuild; twist only at the door frame moment (item 30) |
| Idle weight shift: upper body tilts entirely to one side | Knee limit reduces hip lean; waist and chest still rotate fully in opposite direction | Add the reduced hip lean amount to the waist (item 31) |
Source & Maintenance
- Source: Cirra (AI desktop pet, RedCarpG/cirra-deskpet), October 2026 to November 2026.
- Live2D code:
src/pet/live2d.ts,src/pet/live2d-physics.ts,src/pet/cubism-core.ts; rendering libraryuntitled-pixi-live2d-engine1.4.0 (PixiJS 8). - VRM code:
src/pet/vrm.ts,src/pet/vrm-motion.ts, plusvrm-face.ts,vrm-hands.ts,vrm-blend.ts,vrm-reach.ts,vrm-gaze.tsfrom 2026-11-11; commits 2b6ab57 (clampWhenFinished), e5e8687 (no combineSkeletons). - Documentation:
docs/stage5.md,docs/live2d-motions.md,docs/live2d-common-motions.md,docs/live2d-studio.md,docs/loop-motions.md,docs/expression-outfit-split.md,docs/music.md,docs/vrm.md,docs/vrm-motion.md,docs/vrm-motion-library.md,docs/vrm-studio.md,docs/vrm-toon.md,docs/beat-tracking.md,docs/vrm-skirt.md,docs/vrm-motion-polish.md. - Experience: Private repo
lessons/live2d-motion.md,lessons/threejs-vrm-animation.md.
- Live2D code:
- Upstream:
skills/live2d-vrm-animation/in RedCarpG/redcarpg-skills. The copy under~/.claude/skills/is an installed copy and should not be edited directly. The code in Cirra is its source; the two are not automatically synchronized. - How to modify:
- Edit in redcarpg-skills via PR; increment version in
manifest.json, passnode scripts/validate.mjs, then tag (live2d-vrm-animation-v<version>) and reinstall the local copy. First check the two lesson files above for new items that could be included. - After upgrading three.js, three-vrm, or the Live2D rendering library, re-verify the entries in the "Most Common Pitfalls" table that relate to internal library behavior (mixer not writing unchanged values,
clampWhenFinished,shouldRequestIdleMotion, loading order). - If a symptom is observed on only one model type, indicate which model type; do not generalize.
- Edit in redcarpg-skills via PR; increment version in
- Changelog:
- 2026-11-12 · 1.0.0, moved to redcarpg-skills for public release. Removed local absolute paths, linked to source project; content unchanged.
- 2026-11-10 · Cirra · Initial version: three principles, pitfall quick reference; Live2D, VRM, AI-generated motion references; two test prompts in
evals/. - 2026-11-10 · Cirra · Revised VRM item 15 (skirt clipping): original "just use capsules" fix didn't work; replaced with collider group registration, generic leg capsules, and hip-jump avoidance (
docs/vrm-skirt.md); added row to pitfall table. - 2026-11-10 · Cirra · Item 15: added long hair into sleeves (arm capsules cannot be retracted); clothing bulging out is a skinning issue.
- 2026-11-10 · Cirra · Added to
references/vrm.md: "Standard bone VRM 0.x faces -Z" (change orientation for procedural poses; don't cancel out rotateVRM0 when turning; commit e2e6c0c,src/pet/vrm-basis.ts). - 2026-11-11 · Cirra · Added items 19–28 to
references/vrm.md(no hand drop on interrupt, IK takeover during leg clip fade-out, knee limit and heel lift, retarget captured fingers to hand shapes, hand shapes and wrist, hand-to-body IK, motion expression pairs, override blocking beats, empty single-eye blink, eyes follow head); updated item 11 to point to item 25; added face, hands, reach format and validation toai-motion.md; added 9 rows to pitfall table. Source:docs/vrm-motion-polish.md§10, §11,src/pet/vrm-face.ts,vrm-hands.ts,vrm-blend.ts,vrm-reach.ts,vrm-gaze.ts,vrm-clip-extras.ts,vrm-pose.ts,sidecar/src/shared/motion-face.ts,motion-hands.ts(merged commits b2f115d, ea3be9e). - 2026-11-11 · Cirra · Added items 29–31 to
references/vrm.md(keyframe per-segment deceleration causing jerkiness → monotone cubic spline and staggered keyframes; stretch/segmentation and upper arm twist; knee limit reducing hip lean causing upper body tilt); added 3 rows to pitfall table. Source:docs/vrm-motion-polish.md§12,src/vrm-studio/motion-spec.tssmoothAt,src/pet/vrm-pose.ts(merged commit c5b4728). - 2026-11-11 · Cirra · Added to
references/live2d.mditem 21: two exclusion categories for VTube Studio models (physics inputs, non-Param facial parameters) to prevent facial expression from yielding during nodding. Source:src/pet/clash.tsstandardInputs,isStandardParam,docs/expression-clash.md(commit 7721742).