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
Acceptance Criteria
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
Parent
eng2-p8-capabilities)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
AnimationControllerstate-machine pattern to 3D skeletal animation.Architecture Context
Layer: Layer 3 (Services,
ecs/systems/) for the blend-tree state machine, mirroringecs/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/andgoud_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 existinggoud_renderer3d_blend_animations/goud_renderer3d_transition_animation.Boundary constraints (only those that apply):
#[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,TransitionConditionenum:BoolEquals,FloatGreaterThan, etc.) andgoud_engine/src/ecs/systems/animation_controller/system.rs:74-192(two-phase read/apply system: evaluateActionfrom currenttransition_progressorfind_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
TransitionCondition-style guards reused or mirrored from the 2D controller.libs/graphics/renderer3d/animation_sampling.rsto sample and combine more than two clips per frame..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
python codegen/validate_coverage.py.cargo check && cargo fmt --all -- --check && cargo clippy -- -D warningsclean;cargo testgreen;./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_animationtwo-clip exports are untouched and remain valid for simple cases).Blocked By
None.
Files Likely Touched
goud_engine/src/ecs/components/animation_blend_tree/,goud_engine/src/libs/graphics/renderer3d/ik.rs, new exports ingoud_engine/src/ffi/renderer3d/animation.rsor a newffi/renderer3d/blend_tree.rssdks/*/for the new blend-tree/IK FFI exportscodegen/goud_sdk.schema.jsonAgent Notes
ecs/components/animation_controller/types.rs:1-40definesAnimationState(a named state wrapping a clip) andTransitionCondition(BoolEquals { param, value },FloatGreaterThan { param, threshold }, and more).ecs/systems/animation_controller/system.rs:74-192runs a two-phase system: phase 1 (lines ~70-88, read-only) evaluates eitherAdvanceTransition/CompleteTransitionfrom an in-progress crossfade (controller.transition_progress) or callsfind_matching_transitionto look for a newly-satisfied condition; phase 2 (from line ~90) applies the resultingActionwith mutable access, including reading "the from/to clips and comput[ing a] blended rect for crossfade."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_factorall as direct parameters, delegating toplayer.blend(a, b, factor)), andgoud_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 noTransitionCondition-equivalent gating on 3D — game code must call these two low-level primitives directly and manage its own state machine externally today.\bik\b,inverse.kinemat, andblend.?tree(case-insensitive) acrossgoud_engine/srcreturns zero matches anywhere in the repo. This is genuinely new capability, not an extension of existing partial IK code.Verification