Skip to content

ENG2-P8-07: 3D animation blend trees + 2-bone IK (extend the 2D controller pattern) #805

Description

@aram-devdocs

Parent

  • Program: ENG2 — GoudEngine v2 Rebuild (see the pinned master tracking issue)
  • Phase / Milestone: Phase 8 — Capability Gaps (eng2-p8-capabilities)
  • Batch / Group: Batch 8.2 — Gameplay services, Group C
  • Runbook spec: docs/src/runbook/phases/phase-8.md (committed with the roadmap)

Summary

Add a 3D animation blend-tree (graph of clips with parameter-driven N-way blending, named states, and transition conditions) and 2-bone IK (e.g. arm/leg reach), extending the existing 2D AnimationController state-machine pattern to 3D skeletal animation.

Architecture Context

Layer: Layer 3 (Services, ecs/systems/) for the blend-tree state machine, mirroring ecs/systems/animation_controller/; Layer 4 (Engine, libs/graphics/renderer3d/) for the IK solver and skeletal blend evaluation; Layer 5 (FFI) for both export surfaces.
Modules/types touched:

  • goud_engine/src/ecs/components/animation_controller/ and goud_engine/src/ecs/systems/animation_controller/ — the 2D state-machine pattern (AnimationState, TransitionCondition, crossfade) to extend/parallel for 3D.
  • goud_engine/src/libs/graphics/renderer3d/animation_sampling.rs, animation_math.rs — new N-way blend evaluation (today only supports a flat two-clip blend) and 2-bone IK solve (position/rotation for a 3-joint chain: root, mid, end-effector).
  • goud_engine/src/ffi/renderer3d/animation.rs — new exports alongside the existing goud_renderer3d_blend_animations/goud_renderer3d_transition_animation.

Boundary constraints (only those that apply):

  • FFI exports: #[no_mangle] extern "C", #[repr(C)], null checks, // SAFETY: comments.

Pattern to follow: goud_engine/src/ecs/components/animation_controller/types.rs:1-40 (AnimationState, TransitionCondition enum: BoolEquals, FloatGreaterThan, etc.) and goud_engine/src/ecs/systems/animation_controller/system.rs:74-192 (two-phase read/apply system: evaluate Action from current transition_progress or find_matching_transition, then apply). The 3D blend tree should reuse this named-state + parameter-condition shape rather than inventing a second DSL for "when does animation X play."

Scope

  • 3D blend-tree data model: named states (each referencing one or more clips), parameter-driven N-way blend nodes (not just the existing flat two-clip blend), and TransitionCondition-style guards reused or mirrored from the 2D controller.
  • Blend-tree evaluation system extending libs/graphics/renderer3d/animation_sampling.rs to sample and combine more than two clips per frame.
  • 2-bone IK solver: analytic (law-of-cosines) solve for a 3-joint chain given a target position, with pole-vector/hint support for elbow/knee orientation.
  • FFI exports: create/configure a blend tree on a model instance, set blend parameters per frame, attach/configure an IK target on a named bone chain.
  • Tests: spec test + unit tests for blend-tree parameter->weight math and IK solver correctness (reachable target produces the expected joint angles; unreachable target clamps to full extension).
  • Docs/rules updates: extend .agents/rules/documentation-standards.md-adjacent animation docs (mdBook) with the 3D blend-tree/IK authoring model, cross-referencing the 2D controller as the pattern precedent.

Acceptance Criteria

  • A 3-clip blend tree (e.g. idle/walk/run driven by a single "speed" parameter) produces smooth, continuous blended poses across the parameter range with no popping at blend-node boundaries.
  • 2-bone IK solver hits a reachable target within a small tolerance and degrades gracefully (fully extended, no NaN/instability) for out-of-reach targets.
  • FFI blend-tree/IK exports have wrappers in all 10 SDKs and pass python codegen/validate_coverage.py.
  • cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings clean; cargo test green; ./codegen.sh && git diff --exit-code (drift gate)

Breaking Change & Throne Follow-up

None — additive/internal (new module, new FFI surface; existing goud_renderer3d_blend_animations/goud_renderer3d_transition_animation two-clip exports are untouched and remain valid for simple cases).

Blocked By

None.

Files Likely Touched

  • New: goud_engine/src/ecs/components/animation_blend_tree/, goud_engine/src/libs/graphics/renderer3d/ik.rs, new exports in goud_engine/src/ffi/renderer3d/animation.rs or a new ffi/renderer3d/blend_tree.rs
  • Generated: SDK bindings under sdks/*/ for the new blend-tree/IK FFI exports
  • Modified: codegen/goud_sdk.schema.json

Agent Notes

  • 2D already has a real, working state-machine implementation to mirror: ecs/components/animation_controller/types.rs:1-40 defines AnimationState (a named state wrapping a clip) and TransitionCondition (BoolEquals { param, value }, FloatGreaterThan { param, threshold }, and more). ecs/systems/animation_controller/system.rs:74-192 runs a two-phase system: phase 1 (lines ~70-88, read-only) evaluates either AdvanceTransition/CompleteTransition from an in-progress crossfade (controller.transition_progress) or calls find_matching_transition to look for a newly-satisfied condition; phase 2 (from line ~90) applies the resulting Action with mutable access, including reading "the from/to clips and comput[ing a] blended rect for crossfade."
  • 3D's equivalent FFI surface (ffi/renderer3d/animation.rs) is narrower than the design-doc shorthand "raw clip playback only" might suggest, but still well short of a blend tree: goud_renderer3d_blend_animations (lines 161-185) only supports a flat two-clip blend (anim_a, anim_b, blend_factor all as direct parameters, delegating to player.blend(a, b, factor)), and goud_renderer3d_transition_animation (lines 189-211) only supports a single timed transition to one target clip (player.transition(target, duration)). There is no named-state concept, no multi-way parameter-driven blending, and no TransitionCondition-equivalent gating on 3D — game code must call these two low-level primitives directly and manage its own state machine externally today.
  • IK is completely absent: grep for \bik\b, inverse.kinemat, and blend.?tree (case-insensitive) across goud_engine/src returns zero matches anywhere in the repo. This is genuinely new capability, not an extension of existing partial IK code.

Verification

cargo check && cargo fmt --all -- --check && cargo clippy -- -D warnings
cargo test
./codegen.sh && git diff --exit-code

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions