GeometryEngine.reverseFace reverses face.vertexIds (and the normal/plane) but leaves the face's half-edge loop pointing the other way. Every consumer that derives a ring position from a half-edge then disagrees with vertexIds.
This already caused a user-visible corruption: HalfEdgeMesh.splitEdgeAtVertex spliced the new vertex after he.originVertexId, which on a reversed face lands on the wrong side of the split edge's endpoints; the pair still read as adjacent and GeometryEngine.insertVertexIntoAdjacentFaceBoundaries inserted a SECOND copy — degenerate rings (same vertex twice) plus phantom diagonal edges and bogus triangles. Repro: push/pull a lone face (its outward-shell orientation reverses the cap), then bisect it with the Line tool → 9 faces instead of 7.
Fixed at the split site (HalfEdgeMesh.insertVertexIntoFaceRing finds the slot from the RING, not the half-edge origin; regression test in tests/integration/bisected-face-delete.test.ts → 'bisecting a face whose winding was reversed'). The underlying asymmetry remains: either reverseFace should also reverse the half-edge loop (next/prev + origins), or every half-edge→ring mapping must be ring-derived. Audit the remaining consumers.
GeometryEngine.reverseFacereversesface.vertexIds(and the normal/plane) but leaves the face's half-edge loop pointing the other way. Every consumer that derives a ring position from a half-edge then disagrees withvertexIds.This already caused a user-visible corruption:
HalfEdgeMesh.splitEdgeAtVertexspliced the new vertex afterhe.originVertexId, which on a reversed face lands on the wrong side of the split edge's endpoints; the pair still read as adjacent andGeometryEngine.insertVertexIntoAdjacentFaceBoundariesinserted a SECOND copy — degenerate rings (same vertex twice) plus phantom diagonal edges and bogus triangles. Repro: push/pull a lone face (its outward-shell orientation reverses the cap), then bisect it with the Line tool → 9 faces instead of 7.Fixed at the split site (
HalfEdgeMesh.insertVertexIntoFaceRingfinds the slot from the RING, not the half-edge origin; regression test intests/integration/bisected-face-delete.test.ts→ 'bisecting a face whose winding was reversed'). The underlying asymmetry remains: either reverseFace should also reverse the half-edge loop (next/prev + origins), or every half-edge→ring mapping must be ring-derived. Audit the remaining consumers.