stride3d/stride · error · InvalidOperationException

Stack should end up with result.

Error message

Stack should end up with result.

What it means

After applying all animation operations, ComputeAnimationBlending requires that, when a result buffer was supplied, the operation stack collapses to exactly one item that is the supplied result. If the stack has a different size or the final popped item is not the result object, the operation sequence was unbalanced and this InvalidOperationException is thrown at AnimationBlender.cs:345.

Solutions

  1. Audit the operation list so Push/Pop operations are balanced: one initial Push and exactly one Pop per subsequently pushed item, ending with the result.
  2. Pass result = null if you don't require the stack to end with a specific buffer (the check only applies when hasResult is true).
  3. Log each AnimationOperation before the call and simulate the stack to find the imbalance.
  4. Check for Stride version mismatches between the code generating operations and the blender implementation.

Example fix

// before
operations.Add(new AnimationOperation { Type = AnimationOperationType.Push, ... });
// missing Pop at the end

// after
operations.Add(new AnimationOperation { Type = AnimationOperationType.Push, ... });
operations.Add(new AnimationOperation { Type = AnimationOperationType.Pop });
Defensive patterns

Strategy: validation

Validate before calling

// Simulate stack balance before blending
int depth = 0;
foreach (var op in operations) {
    if (op.Type == AnimationOperationType.Push) depth++;
    else if (op.Type == AnimationOperationType.Pop) depth--;
    if (depth < 0) throw new ArgumentException("Unbalanced operations: extra Pop");
}
if (depth != 1) throw new ArgumentException("Operations must leave exactly the result on the stack");

Try / catch

try { blender.ComputeAnimationBlending(ops, ref result); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Stack should end up with result")) { /* log ops and fix Push/Pop balance */ }

Prevention

When it happens

Trigger: Passing a non-null result plus an operation list that leaves the internal stack with 0 or >1 items, or whose final item is not the result — e.g. an extra Push, a missing final Pop, or mismatched Push/Pop counts.

Common situations: Hand-built AnimationOperation sequences with unbalanced Push/Pop pairs; animation graph code emitting a blend layer but forgetting the terminating Pop; two blender versions disagreeing on whether result is consumed from the stack.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/fd5a3386bec29d5f. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.Engine/Animations/AnimationBlender.cs:345

            else
            {
                result = AllocateIntermediateResult();
            }

            animationOperation0.Evaluator.Compute((CompressedTimeSpan)animationOperation0.Time, result);

            animationStack.Push(result);

            for (int index = 1; index < animationOperations.Count; index++)
            {
                var animationOperation = animationOperations[index];

                ApplyAnimationOperation(ref animationOperation);
            }

            if (hasResult && (animationStack.Count != 1 || animationStack.Pop() != result))
            {
                throw new InvalidOperationException("Stack should end up with result.");
            }
        }

        private void ApplyAnimationOperation(ref AnimationOperation animationOperation)
        {
            switch (animationOperation.Type)
            {
                case AnimationOperationType.Blend:
                    {
                        // Blend stack[last - 1] and stack[last] into stack[last - 1], then pop stack[last]
                        var op2 = animationStack.Pop();
                        var op1 = animationStack.Peek();
                        Blend(animationOperation.CoreBlendOperation, animationOperation.BlendFactor, op1, op2, op1);
                        FreeIntermediateResult(op2);
                    }
                    break;
                case AnimationOperationType.Push:
                    {

View on GitHub (pinned to 96fad776d2)