stride3d/stride · error · InvalidOperationException
Skeleton nodes are not sorted
Error message
Skeleton nodes are not sorted
What it means
StaticMeshColliderShape.BuildAndShareMeshes walks a mesh's skeleton node hierarchy top-down and caches each node's world transform. Nodes must be ordered so a child appears after its parent; when node.ParentIndex points at a node at or after the current index, the parent's transform is not yet computed, so the code throws this InvalidOperationException to fail fast on malformed skeleton data.
Solutions
- Re-sort the mesh's skeleton nodes so every parent appears before all of its children before calling BuildAndShareMeshes
- Re-export the asset with the original modeling tool / a fixed exporter that emits a topologically ordered node hierarchy
- Validate the loaded model's node ordering (each node.ParentIndex < its index, or ParentIndex == -1 for roots) and reject/reorder the data early
- Check for cycles or self-referencing ParentIndex values in the source asset file
Example fix
// before (unsorted data fed straight to the collider)
var shape = new StaticMeshColliderShape(meshData);
// after (guard: reorder nodes so parents precede children)
var sorted = meshData.Nodes.OrderBy(n => n.ParentIndex == -1 ? -1 : 1).ToList();
// or topologically sort by ParentIndex before building:
if (meshData.Nodes.Any((n, i) => n.ParentIndex >= i))
throw new ArgumentException("Skeleton nodes must be sorted parent-first"); Defensive patterns
Strategy: validation
Validate before calling
static bool IsSkeletonSorted(IReadOnlyList<MeshSkeletonNode> nodes)
{
for (int i = 0; i < nodes.Count; i++)
{
int p = nodes[i].ParentIndex;
if (p != -1 && p >= i) return false;
}
return true;
} Type guard
bool HasValidSkeleton(MeshSkeleton skeleton) =>
skeleton?.Nodes == null ? false :
skeleton.Nodes.All(n => n.ParentIndex == -1 || n.ParentIndex < skeleton.Nodes.IndexOf(n)); Try / catch
try { shape.BuildAndShareMeshes(); }
catch (InvalidOperationException ex) when (ex.Message == "Skeleton nodes are not sorted")
{
meshData.Nodes = TopologicallySortByParent(meshData.Nodes);
shape.BuildAndShareMeshes();
} Prevention
- Topologically sort skeleton nodes by parent index before feeding any mesh to the physics pipeline
- Validate assets at import/build time rather than at collider construction
- Test with assets from every exporter your team uses, since node ordering is exporter-dependent
- Reject model files where any node's ParentIndex is >= its own index during content processing
When it happens
Trigger: Calling BuildAndShareMeshes on a mesh whose skeleton node array is not topologically sorted — i.e. a node whose ParentIndex is >= its own index (self-parent, forward reference, or cyclic parent chain).
Common situations: Importing models from exporters that emit nodes in arbitrary order instead of parent-first order; hand-written or procedurally generated mesh/skeleton data; asset files corrupted or modified by custom tooling that reordered nodes.
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
- Mesh seems to have no volume; triangle rasterization…
- is already assigned to , it cannot be shared with
- Collider type is missing in , please fill an issue or fix it
- Failed to find index buffer while building a convex hull.
- Failed to find index buffer while building a convex hull.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/ab52a434b43b24c1.
Report an issue: GitHub.
Appendix: source
Thrown at sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs:134
}
}
Matrix[] nodeTransforms = null;
if (model.Skeleton != null)
{
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;
}
}
}
int totalVerts = 0, totalIndices = 0;
foreach (var meshData in model.Meshes)
{View on GitHub (pinned to 96fad776d2)