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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions