blender-anim-pipeline
GitHub用于为现有角色模型制作新动画片段,支持移动、攻击等动作。默认采用无依赖的姿势采样混合技术,仅在缺乏源姿势时升级至Blender工具链,确保不改变现有动画分发机制。
Trigger Scenarios
Install
npx skills add levy-street/world-of-claudecraft --skill blender-anim-pipeline -g -y
SKILL.md
Frontmatter
{
"name": "blender-anim-pipeline",
"description": "Author new GLB animation clips for World of ClaudeCraft rigs (locomotion, attacks, casts, ability-specific motions, hit reactions, deaths, emotes) without inventing a new dispatch mechanism. Use when a class, mob family, or NPC needs a distinct clip instead of sharing a generic one, when growing coverage per issue",
"user-invocable": true
}
Animation authoring: pose-sample-and-blend, with a Blender escalation path
Full mechanics reference: scripts/anim/pose_blend.mjs (the shared module every consumer
imports; read its JSDoc before writing a new build script). This skill is the operating
procedure and the decision of when to escalate past it.
Scope check first
This is for new authored clips on rigs this repo already ships (public/models/chars/,
public/models/creatures/). A brand-new creature or player skin needs the asset-pipeline
skill first (rig + base clip vocabulary); this skill only adds MORE clips to a rig that
already has a donor clip library baked in.
Why two techniques, not one
Issue #2889 proposed a Blender MCP server driving new clip authoring at scale (1000+ clips). Investigation found:
- No
blender-mcpserver is connected to this project, and no script in this repo invokes ablenderbinary. Blender-driven authoring is genuinely new tooling, not a wired-up path. scripts/build_bow_anims.mjs(merged, live in production, powers the hunter's bow-draw clip) is an existing, dependency-free precedent for authoring brand new clips WITHOUT Blender: it samples poses already baked into a rig's OWN clip library at chosen timestamps, blends between them with hand-authored easing, and bakes the result into a new clip wired through the ordinaryanimUrls+ClipMapmechanism.
So: pose-sample-and-blend is the primary technique for the large majority of this initiative's scope (a class or creature family needing a distinct clip almost always has a rig that already ships adjacent donor poses: an idle, a windup, a hold, a gesture). Headless Blender is the escalation path, used only when no donor clip anywhere in the rig's shipped library can supply the pose the new clip needs (a genuinely novel silhouette: a new weapon grip angle, a creature-specific pose no shipped clip approximates).
Both land through the exact same seam: an animUrls GLB entry consumed by an existing or new
VisualDef/ClipMap in src/render/characters/manifest.ts. Neither technique touches
src/render/renderer.ts as the animation driver, and neither adds a dispatch path outside
visualKeyFor / ClipMap / VisualDef (issue #2889's own acceptance criteria).
Technique 1: pose-sample-and-blend (default, no new dependency)
1. Pick the batch
Batches are scoped by class, creature family, or ability set, chosen so each batch's
manifest.ts edits are disjoint object literals (independent PRs, low merge-conflict risk).
Evidence a batch is worth doing:
- A class has few or no
attackByAbilityoverrides across its kit (grepmanifest.tsfor the class'splayer_<class>block; count entries againstsrc/sim/content/classes.ts's ability list for that class). - A creature family's
ClipMapconstant (e.g.FLOATING,BIPED14,ENEMY7) is shared BY REFERENCE across many unrelated mob templates (grep -n "clips: <CONST>" manifest.ts); a family with a distinct silhouette or role deserves its own attack instead of the generic set.
2. Inventory the donor rig's own clip library
Every technique-1 clip is sampled from poses the target GLB ALREADY ships. Inspect what is available before writing any timeline:
import { createGlbIO, indexClip } from './scripts/anim/pose_blend.mjs';
const doc = await createGlbIO().read('public/models/.../<rig>.glb');
console.log(doc.getRoot().listAnimations().map((a) => a.getName()));
Cross-reference against manifest.ts's existing ClipMap for that rig: clips already wired
into idle/walk/attack/etc are documented donors; clips NOT named anywhere in the
ClipMap (an unused gesture, an alternate attack) are free raw material, same as
build_elemental_anims.mjs reusing golelingevolved.glb's shipped-but-unwired No/Yes
gestures.
3. Author the timeline
scripts/anim/pose_blend.mjs exports the full toolkit: indexClip (donor channel index),
samplePose (a full pose at time t), poseValue (channel lookup with fallback),
mergePoses (merge several donor poses into one fallback so a bone any donor animates always
resolves to something instead of null, see the doc comment for why a single-pose fallback is
often incomplete), pushPoseRamp (append an eased blend between two static poses),
blendValue/lerpV/slerpQ/easeOutCubic/easeInOutQuad (the underlying math), and
bakeClip + stripToAnimationsOnly (bake the authored timeline into a new mesh-free
AnimationClip GLB). scripts/build_mage_ability_anims.mjs and scripts/build_elemental_anims.mjs
are the worked examples: read one end to end before writing a new consumer.
Write the new script as scripts/build_<class-or-family>_anims.mjs, following the header
comment convention those two use (what's being authored, which donor poses, WHY each clip
reads the way it does, usage + output path). Support --preview (writes mesh+skin+clips to
tmp/ for a full-rig sanity render) alongside the stripped, mesh-free production output.
4. Bake, review, wire
node scripts/build_<name>_anims.mjs --preview # tmp/<name>_preview.glb: full rig + all clips
node scripts/build_<name>_anims.mjs # production: public/models/.../<name>_anims.glb
Review the preview for the same quality bar the asset-pipeline skill holds static assets to:
no foot sliding, no T-pose pops, correct loop points, no broken limbs mid-blend. A
null reaching slerpQ/lerpV (the mergePoses trap above) throws loudly at bake time; a
plausible-but-wrong pose does not; render the preview and look before wiring it in. In-engine
verification (a screenshot or a live spawn) is the final gate, not the preview render alone.
Wire the clip into src/render/characters/manifest.ts:
- New clip on an EXISTING
ClipMapthe target already uses (adding a distinct attack to one member of a shared family, like the elemental): define a newClipMapconstant that spreads the shared one and overrides just the changed field (ELEMENTAL_FLOATINGis the pattern), and point only the targetVisualDefat it. Do not touch the shared constant itself, or every OTHER family sharing it changes too. - New ability-specific casts on a class: add
attackByAbilityentries to that class'sVisualDef.clips, keyed by real ability ids fromsrc/sim/content/classes.ts. - Every new clip's donor GLB gets listed in the
VisualDef.animUrlsarray (mesh-free clip donors compose with the rig's base GLB at load time, same asbow_anims.glb).
5. Tests, build, gate
- A contract test parsing the shipped GLB's JSON chunk for the new clip names plus the
manifest.tssource for the wiring (the pattern:tests/weapon_skins.test.ts's "bow skin attack animation" describe block). Add one per batch. node scripts/build_media_manifest.mjs generate(auto innpm run build; dev needs no regen) so the new GLB preloads.npx tsc --noEmit, the new contract test,tests/character_clipmaps.test.ts, andtests/architecture.test.ts(sim purity: build scripts live underscripts/, never import fromsrc/sim/).
Technique 2: headless Blender (escalation only)
Use this ONLY when step 2 above finds no donor clip in the rig's shipped library that gets close to the needed pose. This tooling does not exist in this repo yet; the first PR that actually needs it also adds the script this section describes.
- Inputs: the target rig's GLB (for bone names/rest pose) plus a written pose spec (per-bone rotation/position targets, or a small set of named key poses): no reference video or mocap capture is assumed, this is scripted keyframe authoring, not tracking.
- Invocation:
blender --background --python scripts/anim/blender_bake_<name>.py -- <args>. The Python script imports the GLB via Blender's glTF importer, sets bone transforms at authored keyframe times using Blender's own quaternion/vector interpolation (never hand-rolled slerp inside the Blender script; that duplicatespose_blend.mjsmath for no reason), and exports via Blender's glTF exporter to a mesh-free clip GLB in the same shapebakeClipproduces (only anAnimationClip+ the bone node hierarchy, no mesh/skin). - Retarget correctness: verify bone names in the exported clip match the target rig's names EXACTLY (Blender's importer can rename on collision); a mismatch silently fails to bind at runtime rather than erroring at build time, so this is checked by hand, not assumed.
- Review checklist (same bar as technique 1, plus): no foot sliding across the authored loop, no root-motion drift (the rig's root bone returns to its start position/orientation at a clip's loop point unless the clip is deliberately one-shot), no T-pose pops at any sampled frame, retarget bone names verified against the target GLB.
- Where output lands: identical to technique 1:
public/models/.../<name>_anims.glb, wired viaanimUrls+ClipMap/attackByAbilityinmanifest.ts, same test/build/gate steps. The two techniques are indistinguishable to the renderer; only the authoring path differs. - Authoring-time only: Blender is never a runtime or build dependency of the shipped
client; this path runs locally when authoring a batch, the same way the
asset-pipelineskill's Tripo calls are authoring-time, not part ofnpm run build.
Tracking the 1000+ clip target across batches
This is a multi-PR initiative (issue #2889's own acceptance criteria: batches "trackable across multiple PRs instead of one"). Each batch PR:
- Names its class/creature-family/ability-set scope and a rough clip count.
- Touches disjoint
manifest.tsobject literals from every other in-flight batch (newClipMapconstants, newattackByAbilitykeys on ONE class) to keep merge conflicts near zero. - Ships its own build script, GLB(s), contract test, and screenshots, following the recipe above end to end. Do not land a batch's tooling without also landing a reviewed clip using it: an unused script is not progress toward the target.
Version History
- 51b342b Current 2026-08-13 10:12


