You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue documents the design of the MMG-based remeshing that has been developed on a feature branch and will arrive in upcoming PRs. It exists so that developers and reviewers of those PRs share the same mental model: what the freeze/required rules are, why each exists, how the size metric is built, and which alternative designs were already tried and measured to fail.
1. Why an explicit policy is needed at all
Remeshing must repair bad elements while preserving computed fields — above all the localized plastic strain. Every entity the mesher rebuilds gets re-interpolated afterwards, and re-interpolation numerically diffuses whatever it touches. The two mesh engines need opposite treatments:
Triangle/TetGen re-triangulates the existing point cloud: untouched points keep their fields verbatim, so preservation is implicit. The only explicit policy it needs is shared size floors (interior nodes closer than the minimum edge length are decimated before re-triangulation, so it cannot emit an element under the tiny-element trigger).
MMG rebuilds everything it is not explicitly forbidden to touch. Preservation must be explicit, through MMG’s two “required” constraints:
required VERTEX — MMG may not move or delete the node;
required TRIANGLE/TET — MMG may not touch the element at all.
So the policy computes per-entity freedom, and the complement is handed to MMG as constraints.
2. Vocabulary
Entity
Level
Meaning
node
MOVABLE
free; MMG may move or delete it
node
PINNED
MMG required vertex; its nodal fields are carried verbatim across the remesh
element
FREE
may be split/collapsed/swapped, and its nodes are made MOVABLE — full repair freedom
element
SPLIT
may be split/swapped, but its nodes are not freed: refinement inserts new nodes without moving old ones, so e.g. the shear band’s plastic strain is carried verbatim and only new nodes are interpolated
element
FROZEN
becomes an MMG required element iff all of its nodes ended up PINNED
DERIVED REQUIRED — the only place freedom becomes MMG constraints:
required node = NOT movable
required element = FROZEN grade AND all of its nodes pinned
An element with any movable node is therefore never required: this is the built-in one-element hinge/transition layer around every freed region. It matters twice — in the rule mask and again in the post-output quality gate (§7).
3. Pipeline overview
flowchart TD
T["remesh trigger:<br/>quality below minimum · boundary distortion · tiny element"] --> P{path}
P -->|"nodes crossed a restored boundary,<br/>or crowded free-surface cliffs"| C["COLLAPSE path<br/>conservative node collapse<br/>before MMG"]
P -->|otherwise| D["DIRECT path<br/>adapt current mesh in place"]
C --> M
D --> M["freeze mask<br/>rules R1–R5 (+R6 direct, R6/R7 collapse)"]
M --> F["size metric<br/>frozen base · plastic refinement ·<br/>anticipation · floor"]
F --> I["inflow material interface (2D, option 13):<br/>required interface edges +<br/>pre-subdivision of over-long edges"]
I --> A["MMG adaptation<br/>required vertices / elements / edges ·<br/>scalar or tensor metric"]
A --> Q{"quality gate:<br/>output element below minimum quality<br/>or under tiny trigger + ½ gap?"}
Q -->|"yes, attempt < 4"| U["escalating unfreeze:<br/>CORE rings fully freed,<br/>HINGE ring keeps nodes pinned<br/>but drops required-element"]
U --> A
Q -->|"still bad after 4"| W[keep last mesh + warning]
Q -->|clean| O["accept: interpolate fields,<br/>pinned entities verbatim"]
Loading
The 2D and 3D remesh drivers share this structure. The collapse path is taken when material crossed a restored boundary or, in 2D, when crowded free-surface “cliff” nodes below the minimum edge length were collapsed conservatively before MMG; the freeze masks are then translated through the collapse remap before use.
4. The rules
Applied in order; each rule may only RAISE freedom, never lower it.
R1 — REPAIR
Triggers when element quality falls below min_quality * remesh_defensive_quality_ratio (the defensive band: repair marginal elements with headroom now rather than re-trigger a remesh soon) or the element measure falls below the smallest allowed volume (tiny). Marks the element FREE and frees its nodes — repair needs node motion.
Two sub-cases:
tiny-only elements free their nodes unconditionally — the tiny trigger is a hard remesh failure and must always be fixable;
low-quality elements free their nodes except a node that also touches an element sharing no marker material type: a material-interface node stays pinned so the repair doesn’t drag the material boundary.
R1’s tiny test deliberately uses the raw size floor, not the margin-scaled trigger (§7).
R2 — REFINE
Triggers when the plastic-strain increment since the last remesh exceeds remesh_active_plstrain — the actively growing band, not the fossil band. Marks the element SPLIT: MMG adds resolution, but existing nodes stay pinned, so the band’s plastic strain is carried verbatim and only newly-inserted nodes are interpolated. Default threshold 0.01: two orders below a real shear band (~1), three above the noise floor.
R3 — BOUNDARY
A boundary node becomes MOVABLE only where the surface is being reshaped — i.e. an ACTIVE free-surface (TOP) node, gated on surface-process activity (displacement > 1 % of resolution per remesh interval). Everything else on the boundary stays pinned:
quiet pure-TOP nodes — no top-flattening step exists; freeing them re-tessellated the whole top row every remesh (surface churn). But an entirely pinned top lid starves MMG of slack and death-spirals — hence the activity gate rather than a blanket pin;
every pure SIDE-WALL node of a restored side (remeshing option 13) — the wall column is carried verbatim, including its quiet TOP corner (leaving the corner movable diagonal-flipped the corner elements);
every BOTTOM node — carried verbatim; the bottom-flattening step still snaps it back onto the bottom plane in the boundary outline.
R4 — INFLOW SIDE STRIP
Remeshing option 13, INFLOW sides only. Wall-faceted elements plus 2 connectivity rings become FREE and their interior nodes movable, so the ~resolution inflow wall band can coarsen back to its intended size once material moves off the wall. R3 pins all restored side walls; R4 re-frees the inflow band on top of that, while R5’s fixed-side guard keeps the FIXED sides verbatim. Boundary nodes and material-interface nodes are never freed here.
R5 — SIZE RECOVERY
The freeze has no size term, so a quiet wrong-sized element would otherwise be carried verbatim forever. R5 frees a quiet element that is the wrong size in either direction:
(a) fossil-fine — finer than the GRADATION ENVELOPE by more than remesh_size_recovery_ratio → free to COARSEN. The envelope is the smallest size MMG’s gradation could legally assign at a node, grown by the gradation factor (1.3, MMG’s default, mirrored here) from every intentionally-fine metric target (wall clamp, plastic refinement, floors) via fixpoint relaxation. The legal size-transition zone and the refined band are thus never touched — only fine mesh with no justification (typically wall-band elements that advected inland and froze at wall size).
(b) oversized — at least 2× its own pre-gradation metric target → free to SPLIT. A conservatively-frozen quiet element left grossly too coarse when the metric refined under it; MMG cannot split a required element, so it must be freed.
Guards: skips R2 growing-band elements, top-surface-connected elements, the fixed-side wall band (wall + 4 rings), and — for coarsening only — material-interface elements. A freed element never unpins a SIDE-WALL or BOTTOM node.
R6 — FLATTEN-BROKEN and R7 — COLLAPSE-REGION
R6: elements that the boundary-flattening step broke (post-flatten quality below the minimum, judged after the R1–R5 masks) → FREE. Applies on both paths.
R7 (collapse path only): elements reshaped by the outside-node collapse, plus 1 connectivity ring → FREE.
With material entering through a restored side wall, the layering it carries must survive coarsening. Node pinning alone is not enough: MMG could still collapse a straddling element across a ~1-element-thick layer. Two cooperating mechanisms fix this:
Interface-edge detection. Within the inflow wall band, each element’s dominant marker material type is computed, and every interior edge shared by two band elements of different dominant type — the discrete material-interface line — is handed to MMG as a required edge (tagged with a sentinel reference so the edges can be recognized and filtered back out of the boundary description after adaptation; they are internal, not boundary). The required-edge constraint is used because it is the only tag that reliably keeps the internal line straight — open-boundary and plain reference edges let MMG curve (Bezier) or cross the line, and corner-tagging moves it; both were tested and rejected.
Pre-subdivision. A required edge is frozen — MMG cannot split it — so an oversized element sitting on the interface would stay oversized. Before MMG runs, any interface edge longer than ≈ √2 × the local metric is therefore bisected at its collinear midpoint (line geometry stays exact), both adjacent triangles are split in place, and the new node plus both half-edges are marked required. The split is sized to the neighbourhood-minimum metric, so the fine wall size propagates along the interface as it advects inland. One split per triangle per pass; each pass halves an over-long edge, so it converges.
Together: the inflow material boundary stays a straight, conforming, refinable line of required edges. On the collapse path the edge endpoints are translated through the collapse remap first.
An earlier design — per-element MMG references to make the interface conforming — was abandoned: it produced near-wall sliver elements (confirmed by an A/B comparison with the references off).
6. The metric system
6.1 Frozen base — the initial nodal size field
The scalar metric fed to MMG is anchored to a frozen nodal size field recording each node’s intended element size:
Away from plastic strain the volume ratio is 1, so element sizes are maintained across remeshes instead of drifting toward uniformity. The frozen size field is:
initialized at step 0 — measured area-weighted from the as-generated mesh, or zone-aware when the two-stage metric-aware init is enabled (a mesh region with size ≤ 0 targets each element’s own as-meshed size rather than the global resolution);
maintained — interpolated to every new mesh, remapped through renumbering;
checkpointed — written and restored, so restarts keep the run-start sizes;
pinned at inflow walls — a run-start snapshot of the full wall profile (including nodal sizes) is kept, and every remesh re-pins the wall-column nodes to the run-start size for their depth, so the metric base never ratchets at the wall.
6.2 Plastic refinement
Per element, the target volume is scaled by 1/(1 + coeff · plastic_strain) (max-area convention; edge length scales as that ratio^(1/NDIMS)), with coeff = mmg_metric_refine_coeff (default 5; 0 disables plastic refinement entirely).
6.3 Metric floor (tiny-element hysteresis)
The metric is floored one hysteresis step above the minimum edge length: floor = h_min · remesh_tiny_margin^(1/NDIMS). Without this, the shear band lives exactly at the minimum and any compression immediately re-fires the tiny-element trigger (a remesh storm). See §7 for the trigger side of the same margin.
6.4 Surface-motion anticipation (2D only)
For top-row elements, nodal motion (advection plus the surface-process rate) gives a signed area rate; a shrinking element gets its target volume inflated by min(8, exp(−rate·τ)) where τ is the time since the last remesh. Elements under a fast-moving surface (e.g. a landslide) are rebuilt with room to be crushed instead of at their final size. The cap was raised from 4× to 8× for fast surface events; a divergence-of-velocity variant was tried and reverted. Self-tuning — no config parameter.
6.5 Anisotropic inflow-wall metric (2D + 3D)
Gated on remeshing option 13 with mmg_aniso_wall_ratio > 0: a per-node symmetric tensor metric is built — fine tangential to the inflow wall (h), coarser (h · ratio) in the wall-perpendicular direction; isotropic elsewhere. When present, the tensor replaces the scalar metric in the MMG call. The achievable aspect is capped by MMG’s maximum-size truncation. Ratios in (0,1) are rejected at input (they would refine, not coarsen, the perpendicular axis).
6.6 MMG global bounds
h_min = smallest_size^(1/NDIMS) · resolution, h_max = largest_size^(1/NDIMS) · resolution, Hausdorff distance = mmg_hausd_factor · resolution. The gradation factor is deliberately not a config parameter — MMG’s default 1.3 is kept (and mirrored in R5’s envelope).
7. Robustness machinery
Tiny-element trigger + hysteresis. A remesh fires when an element measure drops below smallest_volume / remesh_tiny_margin, where the smallest volume (derived from smallest_size and resolution) is also the remesher’s size floor. Margin 1: trigger sits on the floor (high remesh frequency). Margin > 1: opens a hysteresis gap, paid for with smaller elements (smaller dt) between remeshes. The same margin sets the metric floor (§6.3) and buffers the quality gate.
Post-output quality gate. If MMG’s output contains an element that would immediately re-trigger remeshing (below the minimum quality, or under the tiny trigger plus half the hysteresis gap), a graded region around each bad output element is unfrozen and MMG is re-run:
CORE — grown from the bad element to frozen contact (capped at 3 rings) plus one extra ring per attempt (geometric escalation): full freedom, both required-element and required-node dropped;
HINGE — the derived-required invariant re-enforced: required elements bordering the core lose required-element status while their nodes stay pinned.
Up to 4 attempts; a still-bad final mesh is kept with a warning rather than aborting.
Boundary-facet hygiene. MMG re-emits required-element edges as extra unflagged boundary-facet copies. Duplicated facets are merged with their boundary flags OR-combined (without this, an unflagged copy could win the lookup and silently strip a boundary flag — this was a real bottom-boundary-condition-loss bug), and unflagged interior echoes are dropped so they don’t ratchet remesh-over-remesh. Both cleanups run on MMG input and output.
Log taxonomy. Every remesh prints stage-tagged lines reviewers will meet in run logs:
[freeze] conservative remesh (<path>): M elements -> F free + S split + N frozen (R required verbatim + H hinge with movable nodes); P/M nodes pinned. followed by per-rule attribution lines (free: R1/R4/R5/R6/R7, split: R2, nodes: R1/R3/R4/R5);
[unfreeze] … — R4 strip and R5 size-recovery frees;
[require] pre-subdivided N over-long interface edge(s);
[metric] surface-motion anticipation …;
[quality] MMG output has N below-min_quality/tiny element(s); unfroze … — gate retries.
A surface-churn alarm prints only when an abnormal fraction of top-row nodes ends up movable, and an environment variable (DES_DEBUG_MASK) dumps the per-entity masks for debugging.
8. Configuration parameters
All under the [mesh] cfg section. Validation: remesh_tiny_margin ≥ 1, remesh_defensive_quality_ratio ≥ 1, remesh_size_recovery_ratio = 0 or > 1, mmg_metric_refine_coeff ≥ 0, mmg_aniso_wall_ratio = 0 or ≥ 1, smallest_size ≤ largest_size.
Parameter
Default
Role
min_quality
0.4
remesh trigger; R1 base threshold; quality-gate threshold
Scalar metric (frozen base + plastic refinement + floor)
✅
✅
Anisotropic wall tensor
✅
✅ (y-walls too)
Required interface edges + pre-subdivision
✅
❌ (no internal-edge equivalent wired for 3D)
Surface-motion anticipation
✅
❌ (relies on 2D top-chain bookkeeping)
Initial side-wall refinement pass
✅
❌ (3D relies on progressive remesh-time wall clamp)
10. Measured dead ends — do not re-propose without new evidence
Designs that were implemented, measured, and rejected
Per-element MMG references for interface conformity — produced near-wall slivers; replaced by required interface edges (§5).
Open-boundary / plain reference edges / corner-tagging for the interface line — MMG curves (Bezier) or moves the line; only the required-edge constraint keeps it straight.
Unbounded undersized flood-fill unfreeze — re-frees the gradation transition zone forever.
Freeing wider than REPAIR around flatten damage — produced NaNs at the bottom boundary.
Dissolving small required-element islands — more gate retries, not fewer.
Raising R1’s tiny floor to the metric floor — diffuses the shear band.
Explicit extra hinge ring in the gate’s unfreeze — exact no-op.
SPLIT instead of FREE for R1’s quality term — both variants (all distorted elements; defensive-band-only at ratios 1.25–3.0): more remeshes/retries, quality floor erodes, no preservation gain.
Divergence-of-velocity metric anticipation — reverted in favour of the surface-motion (advective + surface-rate) form (§6.4).
Per-element frozen size base — replaced by the nodal size field of §6.1.
The upcoming PRs carry the implementation; a consolidated policy comment at the top of the remeshing policy code restates §§1–4 and 7 next to the rules themselves and is kept in sync as the living source of truth.
Feedback welcome — especially on naming, on which parts should be split into separate PRs, and on anything above that deserves its own regression test before merge.
This issue documents the design of the MMG-based remeshing that has been developed on a feature branch and will arrive in upcoming PRs. It exists so that developers and reviewers of those PRs share the same mental model: what the freeze/required rules are, why each exists, how the size metric is built, and which alternative designs were already tried and measured to fail.
1. Why an explicit policy is needed at all
Remeshing must repair bad elements while preserving computed fields — above all the localized plastic strain. Every entity the mesher rebuilds gets re-interpolated afterwards, and re-interpolation numerically diffuses whatever it touches. The two mesh engines need opposite treatments:
Triangle/TetGen re-triangulates the existing point cloud: untouched points keep their fields verbatim, so preservation is implicit. The only explicit policy it needs is shared size floors (interior nodes closer than the minimum edge length are decimated before re-triangulation, so it cannot emit an element under the tiny-element trigger).
MMG rebuilds everything it is not explicitly forbidden to touch. Preservation must be explicit, through MMG’s two “required” constraints:
So the policy computes per-entity freedom, and the complement is handed to MMG as constraints.
2. Vocabulary
DERIVED REQUIRED — the only place freedom becomes MMG constraints:
An element with any movable node is therefore never required: this is the built-in one-element hinge/transition layer around every freed region. It matters twice — in the rule mask and again in the post-output quality gate (§7).
3. Pipeline overview
flowchart TD T["remesh trigger:<br/>quality below minimum · boundary distortion · tiny element"] --> P{path} P -->|"nodes crossed a restored boundary,<br/>or crowded free-surface cliffs"| C["COLLAPSE path<br/>conservative node collapse<br/>before MMG"] P -->|otherwise| D["DIRECT path<br/>adapt current mesh in place"] C --> M D --> M["freeze mask<br/>rules R1–R5 (+R6 direct, R6/R7 collapse)"] M --> F["size metric<br/>frozen base · plastic refinement ·<br/>anticipation · floor"] F --> I["inflow material interface (2D, option 13):<br/>required interface edges +<br/>pre-subdivision of over-long edges"] I --> A["MMG adaptation<br/>required vertices / elements / edges ·<br/>scalar or tensor metric"] A --> Q{"quality gate:<br/>output element below minimum quality<br/>or under tiny trigger + ½ gap?"} Q -->|"yes, attempt < 4"| U["escalating unfreeze:<br/>CORE rings fully freed,<br/>HINGE ring keeps nodes pinned<br/>but drops required-element"] U --> A Q -->|"still bad after 4"| W[keep last mesh + warning] Q -->|clean| O["accept: interpolate fields,<br/>pinned entities verbatim"]The 2D and 3D remesh drivers share this structure. The collapse path is taken when material crossed a restored boundary or, in 2D, when crowded free-surface “cliff” nodes below the minimum edge length were collapsed conservatively before MMG; the freeze masks are then translated through the collapse remap before use.
4. The rules
Applied in order; each rule may only RAISE freedom, never lower it.
R1 — REPAIR
Triggers when element quality falls below
min_quality * remesh_defensive_quality_ratio(the defensive band: repair marginal elements with headroom now rather than re-trigger a remesh soon) or the element measure falls below the smallest allowed volume (tiny). Marks the element FREE and frees its nodes — repair needs node motion.Two sub-cases:
R1’s tiny test deliberately uses the raw size floor, not the margin-scaled trigger (§7).
R2 — REFINE
Triggers when the plastic-strain increment since the last remesh exceeds
remesh_active_plstrain— the actively growing band, not the fossil band. Marks the element SPLIT: MMG adds resolution, but existing nodes stay pinned, so the band’s plastic strain is carried verbatim and only newly-inserted nodes are interpolated. Default threshold 0.01: two orders below a real shear band (~1), three above the noise floor.R3 — BOUNDARY
A boundary node becomes MOVABLE only where the surface is being reshaped — i.e. an ACTIVE free-surface (TOP) node, gated on surface-process activity (displacement > 1 % of resolution per remesh interval). Everything else on the boundary stays pinned:
R4 — INFLOW SIDE STRIP
Remeshing option 13, INFLOW sides only. Wall-faceted elements plus 2 connectivity rings become FREE and their interior nodes movable, so the ~resolution inflow wall band can coarsen back to its intended size once material moves off the wall. R3 pins all restored side walls; R4 re-frees the inflow band on top of that, while R5’s fixed-side guard keeps the FIXED sides verbatim. Boundary nodes and material-interface nodes are never freed here.
R5 — SIZE RECOVERY
The freeze has no size term, so a quiet wrong-sized element would otherwise be carried verbatim forever. R5 frees a quiet element that is the wrong size in either direction:
remesh_size_recovery_ratio→ free to COARSEN. The envelope is the smallest size MMG’s gradation could legally assign at a node, grown by the gradation factor (1.3, MMG’s default, mirrored here) from every intentionally-fine metric target (wall clamp, plastic refinement, floors) via fixpoint relaxation. The legal size-transition zone and the refined band are thus never touched — only fine mesh with no justification (typically wall-band elements that advected inland and froze at wall size).Guards: skips R2 growing-band elements, top-surface-connected elements, the fixed-side wall band (wall + 4 rings), and — for coarsening only — material-interface elements. A freed element never unpins a SIDE-WALL or BOTTOM node.
R6 — FLATTEN-BROKEN and R7 — COLLAPSE-REGION
5. Required interface edges — inflow front-tracking (2D, option 13)
With material entering through a restored side wall, the layering it carries must survive coarsening. Node pinning alone is not enough: MMG could still collapse a straddling element across a ~1-element-thick layer. Two cooperating mechanisms fix this:
Interface-edge detection. Within the inflow wall band, each element’s dominant marker material type is computed, and every interior edge shared by two band elements of different dominant type — the discrete material-interface line — is handed to MMG as a required edge (tagged with a sentinel reference so the edges can be recognized and filtered back out of the boundary description after adaptation; they are internal, not boundary). The required-edge constraint is used because it is the only tag that reliably keeps the internal line straight — open-boundary and plain reference edges let MMG curve (Bezier) or cross the line, and corner-tagging moves it; both were tested and rejected.
Pre-subdivision. A required edge is frozen — MMG cannot split it — so an oversized element sitting on the interface would stay oversized. Before MMG runs, any interface edge longer than ≈ √2 × the local metric is therefore bisected at its collinear midpoint (line geometry stays exact), both adjacent triangles are split in place, and the new node plus both half-edges are marked required. The split is sized to the neighbourhood-minimum metric, so the fine wall size propagates along the interface as it advects inland. One split per triangle per pass; each pass halves an over-long edge, so it converges.
Together: the inflow material boundary stays a straight, conforming, refinable line of required edges. On the collapse path the edge endpoints are translated through the collapse remap first.
An earlier design — per-element MMG references to make the interface conforming — was abandoned: it produced near-wall sliver elements (confirmed by an A/B comparison with the references off).
6. The metric system
6.1 Frozen base — the initial nodal size field
The scalar metric fed to MMG is anchored to a frozen nodal size field recording each node’s intended element size:
Away from plastic strain the volume ratio is 1, so element sizes are maintained across remeshes instead of drifting toward uniformity. The frozen size field is:
6.2 Plastic refinement
Per element, the target volume is scaled by
1/(1 + coeff · plastic_strain)(max-area convention; edge length scales as that ratio^(1/NDIMS)), withcoeff = mmg_metric_refine_coeff(default 5; 0 disables plastic refinement entirely).6.3 Metric floor (tiny-element hysteresis)
The metric is floored one hysteresis step above the minimum edge length:
floor = h_min · remesh_tiny_margin^(1/NDIMS). Without this, the shear band lives exactly at the minimum and any compression immediately re-fires the tiny-element trigger (a remesh storm). See §7 for the trigger side of the same margin.6.4 Surface-motion anticipation (2D only)
For top-row elements, nodal motion (advection plus the surface-process rate) gives a signed area rate; a shrinking element gets its target volume inflated by
min(8, exp(−rate·τ))where τ is the time since the last remesh. Elements under a fast-moving surface (e.g. a landslide) are rebuilt with room to be crushed instead of at their final size. The cap was raised from 4× to 8× for fast surface events; a divergence-of-velocity variant was tried and reverted. Self-tuning — no config parameter.6.5 Anisotropic inflow-wall metric (2D + 3D)
Gated on remeshing option 13 with
mmg_aniso_wall_ratio > 0: a per-node symmetric tensor metric is built — fine tangential to the inflow wall (h), coarser (h · ratio) in the wall-perpendicular direction; isotropic elsewhere. When present, the tensor replaces the scalar metric in the MMG call. The achievable aspect is capped by MMG’s maximum-size truncation. Ratios in (0,1) are rejected at input (they would refine, not coarsen, the perpendicular axis).6.6 MMG global bounds
h_min = smallest_size^(1/NDIMS) · resolution,h_max = largest_size^(1/NDIMS) · resolution, Hausdorff distance =mmg_hausd_factor · resolution. The gradation factor is deliberately not a config parameter — MMG’s default 1.3 is kept (and mirrored in R5’s envelope).7. Robustness machinery
Tiny-element trigger + hysteresis. A remesh fires when an element measure drops below
smallest_volume / remesh_tiny_margin, where the smallest volume (derived fromsmallest_sizeand resolution) is also the remesher’s size floor. Margin 1: trigger sits on the floor (high remesh frequency). Margin > 1: opens a hysteresis gap, paid for with smaller elements (smaller dt) between remeshes. The same margin sets the metric floor (§6.3) and buffers the quality gate.Post-output quality gate. If MMG’s output contains an element that would immediately re-trigger remeshing (below the minimum quality, or under the tiny trigger plus half the hysteresis gap), a graded region around each bad output element is unfrozen and MMG is re-run:
Up to 4 attempts; a still-bad final mesh is kept with a warning rather than aborting.
Boundary-facet hygiene. MMG re-emits required-element edges as extra unflagged boundary-facet copies. Duplicated facets are merged with their boundary flags OR-combined (without this, an unflagged copy could win the lookup and silently strip a boundary flag — this was a real bottom-boundary-condition-loss bug), and unflagged interior echoes are dropped so they don’t ratchet remesh-over-remesh. Both cleanups run on MMG input and output.
Log taxonomy. Every remesh prints stage-tagged lines reviewers will meet in run logs:
[freeze] conservative remesh (<path>): M elements -> F free + S split + N frozen (R required verbatim + H hinge with movable nodes); P/M nodes pinned.followed by per-rule attribution lines (free:R1/R4/R5/R6/R7,split:R2,nodes:R1/R3/R4/R5);[unfreeze] …— R4 strip and R5 size-recovery frees;[require] pre-subdivided N over-long interface edge(s);[metric] surface-motion anticipation …;[quality] MMG output has N below-min_quality/tiny element(s); unfroze …— gate retries.A surface-churn alarm prints only when an abnormal fraction of top-row nodes ends up movable, and an environment variable (
DES_DEBUG_MASK) dumps the per-entity masks for debugging.8. Configuration parameters
All under the
[mesh]cfg section. Validation:remesh_tiny_margin ≥ 1,remesh_defensive_quality_ratio ≥ 1,remesh_size_recovery_ratio = 0 or > 1,mmg_metric_refine_coeff ≥ 0,mmg_aniso_wall_ratio = 0 or ≥ 1,smallest_size ≤ largest_size.min_qualitymax_boundary_distortionsmallest_sizelargest_sizeremesh_active_plstrainremesh_tiny_marginremesh_defensive_quality_ratioremesh_size_recovery_ratiommg_metric_refine_coeffmmg_aniso_wall_ratiommg_hausd_factoruse_mmg_initmmg_init_coarsening_factormmg_verbose/mmg_debug9. 2D vs 3D applicability
10. Measured dead ends — do not re-propose without new evidence
Designs that were implemented, measured, and rejected
The upcoming PRs carry the implementation; a consolidated policy comment at the top of the remeshing policy code restates §§1–4 and 7 next to the rules themselves and is kept in sync as the living source of truth.
Feedback welcome — especially on naming, on which parts should be split into separate PRs, and on anything above that deserves its own regression test before merge.