Game guide · source of truth
Production

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.

Skill timing contract (30 fps authoring) — any skill
22 frames · 0.72 s
windup 8factive 4frecovery 10f
WINDUP — readable anticipation, can be interrupted · frame 0
Key poses
Weight shift onto back foot, wide horizontal sweep at chest height through 160°, follow-through wraps around body.
VFX
Long sun-gold ribbon; dust sheet along arc on ground.
SFX
Long steel ring whoosh + clean slice impacts.
Events
trail_on @ f7 · contact @ f8 · trail_off @ f12 · cancel @ f12
Reaction
Medium: flinch_heavy on small/medium.

◆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, … plus root (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)

SetCountSource
Core locomotion (idle ×3, walk, run, sprint, start/stop, turn 90/180, jump/land, mount on/off, swim-wade)18Mixamo 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)≈ 96Mixamo/library base + hand polish
Skills99 + follow-ups (≈ 110)authored per contract (below)
Movement gems6key poses in weapons.json → movement gems
Hit reactions (player)flinch light ×4 dir, heavy ×4, stagger, knockdown, launch, stun, sleep, death ×5library + polish
Socialsit, wave, cheer, bow, dance ×2Mixamo
Monstersper reaction set (see below) + 1 basic + 2 abilities eachauthored 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.

  1. 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.
  2. Previz (agent, Blender script): tools/blender/previz_skill.py --skill warblade.horizon_cleave keys 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.
  3. 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.
  4. 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.
  5. Events (presentation only): trail_on, trail_off, sfx:<cue>, fx:<id>, footstep, cam:impulse at frame numbers, exported in clip metadata. They must align with the server contact time.
  6. Grip variants: if a skill is shared across grips (e.g. dual vs single dagger), make separate clips. Never hide a mismatched grip.
  7. Export + tests: .glb animation + metadata JSON → pnpm test anim-contract (timings), footslide, hand-drift, loop.
  8. 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 setMembersClips
small_quadrupedhare, fox, jerboaflinch ×4, heavy ×2, stagger, knockback, knockdown (roll), launch, pull, stun, sleep, death ×4
medium_quadrupedboar, wolf, hyena, stalker, pangolinsame set, heavier timing
insectoidbeetle, scorpions, scarabflinch ×2, heavy ×2, stagger, knockback, knockdown = flips on back + rights itself, launch, stun, sleep, death ×3
reptilelizard, gecko, viper, monitor, tortoiseflinch ×2, heavy ×2, stagger, knockback, knockdown, launch (small only), stun, sleep, death ×3
birdkestrelflinch ×2, heavy, airborne wobble, knockback, grounded knockdown, stun, sleep, death ×2
humanoidbandits, players, NPCsflinch ×4, heavy ×4, stagger, knockback, knockdown, launch, pull, stun, sleep, death ×5
boss_tortoise / boss_beastRegent / Kharuunhit 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.cullingMode skips 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.