stride3d/stride · error · InvalidOperationException
Skeleton nodes are not sorted
Error message
Skeleton nodes are not sorted
What it means
ShapeCacheSystem.ExtractNodeTransforms walks a skeleton's nodes in order, computing world matrices by composing each node with its parent at an earlier index. If a node references a ParentIndex >= its own position, the parent transform has not been computed yet, so the system throws InvalidOperationException('Skeleton nodes are not sorted'). This enforces the library invariant that skeleton nodes must be stored parent-before-child.
Solutions
- Sort skeleton nodes topologically so every ParentIndex is strictly less than the node's own index
- Fix the asset/exporter so parents are emitted before children
- After modifying node parenting at runtime, rebuild/re-sort the node list and remap indices
- Detect cycles: a valid sorted skeleton has exactly one node with ParentIndex == -1 (the root) and no index >= position
Example fix
// before
nodes.Add(new Node { ParentIndex = 2 }); // index 0 depends on later node
nodes.Add(new Node { ParentIndex = -1 });
nodes.Add(new Node { ParentIndex = 1 });
// after
// topologically sort so parents come first, then remap ParentIndex
nodes.Sort((a, b) => CompareDepth(a, b)); // root (-1) first
RemapParentIndices(nodes); Defensive patterns
Strategy: validation
Validate before calling
for (int i = 0; i < nodes.Count; ++i)
if (nodes[i].ParentIndex >= i)
throw new InvalidOperationException($"Node {i} references parent {nodes[i].ParentIndex}; sort nodes parent-first."); Type guard
static bool IsSorted(IReadOnlyList<SkeletonNode> nodes) { for (int i = 0; i < nodes.Count; ++i) if (nodes[i].ParentIndex >= i) return false; return true; } Try / catch
try { ExtractNodeTransforms(...); } catch (InvalidOperationException ex) when (ex.Message.Contains("not sorted")) { /* topologically sort skeleton and retry */ } Prevention
- Always topologically sort skeleton nodes (parent before child) when building or editing them
- Remap ParentIndex values after any reorder
- Check for cycles: exactly one root with ParentIndex == -1
- Validate skeleton assets at import time
When it happens
Trigger: Assigning a Skeleton/NodeInformation with nodes whose ParentIndex points to a later node (or forming a cycle); building or editing skeleton asset data programmatically without topologically ordering nodes; deserializing hand-authored or corrupted skeleton assets.
Common situations: Procedurally generated skeletons where nodes were appended child-first; content pipeline exports with unsorted node lists; editing bone parenting in code after loading and forgetting to re-sort.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- The specified NodeName doesn't exist in the model hierarchy.
- All {nameof(contactChangedChannels)} should have hashsets as
- IsObjectReference returned true for an object that is not II
- Asset of type {assetType} was migrated, but still its new ve
- Package RootDirectory is null
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/42ae7d7d52243a22.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs:245
private static Matrix[]? ExtractNodeTransforms(Model model)
{
Matrix[]? nodeTransforms = null;
if (model.Skeleton == null)
return nodeTransforms;
var nodesLength = model.Skeleton.Nodes.Length;
nodeTransforms = new Matrix[nodesLength];
nodeTransforms[0] = Matrix.Identity;
for (var i = 0; i < nodesLength; i++)
{
var node = model.Skeleton.Nodes[i];
Matrix.Transformation(ref node.Transform.Scale, ref node.Transform.Rotation, ref node.Transform.Position, out var localMatrix);
Matrix worldMatrix;
if (node.ParentIndex != -1)
{
if (node.ParentIndex >= i)
throw new InvalidOperationException("Skeleton nodes are not sorted");
var nodeTransform = nodeTransforms[node.ParentIndex];
Matrix.Multiply(ref localMatrix, ref nodeTransform, out worldMatrix);
}
else
{
worldMatrix = localMatrix;
}
if (i != 0)
{
nodeTransforms[i] = worldMatrix;
}
}
return nodeTransforms;
}
/// <summary>View on GitHub (pinned to 96fad776d2)