Animation pipeline
Rig contract, clip inventory, how each skill animation is authored from its timing contract (previz → base motion → polish → events), hit-reaction sets per monster family, retargeting, runtime blending, and the tests that keep animation in sync with the server.
Animation in Zoen never decides gameplay. The server runs the phases in skills.json. Animation must land on
those times exactly. A greatsword's contact frame is the server's windupS. Every clip is authored to its contract,
then tested against it.
◆Mandatory mannequin and self-review
Establish the versioned mannequin before skill clips. Match rest pose, hierarchy, pivots, axes, bind matrices and sockets;
validate the Unity Humanoid Avatar. Use rig-specific motion limits/reference poses rather than generic clinical angles.
Follow the eight-view plus gameplay-camera self-review, including
continuous motion, feet/head/body/grip checks, body/armour variants and transitions into every movement gem.
Threshold proposals are in development-standards.json. Failed or missing reviews are not passed by import success.
ADR-027 requires movement cancellation through every attack phase: stop future owned presentation events and blend
from the actual current pose immediately, including during local hitstop. Damage stays server-owned.
◆Rig contract
- Skeleton: the Zoen game skeleton with 65 deform bones, Mixamo-compatible naming (prefix-free):
Hips, Spine, Spine1, Spine2, Neck, Head, LeftShoulder, LeftArm, LeftForeArm, LeftHand (+15 finger bones), …Right…, LeftUpLeg, LeftLeg, LeftFoot, LeftToeBase, …plusroot(ground projection) and the gameplay sockets below. - Sockets:
weapon_r,weapon_l,shield_l,back_2h,hip_l,hip_r,quiver,lantern_l,fx_chest,fx_head,fx_feet. - Units: 1 unit = 1 m, +Y up (glTF), forward +Z. Authoring 30 fps, playback 60 fps (interpolated).
- Root motion: every clip is authored in place. Travel distances for dashes/leaps are data (
shape.length/leap) and the server moves the capsule. The animation's root curve is only used to verify that it matches the travel.
◆Clip inventory (MVP)
| Set | Count | Source |
|---|---|---|
| Core locomotion (idle ×3, walk, run, sprint, start/stop, turn 90/180, jump/land, mount on/off, swim-wade) | 18 | Mixamo base → retarget → polish |
| Grip layers: 12 grips (2H heavy, 2H blade, dual 1H, 1H+shield, dual dagger, dual claw, bow, crossbow, chakram, caster 1H, instrument, staff, polearm) × (idle, run, basic chain 3–4 hits, block/aim, sheath) | ≈ 96 | Mixamo/library base + hand polish |
| Skills | 99 + follow-ups (≈ 110) | authored per contract (below) |
| Movement gems | 6 | key poses in weapons.json → movement gems |
| Hit reactions (player) | flinch light ×4 dir, heavy ×4, stagger, knockdown, launch, stun, sleep, death ×5 | library + polish |
| Social | sit, wave, cheer, bow, dance ×2 | Mixamo |
| Monsters | per reaction set (see below) + 1 basic + 2 abilities each | authored on family rigs |
◆Authoring a skill animation (one skill = one small task series)
Every skill in skills.json has timing and presentation.anim (key poses). Frames = seconds × 30.
- Contract sheet (auto):
tools/anim/contract.mjs <skillId>prints frame marks, e.g. Horizon Cleave: windup 0–8 (0.28 s), contact 8–12, recovery 12–22, cancelable from frame 12. - Previz (agent, Blender script):
tools/blender/previz_skill.py --skill warblade.horizon_cleavekeys the listed key poses on the game skeleton at the contract frames (anticipation → contact → follow-through) with default arcs. Output: a greybox clip that is correctly timed, even if stiff. Test: contact frame matches ±1. - Base motion: replace the previz with library motion where one exists (Mixamo/mocap). Retime it with a
time-warp curve so its contact lands on frame 8 (
tools/blender/retime.py). Test: contact ±1 frame, no foot slide > 2 cm. - Polish (AAA pass): clear anticipation (0.25 s+ on heavy), spacing that accelerates into contact, 2–4 frame smear/blur on the fastest arc, overshoot and settle in recovery, weight shift and a planted pivot foot. Weapon arcs follow the VFX trail path. This needs human-quality judgement: the agent proposes, then a review clip goes to the owner.
- Events (presentation only):
trail_on,trail_off,sfx:<cue>,fx:<id>,footstep,cam:impulseat frame numbers, exported in clip metadata. They must align with the server contact time. - Grip variants: if a skill is shared across grips (e.g. dual vs single dagger), make separate clips. Never hide a mismatched grip.
- Export + tests:
.glbanimation + metadata JSON →pnpm test anim-contract(timings),footslide,hand-drift,loop. - Evidence: a 5–10 s clip from the gameplay camera and a side camera, plus the contract overlay (phase bar) burned in.
◆Hit reactions per skill (why each skill looks different on the target)
The server sends a reaction id with each hit (Combat). The client picks the clip by reaction + direction + reaction set:
| Reaction set | Members | Clips |
|---|---|---|
| small_quadruped | hare, fox, jerboa | flinch ×4, heavy ×2, stagger, knockback, knockdown (roll), launch, pull, stun, sleep, death ×4 |
| medium_quadruped | boar, wolf, hyena, stalker, pangolin | same set, heavier timing |
| insectoid | beetle, scorpions, scarab | flinch ×2, heavy ×2, stagger, knockback, knockdown = flips on back + rights itself, launch, stun, sleep, death ×3 |
| reptile | lizard, gecko, viper, monitor, tortoise | flinch ×2, heavy ×2, stagger, knockback, knockdown, launch (small only), stun, sleep, death ×3 |
| bird | kestrel | flinch ×2, heavy, airborne wobble, knockback, grounded knockdown, stun, sleep, death ×2 |
| humanoid | bandits, players, NPCs | flinch ×4, heavy ×4, stagger, knockback, knockdown, launch, pull, stun, sleep, death ×5 |
| boss_tortoise / boss_beast | Regent / Kharuun | hit flash only, partial break, full break, death |
Directional flinches are additive layers on the upper body, so legs keep walking. Launch is a procedural arc (server height curve) with the clip for spin and limbs. Death style comes from the killing blow (ash, shatter, charred, sliced, blown back, launched, collapse) and mixes a clip with a shader effect.
◆Retargeting
- Same skeleton for all lineages. Per-lineage proportion profile (bone lengths) plus IK post-process: feet to ground (two-bone IK with terrain raycast), hands to weapon sockets, look-at.
- Tails (vanara) and antlers/ears get spring-bone secondary motion only.
- Test: every skill clip × 5 lineages × 2 genders checks contact frame ±1, hand-weapon drift < 1 cm and foot slide < 2 cm.
◆Runtime (Unity)
- One Animator controller per body for locomotion; skill clips play through the Playables API (cross-fade 0.10–0.20 s), an upper-body Avatar Mask additive layer for flinches/aim, and Animation Events only for presentation (VFX/SFX sockets), never damage.
- Animation LOD: 60 Hz (tier A) → 30 Hz (B, manual PlayableGraph evaluation) → baked vertex-animation crowds at 15 Hz (C);
Animator.cullingModeskips off-screen characters. - Local hitstop: set the attacker's and target's graph speed to 0 for the impact duration, then resume. Never
Time.timeScale; the server tick never pauses. Valid local movement cancel releases the attacker's held pose immediately; it never waits for hitstop expiry. - Prediction: the windup starts on input. If the server rejects it, blend to a 0.15 s fizzle clip.
◆Video showcase
The wiki's Showcase has a live Feel Lab: a procedural mannequin playing skill timings with windup/contact/recovery and per-skill reactions on dummies, plus 5–10 s recorded clips. It is previz that proves timing and feel rules. It is not final animation quality.
Source: zoen/docs/production/ANIMATION.md · 1,219 words · edit the Markdown, not this page.
