{"record":{"id":"80fd761534ed5904","repo":"bevyengine/bevy","slug":"mismatched-vertex-attribute-values","errorCode":null,"errorMessage":"Mismatched vertex attribute values","messagePattern":"Mismatched vertex attribute values","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/bevy_mesh/src/vertex.rs","lineNumber":706,"sourceCode":"            VertexAttributeValues::Float64(values) => bytes_of(&values[i]),\n            VertexAttributeValues::Float64x2(values) => bytes_of(&values[i]),\n            VertexAttributeValues::Float64x3(values) => bytes_of(&values[i]),\n            VertexAttributeValues::Float64x4(values) => bytes_of(&values[i]),\n            VertexAttributeValues::Unorm10_10_10_2(values) => bytes_of(&values[i]),\n            VertexAttributeValues::Unorm8x4Bgra(values) => bytes_of(&values[i]),\n        }\n    }\n\n    #[expect(\n        clippy::match_same_arms,\n        reason = \"Although the `values` binding on some match arms may have matching types, each variant has different semantics; thus it's not guaranteed that they will use the same type forever.\"\n    )]\n    pub(crate) fn push_from(&mut self, source: &VertexAttributeValues, i: usize) {\n        match (self, source) {\n            (VertexAttributeValues::Float32(this), VertexAttributeValues::Float32(source)) => {\n                this.push(source[i]);\n            }\n            (VertexAttributeValues::Float32(_), _) => panic!(\"Mismatched vertex attribute values\"),\n            (VertexAttributeValues::Sint32(this), VertexAttributeValues::Sint32(source)) => {\n                this.push(source[i]);\n            }\n            (VertexAttributeValues::Sint32(_), _) => panic!(\"Mismatched vertex attribute values\"),\n            (VertexAttributeValues::Uint32(this), VertexAttributeValues::Uint32(source)) => {\n                this.push(source[i]);\n            }\n            (VertexAttributeValues::Uint32(_), _) => panic!(\"Mismatched vertex attribute values\"),\n            (VertexAttributeValues::Float32x2(this), VertexAttributeValues::Float32x2(source)) => {\n                this.push(source[i]);\n            }\n            (VertexAttributeValues::Float32x2(_), _) => {\n                panic!(\"Mismatched vertex attribute values\")\n            }\n            (VertexAttributeValues::Sint32x2(this), VertexAttributeValues::Sint32x2(source)) => {\n                this.push(source[i]);\n            }\n            (VertexAttributeValues::Sint32x2(_), _) => panic!(\"Mismatched vertex attribute values\"),","sourceCodeStart":688,"sourceCodeEnd":724,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_mesh/src/vertex.rs#L688-L724","documentation":"Defensive panic inside VertexAttributeValues::push_from, the vertex-copy helper used by Mesh::merge_duplicate_vertices (crates/bevy_mesh/src/mesh.rs:1465). It copies one vertex's attribute value from a source attribute store into a destination store, and this arm fires when the destination attribute is VertexAttributeValues::Float32 but the source value is a different variant. Within bevy both sides come from the same attribute map, so via public API this indicates corrupted mesh state or an engine bug; the user-facing cousin for two-mesh merges is MeshMergeError::IncompatibleVertexAttributes.","triggerScenarios":"Mesh::merge_duplicate_vertices on a mesh whose attribute state is internally inconsistent, such that the same attribute id resolves to a Float32 store on one side and a non-Float32 store on the other. Not reachable by merging two ordinary meshes (that path returns a Result error instead).","commonSituations":"Almost always an engine-level invariant violation: a bug in custom mesh-manipulation code or a corrupted/deserialized Mesh. Practically unreachable for well-formed meshes, since push_from is pub(crate) and both operands share one attribute map.","solutions":["Treat this as an engine bug or corrupted mesh state: minimize the mesh that triggers it and report it to the bevy issue tracker with a repro","Audit any custom code that constructs or mutates Mesh attribute maps directly and ensure each attribute id always maps to one VertexAttributeValues variant","If you were merging two meshes with different formats (e.g. Float32 vs Float32x3 for the same attribute name), normalize both to the same format first and use Mesh::merge, which reports incompatibilities as a Result instead of panicking"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Defensive check before deduplicating or merging: one variant per attribute id\nfn attribute_variants_consistent(mesh: &Mesh) -> bool {\n    // Well-formed meshes always satisfy this; useful after custom manipulation\n    mesh.attributes().all(|(_, data)| {\n        VertexFormat::from(&data.values) == VertexFormat::from(&data.values)\n    })\n}\n\n// Practical guard for merging two meshes (the user-facing cousin)\nfn mergeable(a: &Mesh, b: &Mesh) -> bool {\n    a.primitive_topology() == b.primitive_topology()\n        && a.attributes().all(|(id, val)| {\n            b.attribute_by_id(*id)\n                .map_or(true, |other| std::mem::discriminant(val) == std::mem::discriminant(other))\n        })\n}","typeGuard":"fn attribute_formats_match(a: &VertexAttributeValues, b: &VertexAttributeValues) -> bool {\n    std::mem::discriminant(a) == std::mem::discriminant(b)\n}","tryCatchPattern":null,"preventionTips":["Use Mesh::merge's Result API and handle MeshMergeError::IncompatibleVertexAttributes instead of relying on panics","Keep one VertexFormat per attribute id across all meshes produced by your pipeline","If you hit this panic on unmodified meshes, capture a minimal repro and report it to bevy"],"tags":["bevy","mesh","attributes","vertex-format","panic","merge"],"backgroundTag":"vertex-attribute-format-mismatch","analyzedSha":"396ca727080776bd313bb892423b7d94e03b81b4","analyzedAt":"2026-08-20T16:12:39.808Z","contentChangedAt":"2026-08-20T16:12:39.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}