"A second render material can seal a doorway shut in the compiled collision"
▸ SYMPTOM
You migrate the interior surface material on a kit mesh. The render mesh looks correct and the diff shows only material and UV lines. After the change, a doorway or window built from that kit is solid: a trace or a mover cannot pass through the opening that was there before.
Every offline check stays green. Triangle counts match, the UV and manifest lints pass, and a byte-identical full rebuild passes. Comparing the exported mesh source between a working and a broken version shows only usemtl and vt lines differing, never vertex or face counts.
▸ CAUSE
A PhysicsHullFile node compiled with import_mode = "HullPerElement" builds one convex hull per element, and it treats a material break as an element boundary.
While a kit mesh carries one material, an element tracks connected primitives. A doorway built from a jamb, a header, and the wall beside them compiles to three convex hulls, with the opening preserved between them. Give any two of those primitives a shared second material and the compiler folds them into one element and builds one convex hull across both. The convex hull of a wall with a hole in it is a solid wall.
The opening is never removed from the render mesh, only from the hull that used to stop at its edges. So a single material change seals every doorway and window built the same way at once, with no geometry change in the diff. None of the offline checks compile the model, so none of them see it. Only the compiled collision, which the editor or an in-engine walk or trace probe reads, shows the defect.
▸ FIX
- Give physics its own single-material copy of the source mesh. Export a physics-frame variant with every material break collapsed to one name before it feeds the
PhysicsHullFileorPhysicsMeshFilenode. - Keep the render mesh's material list off the physics node. This is the same render and physics split that a tagged collision proxy already uses, so only meshes that actually gain a second material move a physics byte.
- Pin the physics mesh to the render mesh with a lint, so a future material split cannot regress it silently again.
Confirm the mechanism before you trust either story. A render node handing back an error or placeholder model is a plausible-looking culprit, but it is a different failure with a different console signature. Collapse one mesh's material back to a single name and re-probe. If the opening returns, the material break was the cause.
▸ WHY IT WORKS
The compiler reads one source file into a render node and a physics node, and the two nodes read the material list differently. To the render node a second material is a second draw range. To a PhysicsHullFile node in HullPerElement mode it is an element boundary. So a change that is meaningful to only the render side is still a change to both sides.
A separate physics-frame mesh removes the shared input. When the physics node reads a single-material copy, no render-side material edit can reach it, and the element boundaries stay where the geometry puts them. Treat any material-count change to a kit mesh as a collision change until an editor-run walk or trace probe, compared against a baseline in the same session, proves otherwise.
- Published