{"record":{"id":"ab52a434b43b24c1","repo":"stride3d/stride","slug":"skeleton-nodes-are-not-sorted-staticmeshcollidershape","errorCode":null,"errorMessage":"Skeleton nodes are not sorted","messagePattern":"Skeleton nodes are not sorted","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs","lineNumber":134,"sourceCode":"                }\n            }\n            \n            Matrix[] nodeTransforms = null;\n            if (model.Skeleton != null)\n            {\n                var nodesLength = model.Skeleton.Nodes.Length;\n                nodeTransforms = new Matrix[nodesLength];\n                nodeTransforms[0] = Matrix.Identity;\n                for (var i = 0; i < nodesLength; i++)\n                {\n                    var node = model.Skeleton.Nodes[i];\n                    Matrix.Transformation(ref node.Transform.Scale, ref node.Transform.Rotation, ref node.Transform.Position, out var localMatrix);\n\n                    Matrix worldMatrix;\n                    if (node.ParentIndex != -1)\n                    {\n                        if (node.ParentIndex >= i)\n                            throw new InvalidOperationException(\"Skeleton nodes are not sorted\");\n                        var nodeTransform = nodeTransforms[node.ParentIndex];\n                        Matrix.Multiply(ref localMatrix, ref nodeTransform, out worldMatrix);\n                    }\n                    else\n                    {\n                        worldMatrix = localMatrix;\n                    }\n\n                    if (i != 0)\n                    {\n                        nodeTransforms[i] = worldMatrix;\n                    }\n                }\n            }\n\n            int totalVerts = 0, totalIndices = 0;\n            foreach (var meshData in model.Meshes)\n            {","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.Physics/Shapes/StaticMeshColliderShape.cs#L116-L152","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before (unsorted data fed straight to the collider)\nvar shape = new StaticMeshColliderShape(meshData);\n\n// after (guard: reorder nodes so parents precede children)\nvar sorted = meshData.Nodes.OrderBy(n => n.ParentIndex == -1 ? -1 : 1).ToList();\n// or topologically sort by ParentIndex before building:\nif (meshData.Nodes.Any((n, i) => n.ParentIndex >= i))\n    throw new ArgumentException(\"Skeleton nodes must be sorted parent-first\");","handlingStrategy":"validation","validationCode":"static bool IsSkeletonSorted(IReadOnlyList<MeshSkeletonNode> nodes)\n{\n    for (int i = 0; i < nodes.Count; i++)\n    {\n        int p = nodes[i].ParentIndex;\n        if (p != -1 && p >= i) return false;\n    }\n    return true;\n}","typeGuard":"bool HasValidSkeleton(MeshSkeleton skeleton) =>\n    skeleton?.Nodes == null ? false :\n    skeleton.Nodes.All(n => n.ParentIndex == -1 || n.ParentIndex < skeleton.Nodes.IndexOf(n));","tryCatchPattern":"try { shape.BuildAndShareMeshes(); }\ncatch (InvalidOperationException ex) when (ex.Message == \"Skeleton nodes are not sorted\")\n{\n    meshData.Nodes = TopologicallySortByParent(meshData.Nodes);\n    shape.BuildAndShareMeshes();\n}","preventionTips":["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"],"tags":["physics","collider","mesh","invalid-data"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}