{"record":{"id":"42ae7d7d52243a22","repo":"stride3d/stride","slug":"skeleton-nodes-are-not-sorted","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.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs","lineNumber":245,"sourceCode":"    private static Matrix[]? ExtractNodeTransforms(Model model)\n    {\n        Matrix[]? nodeTransforms = null;\n        if (model.Skeleton == null)\n            return nodeTransforms;\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        return nodeTransforms;\n    }\n\n    /// <summary>","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Systems/ShapeCacheSystem.cs#L227-L263","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nnodes.Add(new Node { ParentIndex = 2 }); // index 0 depends on later node\nnodes.Add(new Node { ParentIndex = -1 });\nnodes.Add(new Node { ParentIndex = 1 });\n// after\n// topologically sort so parents come first, then remap ParentIndex\nnodes.Sort((a, b) => CompareDepth(a, b)); // root (-1) first\nRemapParentIndices(nodes);","handlingStrategy":"validation","validationCode":"for (int i = 0; i < nodes.Count; ++i)\n    if (nodes[i].ParentIndex >= i)\n        throw new InvalidOperationException($\"Node {i} references parent {nodes[i].ParentIndex}; sort nodes parent-first.\");","typeGuard":"static bool IsSorted(IReadOnlyList<SkeletonNode> nodes) { for (int i = 0; i < nodes.Count; ++i) if (nodes[i].ParentIndex >= i) return false; return true; }","tryCatchPattern":"try { ExtractNodeTransforms(...); } catch (InvalidOperationException ex) when (ex.Message.Contains(\"not sorted\")) { /* topologically sort skeleton and retry */ }","preventionTips":["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"],"tags":["physics","skeleton","invariant"],"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-15T23:17:13.987Z"}