Skip to content

pull_vertex_onto: two control points can claim the same vertex, silently unplacing the first #559

Description

@lmoresi

Found while responding to review on #545. Distinct from the fix in that PR, and more consequential than the stale-geometry finding it was raised alongside.

What happens

pull_vertex_onto(dm, targets) moves a vertex onto each target in turn. Nothing prevents two targets from selecting the same vertex: the second write wins, and the first target silently ends up with no vertex on it.

add_fault calls it with every control point of a trace at once, so this is reachable from ordinary use. The resulting state — a control point that should carry a vertex and does not — is exactly what cut_along_lines reports as

N triangle(s) are entered but not left, which means a line ends inside the mesh

which is the failure #542/#545 exists to fix, arriving by a different route.

Measured

Unstructured box, h = 0.06, straight-ish trace, sweeping control-point spacing. "Collisions" is the number of targets whose chosen vertex was already claimed:

spacing points collisions (selection from original X) collisions (selection from the updated array)
1.00 h 9 0 0
0.50 h 17 5 5
0.25 h 34 22 25
0.10 h 84 72 75

Two points worth noting:

  • Collisions start at 0.5 h spacing — one step coarser than the stale-geometry effect raised in review on Place a tip vertex from the face that holds it, not from the mesh at large #545, which needs sub-0.25 h before it changes any selection.
  • Resolving against the updated coordinates does not help. It is marginally worse (25 against 22 at 0.25 h). So the arr-versus-X question and this one are independent, and fixing the former would not touch this.

On the real listric trace that motivated #545 — 22 control points at roughly cell spacing — there are no collisions and both schemes pick identical vertices, which is why this has not bitten yet.

The fix

Exclude already-claimed vertices when selecting for each subsequent target: carry a claimed set, drop those candidates, and refuse (or warn, naming the target) when a target's minimal face has no unclaimed vertex left. That last case is real information — it means the trace is sampled more finely than the mesh can represent, and the honest response is to say so rather than to quietly place fewer vertices than were asked for.

Related: line_cut.resample already exists to thin a trace to cell spacing, with a docstring noting that "a trace sampled far finer than the cell size lands several points on one vertex and the facet chain fragments" — so the failure mode is known; it is simply not guarded at the point where the vertices are placed.

Practical limit until then

Keep fault control-point spacing at or above 0.5 h.

Underworld development team with AI support from Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions