Skip to content

Topological offset (topological_offset)

Adds a region at a target distance around a selection of a tagged tet mesh (the 2D offset, on triangle meshes, is refused for now): the boundary of a tag, an expression of tags, or an open curve or sheet. It grows the offset band from the selection and optimizes the mesh until the band's front lies at the target distance, keeping the band's topology throughout.

Tutorial · Spec source · Defaults · JSON Schema

Cite: Topological Offsets, ACM TOG 2025

Required

application

string · required · one of topological_offset

Application name must be topological_offset.

input

string · required

Input mesh, .msh: a tetrahedral mesh (a triangle mesh is refused for now: the 2D offset is being brought up to date). The two pipelines are the same algorithm one dimension apart and read every key below the same way.

Key options

output

string · default "out"

Output file name (without extension).

offset_selection

string · default "!_"

Boolean expression for input simplicial complex to offset. If a single tag (ie 'tag_0') is given, single body mode is used.

offset_output_tags

list of string · default ["newtag"]

Tags to add to elements in the resulting offset region.

Each entry: A tag.

protected_tags

list of string · default []

Set of tags that will not be overwritten by the offset.

Each entry: A tag.

offset_in

bool · default false

Only relevant for single body mode. Whether to create offset inside body

offset_out

bool · default true

Only relevant for single body mode. Whether to create offset outside body

target_distance_rel

float · default 0.01

Target offset distance relative to the bounding box diagonal of the mesh. Ignored if target_distance > 0.

target_distance

float · default -1.0

Target distance for offset. If <= 0 (the default -1): derived from target_distance_rel.

front_conv_rel

float · default 0.1

THE convergence epsilon as a fraction of target_distance, used when front_conv is <= 0; ignored if front_conv > 0. Since 2026-10-21: the config's target_distance, fixed for the whole run -- every growth_mode stage and attempt included, though they run at smaller target. Must not exceed 1 (startup refuses front_conv above target_distance). 0.1 (the default since 2026-10-29, the value of the latest offset runs; 0.025 before, which kept a fully default run at the bar it had, 2.5e-4 x the bounding box diagonal at the default target_distance_rel 0.01). From 2026-09-23 to 2026-10-20 this key was a fraction of the bounding box diagonal (default 2.5e-4): a value written for that meaning now asks for a bar target_distance / diagonal times as large (1/100 at the default target). See front_conv for what the bar gates.

optimize_offset

bool · default true

Run the optimization (optimize_offset: pre-loop setup, the loop's turns, the final pass and the convergence verdict) after the offset is constructed. Default true. false: the run stops at the constructed offset -- the simplicial embedding, the marching and the tag assignment still run -- and writes it as the result. The constructed band lies on the background mesh's own cell boundaries, so its distance to the input complex is set by the input mesh's resolution, not by target_distance; the report then carries only the before/after counts, threads and time -- no converged, offset_level, offset_dhat or optimization_metrics -- and throw_on_nonconvergence is not consulted. The offset potential is built only for the optimization, so with false phi_grid_resolution is ignored (with a warning) and the save_vtu file's energy and distance fields are NaN.

max_rounds

int · default 30

The outer loop's budget, in turns, one turn being split, collapse and swap with smoothing after each, followed by the front test. An upper bound rather than a schedule -- exhausting it is a warning and not an error, because a front that needs more turns is a legitimate outcome to inspect.

num_threads

int · default 0

Number of threads used for parallel execution (smoothing, edge collapse). 0 means single-threaded.

check_manifoldness

bool · default true

After performing offset, check if offset region is manifold

Other options

throw_on_nonconvergence

bool · default false

If true, a run that finishes without meeting the convergence criteria raises an error instead of logging a warning. Default false: a non-converged offset is still a usable one, and the warnings already name which criterion failed. Integration tests set it true so a convergence regression fails the run rather than passing with a warning.

envelope_size_rel

float · default 0.001

Half-width, relative to the bounding box diagonal, of the envelope that holds the held surfaces during optimization; any operation that would push a held face outside it is rejected, and a held vertex is smoothed toward it. 3D: ONE envelope around every held face, built after the simplicial embedding, before the repulsion and the march: during the loop the domain wall and the input complex's boundary; in a TetWild pass (the final pass, and optimize_construction's) every other region boundary as well, at its position then (see use_rest_pose). The offset surface is never in it, but in a TetWild pass it is held to an envelope of its own with this same eps (since 2026-10-20, when offset_envelope / offset_envelope_rel were removed; optimize_construction's pass holds it to half the traced distance instead). This eps is also the derived sizing floor when min_edge_length_rel is negative. 2D: one envelope per region tag, as before. Ignored if envelope_size > 0.

envelope_size

float · default -1.0

Absolute half-width of the envelope that holds the held surfaces (see envelope_size_rel). If <= 0 (the default -1): derived from envelope_size_rel.

offset_field

string · default "euclidean" · one of smooth, euclidean

Which field defines the offset, and so what the front is placed on, what the criterion measures and what the sizing field refines by. 'smooth' is the offset geometric contact potential Phi, with the front on the level set Phi = c: C^2, analytic derivatives, and a level set that bulges outward at reentrant features, so it is a smoothed offset rather than the exact one. 'euclidean' (the default) is the exact distance to the input complex, which makes the residual exact but introduces a gradient discontinuity across the medial axis and a crease at every reentrant feature that no refinement resolves; offset_dhat_factor and the outside-the-support guard stop meaning anything, since it has no compact support. 3D refuses 'smooth' with an error at startup: the 3D energy's band term reads the euclidean distance, and the smooth field has no band term; 'smooth' is 2D only.

offset_dhat_factor

float · default 2.0

Support radius of the smooth offset potential, as a multiple of target_distance. Must be > 1. The offset level set lies strictly inside the support; a band vertex that leaves the support is a hard error.

front_conv

float · default -1.0

THE convergence epsilon, as an ABSOLUTE length in model units; <= 0 (the default -1): derived from front_conv_rel. Absolute or relative: front_conv_rel is a fraction of target_distance (since 2026-10-21; of the bounding box diagonal from 2026-09-23 to 2026-10-20). Startup refuses a value above target_distance: an epsilon coarser than the offset it measures decides nothing, since every front face then reads as resolved from the first turn. THIS IS THE ONLY BAR, in 2D and 3D. The measure it gates is one quantity everywhere: over a face's stencil_order stencil (in 2D a front chord's), the ROOT MEAN SQUARE of the distance to the level set along the field, over target_distance (for 'euclidean' the relative error (Phi - c)/c = (d - target_distance)/target_distance; for 'smooth' the root of Phi = c along grad Phi, since there (Phi - c)/c is not a length and read 3.44x the distance error at offset_dhat_factor 2), taken as a length. A face is resolved when that RMS is <= front_conv, i.e. when the MEAN SQUARED relative error is <= (front_conv/target_distance)^2; a vertex is placed when the same measure at the vertex alone -- the order-0 stencil -- is within it. So the vertex measure and the face measure are ONE measure at two sample counts, which is why vertex_conv and sag_conv, which split them apart on 2026-09-23, no longer exist. The offset surface debug field f_err reports the same number. The 2D front smoothing objective (StencilEnergy2D) has the same zero set but, under 'smooth', charges the field error (Phi - c)/c rather than this distance: about 3.44x it at offset_dhat_factor 2. The 3D smoothing objective is the energy (see offset_amips_weight), which has no face term. WHAT THE LOOP EXITS ON is set by front_measure: under the default 'vertex_ring', every front vertex's ring measure (the RMS of its offset faces' measures, every face weighted equally) within the bar; under 'face', the rule of 2026-09-25, every offset face's measure within the bar. Either way nothing may be unmeasurable, the single-point vertex measure is a diagnostic that decides nothing about termination, and an element over the bar that refinement cannot take (the sizing floor) blocks the exit and is named in a warning every turn it exists. 2D runs the same criterion on its front chords (offset edges in place of offset faces).

stencil_order

int · default 1 · min 0

3D: the lattice below is EXPERIMENTAL_stencil_type "subdivision", the default; "interior" and "fem" (2026-10-27 / 28) have their own lattices for the same order k and need k >= 1 (see there). Order of the sampling stencil on an offset-surface face (3D) or front chord (2D). It sets the convergence measure, and in 2D also the front smoothing energy, which samples the same points; the 3D energy has no face term (see offset_amips_weight). Order 0 is the 3 CORNERS alone, so the measure is exactly the three vertices' own placement error. Order k >= 1 is the vertices of the triangle subdivided k-1 times by 4-way midpoint refinement, plus the centroid of each of its 4^(k-1) sub-triangles: with n = 2^(k-1) segments per side that is (n+1)(n+2)/2 + n^2 points, i.e. 4 at order 1 (the corners and the centroid), 10 at order 2 (the corners, the 3 edge midpoints and the 4 sub-triangle centroids), 31 at order 3, 109 at order 4. On a 2D chord the same rule one dimension down: order 0 is the 2 ENDS alone, order k >= 1 the vertices of the chord cut into 2^(k-1) pieces plus the midpoint of each piece, i.e. 3, 5, 9, 17 points. THE CORNERS ARE IN THE STENCIL, unlike the strictly interior lattice sag_num_samples used, and they have to be: the quantity sampled is a distance to the level set, which at a corner is that vertex's own placement error rather than the identically-zero interpolation error the old measure saw there. That is what lets one stencil replace the separate vertex and sag criteria. COST: every sample is a Phi value, paid in the criterion and the frames, and in 2D also in the operations' energy rules (TopoOffsetTriMesh::tri_energy()) and, with a gradient, in every front solve -- 4 points at the default against 6 for the old default.

front_measure

string · default "vertex_ring" · one of vertex_ring, face

Which measure the loop exits on and refines by, in 3D and in 2D. 'vertex_ring' (the default): at each front vertex v the RING MEASURE r_v = sqrt((1/n_v) sum_f m_f^2), over the n_v offset faces f incident to v whose three corners are front vertices, every face weighted equally, m_f the face measure, the root of face_offset_term() (the RMS over the face's stencil_order stencil of the distance to the level set along the field over target_distance -- for 'euclidean' the relative error (Phi - c)/c -- over the bar front_conv / target_distance). The loop exits when every ring measure is within the bar and nothing is unmeasurable; a vertex with an unmeasurable incident face has no ring measure and blocks the exit. The face and vertex measures are then reported as diagnostics. The halving lowers the sizing scalar at each vertex whose ring measure is over the bar, that vertex alone, under the same floor (max of min_sizing_scalar and min_edge_length / l); a vertex over the bar already at the floor blocks the exit and is named by the [sizing floor] warning. The chord rule is not read in this mode. The operations do not compare this measure (until 2026-09-28 an ops guard compared the ring measure before against after): in 3D the collapse, the swaps and the smoother compare the energy (TopoOffsetTetMesh::tet_energy(), see offset_amips_weight), whose band term is a volume integral of the distance error, not these face measures. From 2026-09-25 to 2026-09-28 this measure weighted each face by its area (A_f); it weights every face equally since. In 2D the same criterion runs on the front chords: r_v = sqrt((1/n_v) sum_e m_e^2) over the n_v front chords e incident to v, m_e the chord measure (the root of edge_offset_term()), every chord weighted equally, with the same exit and the same per-vertex halving; 2D's operations compare the per-cell energy TopoOffsetTriMesh::tri_energy() and its front smoother minimises StencilEnergy2D, the same chords' m_e^2. 'face': the rule of 2026-09-25, kept for comparison. The loop exits when every offset face's (2D: chord's) measure is within the bar and nothing is unmeasurable; the halving lowers the sizing scalar at the corners of every face over the bar whose chord rule still has a target below its corners' scalars. Why the ring measure: the smoother minimises a sum over each vertex's ring while the face exit and refinement are per face, and the two disagree at the margin. Measured on the deliverable cube at target_distance_rel 1e-3 / front_conv_rel 1e-5 under 'face', turns 12-15: the loop never exits because a handful of faces end each turn at 1.00 to 1.19 times the bar, the smoothing having pushed faces from 0.99 to just over the bar while lowering the ring they belong to. On the cube at target_distance_rel 1e-2 / front_conv_rel 1e-4 (the split gate at twice the collapse gate, a key since removed, interleaved_smoothing false, 2 smoothing passes, no alignment term, serial), measured with the area weights and before the per-tet energy: 'vertex_ring' exits after 7 turns with max AMIPS 8.784 and 20724 front faces, 60 of them over the bar (the largest 1.355x) while every ring measure is within it (the largest 0.963x); 'face' exits after 7 turns with max AMIPS 7.814 and 23476 front faces. The debug frames carry the ring measure as point data, front_ring_ratio, in both modes (2D: on the _front.vtu chord mesh).

sorted_marching

bool · default false

Execute the marching in decreasing order of edge length. Increases run time, may increase output mesh quality.

save_offset_correspondence

bool · default false

2D and 3D, and only with optimize_offset false (the run refuses the combination with true). The output .msh carries the node field corr_input_vid, a scalar $NodeData entry for every vertex of the mesh (the retained envelope's own nodes, when the input has an EnvelopeSurface, carry none). The march splits every edge with exactly one endpoint on the input complex and so makes the offset vertices (vertex label 2, the vertices of the offset band that are not on the complex); for each such vertex the field holds the 0-based index of that edge's endpoint on the complex, the input-complex vertex the offset vertex was split off from. Every other vertex holds -1. The index is the output file's own: vertex k is the node with tag k + 1. The optimization splits, collapses and renumbers vertices and does not carry the field, hence the restriction. The prismatic_mesh component reads this file. 3D, memory route: an input that already carries corr_input_vid (an earlier offset's output, with its vid) keeps it, so offsets run one after another hand prismatic_mesh every band's correspondence. Each value holds while its offset vertex keeps its id and its star (its tets, by their corners), from which prismatic_mesh builds the prisms: construction only splits, and only near this run's input complex, so a carried star that changes means the two offsets touch, and the run throws. Without this key the incoming field is ignored, as is any on the .msh route, whose reader drops node data. Default false: the .msh has no node data.

sphere_trace_target_rel_tol

float · default 0.01

In (0, 1). The marching's sphere trace stops at the first point where |d(x) - D| <= this x D, D being the distance the march traces to (target_distance, or half the maximum marchable distance under construction_mode max_marchable_fallback); the placed vertex is that close to that level set along the edge. Smaller is tighter and takes more steps (each step is longer than the tolerance, so at most edge length / (this x D) per edge).

construction_mode

string · default "max_marchable_fallback" · one of max_marchable_fallback, midpoint_fallback

How the marching constructs the offset. Construction first measures the maximum marchable distance, the smallest distance from the input complex of the outer end of any marched edge (an edge with one end in the input complex and the other in the background). Every marched edge's inner end is on the complex, so the level set d = D crosses every marched edge for every D up to that distance. If target_distance is below it, every marched edge is split where d(x) = target_distance, found by sphere tracing from the complex end (to within sphere_trace_target_rel_tol). Otherwise: 'max_marchable_fallback' (the default) traces every marched edge to half the maximum marchable distance instead; 'midpoint_fallback' splits every marched edge at its midpoint. Either way target_distance itself is not changed: the optimization carries the offset surface out to it. Both dimensions.

init_optimize

bool · default false

Default false, as the band-volume runs ran it. 2D and 3D. Construction measures the maximum marchable distance, the smallest distance from the input complex of the outer end of any marched edge (the largest target every marched edge can be traced to). If target_distance is below it, the init_optimize loop is not done: the offset is marched to the target and the loop runs with refinement as usual, and the log says so. If target_distance is not below it, every marched edge is split as construction_mode says (the default traces it to half the maximum marchable distance; midpoint_fallback splits it at its midpoint), the loop first converges under stencil_order with no refinement (the sizing field left as it is), without the final pass, and then the ordinary loop converges under the same stencil_order with its refinement, final pass and verdict. target_distance is never changed. The no-refinement loop's debug frames are led by i.

repulsion_smoothing_passes

int · default 0 · min 0

2D and 3D. At most this many smoothing passes between the simplicial embedding and the march, so that the march can trace to target_distance itself. The march traces every marched edge (one end in the input complex, the other not) to target_distance only if every outer end is farther than it (the maximum marchable distance, see construction_mode); otherwise it falls back. The repulsion vertices are those outer ends -- the one ring of the input complex's vertices minus the complex -- that no input envelope holds. One per-cell energy holds before the march: the energy's AMIPS term (3D: see offset_amips_weight; 2D: w AMIPS, the 2D engine's quality, w = offset_amips_weight, as the front) plus, for each corner v of the cell (2D: face) that is a repulsion vertex joined in that cell to the input complex by a marched edge, O(v) = (max(0, 2 delta - d(v)) / front_conv)^2, delta = target_distance, d the Euclidean distance to the input complex. O is one-sided, so it pushes a vertex out and never pulls one in, and its target is 2 delta so that the march at delta splits each edge with room to spare (the construction's own fallback, half the maximum marchable distance, run backwards). Every vertex is smoothed against the sum of that energy over its ring, so a repulsion vertex's O(v) counts once for every ring cell that carries it; every other vertex is smoothed as in the loop. In 3D no move may raise the sum of the energy over the vertex's ring; in 2D a repulsion move has no veto and every other vertex keeps the veto on the max of the energy over its ring. The passes stop as soon as every outer end is farther than delta + front_conv, i.e. outside the tolerance band. Each pass logs the maximum marchable distance, the outer ends within delta + front_conv, delta and 2 delta, and the max AMIPS; with debug_output a frame repulsion_<k> follows each pass. 0 (the default) = off: no pass, no frame, no log line.

repulsion_rounds

int · default 0 · min 0

2D and 3D. After the repulsion_smoothing_passes (if any) and before the march, at most this many rounds of the loop's operations: split, collapse and swap, each followed by its smoothing passes, shaped as interleaved_smoothing says. No refinement: the sizing field is never lowered, so every operation aims at the base edge length. No split of a marched edge (one end in the input complex): its midpoint would be a new outer end nearer the input. The collapses and swaps compare the per-tet energy of repulsion_smoothing_passes, repulsion terms included, as the loop's rules compare its energy. The input complex stays simplicially embedded, as the march needs it: a collapse or swap that would leave a cell off the complex whose complex vertices span an edge or face off the complex is refused. After every operation pass the construction labels are recomputed from the region tags, and the repulsion vertices follow the operations. Single-body offset_in or offset_out only (the embedding test reads the complex off the cells' labels); other selections, and in 2D a curve group, are refused with an error. The rounds stop as soon as every outer end is farther than target_distance + front_conv. Each round logs its operations' outcomes ([ops accounting], [swap reject], 3D only: the 2D engine has neither; the embedding refusals, which those counters have no name for, are counted on the round's own line), the maximum marchable distance and the max AMIPS; with debug_output a frame repulsion_round_<k> follows each round. The embedding test reads cells beyond an operation's lock, so run the rounds with num_threads 0. 0 (the default) = off.

settle_stall_eps

float · default 0.1 · min 0.0

3D, EXPERIMENTAL_band_volume_rule "surface" only (a non-default value under another rule is refused), with EXPERIMENTAL_settle_refinement. The settle test's stall threshold eps_stuck (Spencer 2026-10-27, HackMD "settlement option 2": TetWild's stuck test on the max offset error across stencil points instead of the max AMIPS across tets). O* = the max over every front face's stencil points (stencil_order, interior) of (d - target_distance)^2; a turn is settled when O* < front_conv^2 (every stencil point within the tolerance; his answer) or O*_prev - O* <= settle_stall_eps (O* - front_conv^2) -- TetOptimizerMesh::optimization_stalled()'s (prev - cur) <= stuck_refine_stall_eps (cur - target). A rise settles. Default 0.1, TetWild's stuck_refine_stall_eps default. The marking, halving and exit of a settled turn are EXPERIMENTAL_settle_mark's, unchanged.

optimize_construction

bool · default true

3D, with optimize_offset (since 2026-10-21; replaces EXPERIMENTAL_construction_tetwild_run, EXPERIMENTAL_refine_construction and EXPERIMENTAL_refine_construction_factor, which the strict spec now refuses). true (the default): before the loop (and before an init_optimize loop), after the 'construction' frame, TetWild's own pass runs on the mesh as constructed (TopoOffsetTetMesh::optimize_construction_pass()). It starts from the sizing field at 1 everywhere: every vertex's sizing scalar is set to 1 first (nothing before the pass lowers it, so a vertex reset there is warned about), and the offset surface's field is not lowered. Every tracked surface is held: the offset surface in an envelope of eps half the distance the march traced it to (d_0 when growth_mode applies, else target_distance) -- in this pass only; the final TetWild pass holds it to envelope_size -- its vertices smoothed like any held vertex, pulled to and kept in that envelope; the domain wall, the input complex's boundary and every other region boundary in the envelope (envelope_size). TetWild's own rules decide: the smoother minimises the raw sum of AMIPS over the one-ring with TetWild's quality veto on the ring's max (TetOptimizerMesh::smooth_after()), and collapses and swaps are judged by the max of AMIPS^3 over the cells they change -- no band term, no summed energy rule, no split gate (EXPERIMENTAL_split_energy_gate), no damping, and no plastic medium whatever use_rest_pose says; TetWild's bare coarsening passes run before the first iteration and after the last. Up to max_iterations iterations of TetWild's operation groups, stopping once max AMIPS < stop_energy. Its operations carry the sizing field (a split's new vertex takes the mean of its edge's scalars, a collapse's survivor the smaller, sizing_collapse_min) and its stall refinement lowers it around the worst cells; the loop starts from the field the pass leaves, with the vertex order recomputed and, under use_rest_pose, the plastic rests stamped again. Logged as [optimize construction] (the envelopes, max AMIPS before and after, the sizing scalar's range after), plus '[energy step] turn 0 start' before it and '[energy step] turn 0 tetwild' after it; its debug frames are labelled cr0F<n>_<operation>, then cr0S_end for the mesh it hands to the loop (DEBUG_remote_output: cr0F_start, cr0F_it<k>). Ignored when optimize_offset is false. false: the loop starts on the mesh as constructed.

growth_mode

string · default "none" · one of none, linear, smart, individual_op_reject

3D (since 2026-10-21; "linear" is the former EXPERIMENTAL_incremental_growth, which the strict spec now refuses with EXPERIMENTAL_incremental_growth_steps). How the loop grows the offset to target_distance from where the march put it. Growth acts only when the march traces the front below the target: construction_mode "max_marchable_fallback" (the default) with target_distance not below the maximum marchable distance M traces every marched edge to d_0 = M / 2 (see construction_mode). Not done -- the loop running once at target_distance as usual -- when the march traces to target_distance itself (the target below M), marches nothing, or cannot trace (a marched edge ends on the complex, so every marched edge is split at its midpoint; a warning); the log says which. Every loop below is the loop as configured, at its own target: the same energy (its band term reads that target), smoothing, settle test, refinement and exit, and the same front_conv (front_conv_rel is relative to the configured target_distance); before each loop the offset field, the per-piece fields and the exact band integral are rebuilt at its target; the mesh and its sizing field carry over, as does every length fixed when the parameters were read (envelope_size, the base edge length, min_edge_length). "none" (the default): one loop, at target_distance. "linear": the loop runs N + 1 times (N = linear_growth_steps): stage k = 0, 1, ..., N at the target d_0 + k (target_distance - d_0) / N, so stage 0 at d_0 and stage N at target_distance exactly, each up to max_rounds turns. Stages 0 to N - 1 each run until the loop converges and end there, without the final TetWild pass or the verdict; stage N is the ordinary loop, with both. A stage before the last that does not converge within max_rounds turns stops the run: the later stages are not run, and the verdict and the report measure the mesh that stage left against target_distance. Logged: one [incremental growth] line at construction, '======== [incremental growth] stage k / N' before each stage; each stage numbers its turns from 1, and its debug frames are led by g<k> (stage N's by nothing). "smart": first the loop at d_0 (level 0), up to max_rounds turns; if it does not converge the run stops with an error. The mesh is then stored in memory (TopoOffsetTetMesh::take_snapshot()) and an attempt runs at target_distance: the loop for up to smart_growth_attempts turns. An attempt succeeds when the loop converges and no offset face is folded at that point (its normal, out of the band, more than foldover_angle from grad d; checked once, on the converged mesh). A failed attempt puts the stored mesh back (restore_snapshot()), and the next is tried halfway: after j failed attempts from the same converged distance d_b the target is d_b + (target_distance - d_b) / 2^j. A success below target_distance makes that target the new d_b: the mesh is stored again and the next attempt is at target_distance again. smart_growth_max_fails failed attempts from one d_b stop the run with an error. The attempt that succeeds at target_distance ends the run as the ordinary loop does (the final TetWild pass when max AMIPS >= stop_energy, the verdict); an attempt at a smaller target ends without them. Logged: one [smart growth] line at construction, '======== [smart growth] level L ...' before level 0 and before each attempt, one result line per attempt (converged or not, folded faces), a 'reverted' line after each failure and a summary at the end; each loop numbers its turns from 1, and the debug frames are led by s0 (level 0) and s<L>a<A> (level L's attempt A; a failed attempt's frames stay). "individual_op_reject" (since 2026-10-21): the loop at d_0 (up to max_rounds turns), then the loop at target_distance, nothing between -- "linear" with one step, logged and framed as it, and stopped the same way when the loop at d_0 does not converge -- and in every loop of the run (not in a TetWild pass: the optimize_construction pass and the final pass run as usual) each collapse, flip of the offset surface and smoothing move is refused when it raises the number of folded offset faces among the faces it changes (folded: the face's normal, out of the band, more than foldover_angle from grad d at its centroid; a collapse: the faces through its two ends before against those through the survivor after; a flip: its two old faces against its two new ones; a smoothing move: the faces through the vertex before and after). An operation near an existing fold may keep or remove it. Splits are not checked. Logged per operation group as [op reject] (collapses, flips, smoothing moves refused). Refused with construction_mode "midpoint_fallback" (it never traces below the target) and with init_optimize true (both decide the loops after such a march).

linear_growth_steps

int · default 4 · min 1

3D, growth_mode "linear" (since 2026-10-21; was EXPERIMENTAL_incremental_growth_steps): N, the number of equal steps from the distance the march traced the front to, d_0, to target_distance. The loop runs N + 1 stages, at d_0 + k (target_distance - d_0) / N for k = 0, 1, ..., N (see growth_mode); N = 1 is two loops, at d_0 and at target_distance. Must be >= 1; a value other than the default with another growth_mode is refused with an error. 4 (the default): stages at d_0, then a quarter, half, three quarters and all of the way from d_0 to target_distance.

smart_growth_attempts

int · default 2 · min 1

3D, growth_mode "smart" (since 2026-10-21): the turns each attempt toward a larger target gets to converge (level 0, the loop at d_0, has max_rounds). Must be >= 1; a value other than the default with another growth_mode is refused with an error. 2 (the default).

smart_growth_max_fails

int · default 10 · min 1

3D, growth_mode "smart" (since 2026-10-21): how many attempts from one converged distance d_b may fail -- each failure halves the step toward target_distance -- before the run stops with an error. Counted per converged distance: a success starts the count again. Must be >= 1; a value other than the default with another growth_mode is refused with an error. 10 (the default): the last attempt from d_b is at d_b + (target_distance - d_b) / 2^9.

foldover_angle

float · default 30.0 · min 0.0 · max 180.0

3D. The angle, in degrees, above which an offset-surface face counts as a foldover: the angle between the face's normal, oriented out of the band (away from its band cell's fourth corner), and the field's normal, the unit gradient of the distance d to the input complex at the face's centroid (TopoOffsetTetMesh::front_face_field_angle()). 0 is a face facing exactly along the field, 180 one facing straight back at the input. A face whose angle cannot be measured (no band cell, zero area, or a zero gradient) is not a foldover. Read by the debug frames -- each _off file carries the per-face cell fields field_normal_angle (the angle; NaN where unmeasurable) and field_angle_foldover (1 over this threshold, else 0; named offset_foldover until 2026-10-26, when offset_foldover became the flag of the two faces of every real fold, the real folds of EXPERIMENTAL_exit_no_folds); the per-vertex offset_foldover field the frames carried until 2026-10-20 (an endpoint of a front edge whose two faces met within 30 degrees) is gone -- and by the end of the run, where a warning [foldover] counts the folded offset faces of the mesh about to be written, with the worst angle and where it is. In the optimization this test is read by growth_mode "smart"'s fold-free check, growth_mode "individual_op_reject"'s operation rule, and EXPERIMENTAL_exit_no_folds, which holds the exit only for a real fold with a face over this angle; nowhere else. A second, diagnostic-only field pair on the _off files, dihedral_angle (the band-side dihedral across the face's most folded edge, 180 flat) and offset_foldover_dihedral (1 where it is below this angle or above 360 minus it), uses the same threshold and decides nothing (since 2026-10-21). 2D still labels vertices by its own rule. 30 (the default).

save_vtu

bool · default false

Save .vtu of output mesh

phi_grid_resolution

int · default 0

Samples per side of the grid the smooth offset potential is written on, as <output>_phi.vtu, for the viewer. 0 disables it. The grid is the plane through the box centre normal to the box's shortest extent (the whole domain when the domain is planar). Carries phi, phi_residual_length and euclidean_distance per vertex; warp by phi to see the field as a height surface. Written only when the optimization runs (optimize_offset true): the potential is built for it alone.

num_smoothing_passes

int · default 1

Smoothing passes after the ONE combined split+collapse+swap round when interleaved_smoothing is OFF (the default is OFF since 2026-10-29, ON from 2026-09-28) -- in the loop (3D since 2026-09-25, 2D since 2026-10-05), and in the shared optimization loop, which in this component runs only in the TetWild passes, 2D and 3D alike. The default turn is therefore split, collapse, swap, then 1 smoothing pass (2 before 2026-10-29). Not read when interleaving is on: the count is then interleaved_smoothing_passes, everywhere. TriWild's key; TriWild's default is 2.

length_rel

float · default 0.05

Target edge length relative to the bounding box diagonal. Used to bound which edges edge collapse is allowed to touch (only edges shorter than 4/5 of this length are collapsed). Ignored if length >= 0.

length

float · default -1.0

Target edge length (absolute). If < 0, computed from length_rel.

stop_energy

float · default 100.0

Target AMIPS quality for the elements. Default 100, matching the tetwild component -- the final TetWild pass is TriWild/TetWild and stops where they stop. Do not lower it far: a target the operation set cannot deliver turns the split pass into an unbounded refiner. Edge collapse will not push an already-on-target region's quality back above this. In 3D it also scales the energy's AMIPS term, offset_amips_weight * vol * (AMIPS / stop_energy)^3 (see offset_amips_weight).

max_iterations

int · default 80

Cap on the shared TriWild/TetWild loop, which runs only in the TetWild passes: the final pass, and in 3D the construction pass (optimize_construction). The loop exits as soon as max AMIPS is under stop_energy, so this is an upper bound.

interleaved_smoothing

bool · default false

Run split, collapse and swap as three separate rounds with interleaved_smoothing_passes smoothing passes after each, instead of one combined round followed by num_smoothing_passes. DEFAULT FALSE since 2026-10-29, the setting of the latest offset runs (true from 2026-09-28; it was false from 2026-09-25, when the smoothing was 63% of a turn and 5 of its 6 passes near no-ops; the single energy, the exact Hessian and the relative gradient tolerance made a front solve 2-3 Newton iterations instead of 10). Measured on the cube with the single energy, mix vetos: serial target 1e-2 / tolerance 1e-4, combined 7 turns / 22396 faces / 215 s, interleaved 6 / 20102 / 203 s; 10 threads, combined 7-9 turns / 22260-25416 faces / 55-98 s, interleaved 6-7 / 19870-21186 / 60-75 s; 10 threads 1e-3 / 5e-5 (neither resolved in 10 turns), combined 95040 faces / 330 s, interleaved 75826 / 390 s. The gain is in the late turns: the front the exit test judges has been re-placed after every operation, so fewer vertices are over the bar and halved.

interleaved_smoothing_passes

int · default 2

Smoothing passes after each operation group (split, collapse, swap) when interleaved_smoothing is on -- in the shared TriWild/TetWild loop (the TetWild passes) and in the loop. Default 2, against the shared engine's own 1: each smoothing pass is one placement sweep of the front, so at one pass per group the front contracts about half as fast for the same final result.

colored_smoothing

bool · default false

Parallel smoothing (num_threads > 0) by color class: the vertices are split into classes of pairwise non-adjacent vertices, smoothed one class after another, each class fully in parallel without locks. The result does not depend on the number of threads. false: the locked, partitioned smoothing pass, whose result depends on the thread schedule. Serial runs (num_threads 0) are the same either way. Default false for the offset: on the Geneva mechanism base (inward, target 1e-3 of the largest body, 2026-10-12) the colored order left the front folded -- 129-179 vertices flagged by offset_foldover after turn 1 against 0-6 with the partitioned pass, one thread or six, the same count with one thread as with six, so it is the order and not a race; on the outward cube with every other key at its default (10 threads, 2026-10-13) colored smoothing did not resolve in 40 turns and left 51 flagged vertices standing from turn 15, where the partitioned pass resolved at turn 20 with none in any frame.

pre_smooth

bool · default false

The loop only, not the final pass. true: before the first turn, run one smoothing block on the mesh as constructed -- the same block each operation group is followed by: num_smoothing_passes sweeps under interleaved_smoothing false (the default) and interleaved_smoothing_passes under true, 2D and 3D alike -- (2D: with the plastic rests stamped before it and the released-boundary envelope refreshed after it), so the front is carried toward the level set once before the first split pass measures it. false (the default): the loop starts with turn 1's split pass on the constructed mesh.

coarsen_pass

bool · default false

Run the post-optimization coarsening pass, as TriWild and TetWild do. It trades elements for nothing but the guarantee that the result is still good, so topological_offset holds it to an absolute bar -- both AMIPS and the offset residual inside tolerance after each collapse -- rather than the non-degrading bar the main loop uses. Off by default: the pass can dominate total runtime, and being the last thing to run before the final convergence check, anything it degrades is reported as a quality failure rather than a coarsening one. 3D: EXPERIMENTAL_coarsen_guard picks what judges a coarsening collapse (the absolute bar above is its default, "bar").

coarsen_unbounded

bool · default true

Coarsen as far as the quality guarantee allows, instead of stopping at the target edge length. The pass then answers how few elements can hold this max energy, which is more aggressive than it sounds because the answer ignores how big the elements become; measured over the registered models it roughly doubles the cell reduction while raising max energy on none of them. On by default. Turned off, it leaves alone any edge already at or past the collapse threshold, since collapsing it only makes its neighbours longer. The sizing field is deliberately not applied either way: honouring it here would leave the pass unable to undo refinement that turned out to be unnecessary.

coarsen_max_rounds

int · default 2

Cap on the coarsen/smooth alternation; the pass also stops as soon as a round accepts no collapse. A round does not exist to finish what the previous one started: within a round the collapse pass already runs to a fixed point, so repeating the collapse alone finds nothing. What a round adds is the global smoothing in between, which moves that fixed point. Returns decay fast, and the default of two carries the large majority of the coarsening for a fraction of what the later rounds would cost.

coarsen_global_smoothing_passes

int · default 1

Ordinary whole-mesh smoothing passes run between coarsening rounds. This is what makes a second round worth running at all -- see coarsen_max_rounds -- so setting it to 0 collapses the rounds to one.

coarsen_smooth_ring

int · default 1

How far the local smoothing after each coarsening collapse reaches, in rings; the pass locks one ring wider than this. Deliberately 1 rather than TriWild's and TetWild's 2: a smaller ball perturbs less per collapse, so more collapses clear the offset's absolute coarsening bar.

coarsen_local_smoothing_passes

int · default 0

Smoothing sweeps over the ring inside each candidate coarsening collapse, before its max energy is measured, so the collapse is judged on a relaxed neighbourhood rather than on the configuration it lands in. 0 by default: the ring is large enough that even two sweeps cost hundreds of nonlinear solves per candidate, and turning it off costs almost no cell reduction because the pass runs to a fixed point and finds another candidate instead. At 0 the only relaxation is the global smoothing between rounds, which has no per-operation accept test of its own -- see the forced quality veto in TetOptimizerMesh::coarsen_mesh().

stuck_refine_stall_eps

float · default 0.1

Fire the sizing refinement when an iteration's improvement is small next to the distance the metric still has to cover: (prev - cur) <= stall_eps * (cur - target). 0 means only when it does not improve at all.

stuck_refine_cooldown

int · default 1

Iterations to skip after a sizing refinement before another may fire. Default 1, matching the tetwild component: the refined sizing needs one split/collapse round to act before the stall detector can fairly judge it. 0 allows one every iteration.

stuck_refine_num_worst

int · default 0

How many worst faces seed the refinement. 0 means every face above the filter threshold, which is TriWild's default.

stuck_refine_rings

int · default 0

Vertex rings grown around each seeded face before the sizing scalar is lowered.

stuck_refine_factor

float · default 0.5

Multiplicative reduction of the per-vertex sizing scalar per refinement.

stuck_refine_min_scalar

float · default 0.001

Floor on the per-vertex sizing scalar.

stuck_refine_gradation

float · default 2.0

Neighbouring sizing scalars may differ by at most this factor. The smoothing only ever lowers a scalar, spreading refinement outward. <= 1 disables it.

stuck_refine_force_split

bool · default true

On a stall, also split each seeded face's longest edge once, bypassing the length gate and without touching the sizing field.

sizing_collapse_min

bool · default true

What the surviving vertex of a collapse keeps as its sizing scalar. true (the default): the smaller of the two, which is the shared engine's rule. false: its own scalar, restored after the base collapse. The min rule carries substantially more vertices and lengthens the placement loop where the front travels, because the offset's sizing follows a moving front and has to relax behind it, which a scalar that only ever falls cannot do.

min_edge_length

float · default -1.0

l_min: the shortest edge the sizing field may ask for, in absolute units. If < 0, derived as min_edge_length_rel * target_distance -- tied to the offset distance rather than the bounding box, because the offset is what has to be resolved. This is a floor on REFINEMENT, so raising it makes the offset coarser (paper Fig. 18).

min_edge_length_rel

float · default 0.1

l_min as a multiple of target_distance, used when min_edge_length is negative. 0.1 (the default since 2026-10-29): l_min = target_distance / 10, the value of the latest offset runs. Negative (the default before) derives it from the envelope epsilon, envelope_size -- the offset surface's envelope eps too since 2026-10-20, when offset_envelope (until then this floor's source) was removed -- as a multiple of target_distance: TetWild's own floor (cap the sizing field below by the envelope eps, Sec 3.2) stated in the offset's units, since the front is only held to within eps and edges shorter than eps cannot buy fidelity. A pure anti-runaway rail, far below the chord length any tolerance actually needs.

min_sizing_scalar

float · default 0.001

Lower bound for the per-vertex sizing field (see max_sizing_scalar). Refinement stops once a vertex's sizing scalar reaches this fraction of the base target length.

max_sizing_scalar

float · default 1.0

Upper bound for the per-vertex sizing field, as a multiplier on the target edge length (splitting_l2/collapsing_l2). 1.0 means never coarser than the base target length.

sizing_gradation

float · default 2.0

Gradation cap for the sizing field: neighbouring vertices' sizing scalars may differ by at most this factor. After each refinement pass the refined vertices' lower sizing scalar is propagated outward -- monotone, only ever lowering a neighbour's scalar -- so the mesh does not jump straight from fine to coarse. <= 1 disables gradation. Only read when sizing_gradation_mode is 'ring'.

sizing_gradation_mode

string · default "ring" · one of ring, distance

How a lowered sizing scalar spreads to the vertices around it, at every place the offset lowers the field (the sag rule, the stuck refine). 'ring' (the default): the base gradation_smooth_sizing -- walk outward over mesh neighbours, capping each neighbour at sizing_gradation times the scalar it came from, so the field grades by mesh rings and a fine seed in a coarse mesh drags a shell of rings down with it. 'distance': TetWild's adjust_sizing_field gradation -- walk outward from the seeds and multiply every vertex within R = 1.8 l of its nearest seed by the linear ramp 0.5 + 0.5 dist / R (halved next to a seed, untouched at R), stopping at R; the seeds keep the value they were just given and nothing outside the ball changes. The refinement then reaches a seed geometrically through the split pass (each split child takes the mean of its parents' scalars) instead of by rings, so a fine target stays local to the seed however coarse the mesh around it is.

split_high_valence_threshold

int · default 0

Incident-cell count above which a vertex in a split edge's link accepts only one valence-increasing split per pass, or 0 to disable. Spreads refinement instead of letting it pile onto one vertex.

project_line_search_steps

int · default 12

Bisections tried by the projected line search before it gives up on a vertex: the step along the Newton direction is halved this many times, each candidate projected onto the input, and the first that does not invert and lowers the worst incident element is taken.

project_line_search_nested_steps

int · default 0

After the projected line search gives up, revisit each candidate and bisect between the interpolated point and its projection this many times, taking the longest step toward the input that still does not invert and still lowers the worst element. Lets a vertex whose one-ring cannot tolerate a full projection still travel toward the input, instead of being refused every pass from the same place. 0 disables the pass.

offset_amips_weight

float · default 1.0 · min 0.0

Under EXPERIMENTAL_band_volume_rule "surface" w multiplies the AMIPS term only (the offset term is unweighted, 2026-10-28): w is the ratio of the two terms. The weight w of AMIPS in the per-cell energy. 3D: THE energy E is the sum over the cells of tet_energy (TopoOffsetTetMesh::tet_energy()) = (w / stop_energy^3) vol AMIPS^3 (AMIPS against the cell's rest shape for a plastic cell under use_rest_pose), plus for a band cell b = vol * min over the input triangles P of the mean over its four corners of (d_P - target_distance) / target_distance, d_P the distance to triangle P alone (EXPERIMENTAL_band_volume_rule "corner_bound"; "exact", the default, takes the cell's closed-form integral of (d - target_distance) / target_distance); MAX_ENERGY where the engine cannot score the cell's AMIPS. b is the cell's part of the integral over the band of (d - target_distance) / target_distance, as an upper bound that no split raises (d_P is convex and d <= d_P). Both terms are volumes, so E converges under refinement; a cell at the stop energy costs about w vol in AMIPS. Every operation compares the SUM of tet_energy over the cells it changes, before against after: a collapse may not raise it (a tie passes); a swap -- 3-2, 4-4, 5-6, the face swap, and the 4-4 / 5-6 case search and the face swap's gate that pick and gate the cells it makes -- must strictly lower it; a smoothing move may not raise it over the vertex's ring; a split is judged only under EXPERIMENTAL_split_energy_gate. The engine's own AMIPS^3 rules for collapses and swaps are off. The smoother minimises the vertex's part of E: the AMIPS term over its ring and, unless an input envelope holds the vertex, the band term of its band cells. Needs one input region, an input complex of triangles only, and offset_field 'euclidean' (refused otherwise). Why the corner bound with the min over all triangles, and why a vertex held on the input carries no band term: see TopoOffsetTetMesh::band_cell_term() and smoothing_extra_energy(). DEFAULT 1, the weight the band-volume runs used (the eight-case suite of 2026-10-09 among them). The default before, 1e-4, was chosen 2026-09-30 for the energy this replaced (AMIPS^3 plus the front faces' squared measures, compared by maxima); here it would make the AMIPS term 1e-10 vol AMIPS^3. 2D: the same weight multiplies AMIPS (the 2D engine's cell quality) in tri_energy = w AMIPS + the offset terms of the front chords, and in the 2D front smoother.

smoothing_mode

string · default "projected" · one of projected, exact

How smoothing places a surface vertex. 'projected': smooth with AMIPS alone as if interior, then walk back toward the start projecting each candidate onto the input; accept the first projected candidate that does not invert and strictly lowers the worst incident element, else do not move. Lands exactly on the input; no weights. 'exact': minimize w_amips * AMIPS + (1-w_amips) * (d/eps)^2 with the true region-wise Hessian of the distance to the piecewise-linear input; sliding is free where the input is flat, held at corners, constrained along edges and curve segments. Rests a w_amips-proportional distance off the input.

w_amips

float · default 0.0001

Relative weight of the AMIPS quality term against the envelope term during smoothing of envelope-held vertices (the engine's input-surface envelope term); the envelope weight is 1 - w_amips. The front objective does not read it, in 2D or 3D: in 3D the objective is the vertex's part of the energy (see offset_amips_weight); in 2D AMIPS enters at offset_amips_weight and the offset term at 1 / (front_conv / target_distance)^2, i.e. in units of the tolerance -- the two parts of the per-cell energy.

perform_sanity_checks

bool · default false

Check after every operation pass that no element is inverted and no tracked-surface simplex left its envelope. Slow; for debugging.

use_rest_pose

bool · default false

3D. Which AMIPS the energy (see offset_amips_weight) uses for the cells outside the offset band and the input complex during the loop; the band and the complex are always measured against the regular tet. true: the plastic medium -- each such cell's AMIPS term is vol A^3 with A its AMIPS against its rest shape, stamped when the loop starts, after every smoothing pass (between passes and after the last, not before the first), and, in the operations, on every cell a split or a swap creates or a collapse changes, before that operation's energy rule reads it; so every rule reads up-to-date rests, and a smoothing pass resists only what it moves itself. When an operation's cells before and after are all plastic (no band or input-complex cell), its energy is exactly the same before and after (A = 3 everywhere, one total volume), which floating point does not reproduce (TopoOffsetTetMesh::plastic_tie()). The equality is taken instead: such a swap is refused outright, and such a collapse is not gated by the energy, only by the length gate, the inversion test and the other refusals. Logged per group as [plastic tie]. The rules (tet_energy()), the swap candidates and the smoother all read the same term. false (the default): the regular tet for every cell. Off in the final pass either way. Envelopes, either way: during the loop only the domain wall and the input complex's boundary are held (envelope_size); every other region boundary is a tracked face nothing holds, and is held, as the loop left it, only in a TetWild pass. Replaces deform_others (removed 2026-10-13; 2D hard-wires its old default, false, until the 2D port).

envelope

string · default ""

Memory route only: the name of the input mesh that holds the envelope surface of a tagged mesh input -- the envelope mesh simwild returns with its result. On the file route, the .msh input's own EnvelopeSurface group plays this role, and this option is ignored.

Run control

input_dir

string · default ""

Directory where the input files are located. This is injected by the application and should not be set by the user.

num_threads

int · default 0

Number of threads used for parallel execution (smoothing, edge collapse). 0 means single-threaded.

log_file

string · default ""

Logs are not just printed on the terminal but also saved in this file.

report

string · default ""

A JSON file that stores information about the result and the method execution, e.g., runtime.

Debug options

Diagnostics: slow checks and extra output files. (4)

DEBUG_stencil · bool · default false

DEBUG (2026-10-29), 3D, with DEBUG_output (or DEBUG_remote_output; refused otherwise). true: every debug frame also writes <frame>_stencil.vtu (and <output>_stencil.pvd lists them), one VTK vertex cell per stencil point of every offset face (offset_surface_faces() order times the face stencil: stencil_order, EXPERIMENTAL_stencil_type; a point shared by two faces appears once per face), with point fields stencil_error r^2 (r = (d - target_distance) / target_distance), error_over_bound (d - target_distance)^2 / front_conv^2 (>= 1 out of tolerance), signed_error r, distance_over_target 1 + r, sq_error (d - target_distance)^2 (its max is O*, the "surface" rule's settle metric), face and point. Not written before the loop's offset potential exists, nor for a mesh without offset faces. false (the default): no _stencil file.

DEBUG_output · bool · default false

Write debug VTU frames of the evolving mesh, in both dimensions: one frame after every operation pass and every smoothing pass, as sequential <output>_NNNNN.vtu files so ParaView loads them as one time series, plus <output>frames.txt carrying one 'NNNNN<tab>r<turn><phase><pass><op>' line per frame, which the viewer scripts read for their labels; in 3D also <output>_energy.txt, one 'NNNNN<tab>label<tab>band term<tab>AMIPS term' line per frame, the energy's two parts (see offset_amips_weight). Expect hundreds of frames on a real model; observational only, the frames cannot change the run.

DEBUG_remote_output · bool · default false

DEBUG (2026-10-20), 3D. A sparser debug-frame schedule for runs on a machine short on disk space. true OVERRIDES DEBUG_output: it is set to true, so everything debug output computes runs as usual (the frames carry the same fields, <output>_input_complex.vtu, <output>_energy.txt and <output>_frames.txt are written), but frames are written only at these points: the mesh as constructed, just before the optimization (label 'construction'); at the start of each TetWild pass ('<prefix>r<turn>F_start': optimize_construction's before the loop, the final pass after it) and after each of its iterations ('<prefix>r<turn>F_it<k>'); after the construction TetWild pass, before the loop (its end frame); after every turn of the loop ('<prefix>r<turn>S_end': one frame per split / smooth / collapse / smooth / swap / smooth cycle, after the turn's refinement; every growth stage and the init_optimize loop included); and the final mesh ('final', after the final pass when one runs). Not written: the input, the construction stages before the constructed mesh, the repulsion passes, and every per-operation and per-smoothing-pass frame. false (the default): DEBUG_output decides, with its usual frames.

DEBUG_crossings · bool · default false

Log-only: after every operation pass and every smoothing pass, count the front vertices whose ring measure crossed the bar (from <= 1 to > 1, and back), plus new vertices already over it; inside the front smoother, classify each accepted move whose ring-measure crossing it caused as the moved vertex's own or a front neighbour's. Changes no result; costs an exit-measure evaluation per pass and a local one per front move. 2D and 3D.

Experimental options

Under development: they may change or go away. (14)

EXPERIMENTAL_stencil_type · string · default "subdivision" · one of subdivision, interior, fem

3D (2026-10-28, Spencer: "standard fem triangle points"). Which points stencil_order's face stencil holds. "subdivision" (the default): the corners plus the subdivision lattice described under stencil_order, which the face measures read before this key existed (order 0 the corners alone). "interior" (2026-10-27, Spencer: the surface integral's stencil, every point interior): the strictly interior lattice (i, j, l) / (k + 2), i, j, l >= 1, k (k + 1) / 2 points -- 1 (the centroid), 3, 6, 10, 15 for k = 1..5 -- none on an edge or a corner. "fem": the nodes of the degree-k Lagrange triangle, the lattice (i, j, l) / k with i + j + l = k, i, j, l >= 0, (k + 1) (k + 2) / 2 points -- order 1 the three corners, order 2 the corners and the edge midpoints, order 3 the corners, two points per edge and the centroid; 3, 6, 10, 15, 21 for k = 1..5. The points are weighted by their LUMPED AREA WEIGHTS (Spencer, 2026-10-28: "the area weights are split up properly"; no key): the composite trapezoid rule on the face cut into k^2 sub-triangles whose corners are the points, so a point carries 1 / (3 k^2) of the face's area at a corner, 1 / k^2 at an edge point and 2 / k^2 inside (order 1: a third each); a face's term is its area times the weighted mean of its points. A corner or edge point is shared with the neighbouring faces and gathers its weight from each, one third of the area of the sub-triangles around it. The weights are positive and exact for affine functions, so the energy's offset term converges to L int (d - delta)^2 / front_conv^2 dS at second order in the point spacing h / k, under mesh refinement and under a higher order alike (the interior stencil's plain mean converges under mesh refinement but only at first order as the order rises). "interior" and "fem" need stencil_order >= 1 (refused otherwise); their points are weighted as said, "subdivision"'s and "interior"'s by a plain mean. Read everywhere the face stencil is: EXPERIMENTAL_band_volume_rule "surface"'s face term (the energy, its operation rules, the front solve), its O* settle test and O_g, every 3D face measure (ring and face measures, settle_mark "rms"), the face measures' mean weighted the same way, and the debug frames' _stencil file (DEBUG_stencil) (one point per face and stencil point, so a shared point appears once per face holding it; the O* max reads no weight). 2D does not read it.

EXPERIMENTAL_split_energy_gate · bool · default false

EXPERIMENTAL (2026-10-09), 3D. Every split of the optimisation loop (not the march's) is committed only if the energy E, summed over the cells it changes -- the parents before, the children after -- strictly falls, judged right after the split and before any smoothing; otherwise it is rolled back. A split never raises the energy's band term, so a split passes where the bound tightens by more than the AMIPS term rises (bisecting a well-shaped tet makes worse-shaped children), and is refused where the bound is already exact. Strict, since every split adds cells. What proposes splits is unchanged (the sizing field and its refinement). false (the default) = splits are not judged by the energy.

EXPERIMENTAL_band_volume_rule · string · default "exact" · one of corner_bound, exact, front_flux, exact_swept, surface

EXPERIMENTAL (2026-10-12), 3D. The band term b(t) of a band cell in the energy (see offset_amips_weight; TopoOffsetTetMesh::band_cell_term()), the cell's part of the integral over the band of (d - target_distance) / target_distance. "corner_bound" (the default until 2026-10-29): vol times the min, over all input triangles P, of the mean of (d_P - target_distance) / target_distance over the cell's four corners, d_P the distance to triangle P alone: an upper bound on the cell's part (d <= d_P, and the convex d_P lies below its linear interpolant) that no split raises (a child can use its parent's minimiser, whose finer interpolant lies lower); exact where one triangle's distance is affine on the cell. "exact": J_t / target_distance, J_t = int_t (d - target_distance) dV in closed form (ExactBandIntegral, a C++ port of the Python reference exact_mesh.integrate_mesh): the cell is cut by the local regions of the input's faces, edges and vertices into convex pieces on which d is one plane, line or point distance, each integrated exactly (Euler's theorem for homogeneous integrands and the divergence theorem on the faces; two convergent series with explicit tail bounds for faces nearly parallel to an input edge, stopped at each edge's own ratio). Where the border between two competing features' regions is curved (a face and an edge, or an edge and a vertex, inside one piece: "rule (a)") the piece takes the smaller of their integrals, an upper bound: 0.15-0.35% of the band volume on Thingi 100026, 0.01% on the cylinder, none on the cube and prism. The front smoother descends the same J with its exact gradient and Hessian in the moving vertex (Reynolds' transport theorem over the cell's faces through it); on a rule-(a) piece they are the bound's, and at a kink of d lying on a cell face the Hessian is the one-sided limit for the vertex moving into the cell. A split leaves the band's sum of J unchanged up to rounding and rule (a). Measured (mac-mini, 2026-10-12) against the Python on the 1020 stored band cells of five cases and 21160 random band cells: J agrees to 1.4e-12 relative (the problem's own conditioning: the Python's spread over the 24 corner orders of the same cells is as large), gradient and Hessian to 7e-11 (two of 4800 corner evaluations at 6e-6 in the Hessian, a cell face on an input edge where both are 2e-6 off the one-sided finite difference). Cost per cell: value 2.4-6 us, value with gradient and Hessian 3-43 us, against the corner bound's 0.5-1 us. Cube outward, 3 turns, serial, without refinement, against "corner_bound", the config otherwise the same (measured 2026-10-12 on ee18e19a1d, before held positions were picked by E): turns took 26.7 / 7.5 / 6.2 s against 6.7 / 2.9 / 2.5 s (smoothing 30.6 s against 5.3 s, mean Newton iterations 2.09 against 2.16); no smoothing pass raised E; every split changed the band term by at most 8e-15 relative (the corner bound's: up to 1.3e-3, the bound tightening), the in-loop check that the integral is exact; max AMIPS 16 against 39, the ring measure's max 12.8 against 5.1 bars (worst vertices near the cube's corners). "front_flux" (2026-10-13): the same J_t for every cell the operations, the veto and the energy read ("exact"'s value; a cell's part of the front form is J_t by the divergence theorem), and the front smoother's band term in the front form: per input feature (face, edge, vertex) a field G with div G = d - target_distance, tangent to the feature's region planes and 0 on the input (face (d^2/2 - delta d) n, edge (d/3 - delta/2)(x - its projection on the edge line), vertex (d/4 - delta/3)(x - v)), so the band integral is the flux of G through the band's boundary faces plus, per cell, the jump D_t of G.n across the bisectors (where two features are nearest) inside it. The vertex's band term is the flux through the band's boundary faces through it plus D_t of its flagged band cells, divided by target_distance: "exact"'s term up to a constant in the vertex position (the flux through the faces not holding it). Its gradient is the sum over those faces of n int_f (d - delta) phi dA (phi the vertex's hat function; Reynolds' transport theorem), its one-vertex Hessian the derivative of that, with no D_t term (d is Lipschitz). D_t is computed only for a cell flagged from the input alone: the overlaps of two features' regions within 2 target_distance of the input (and on the offset side of both), in a box tree; a cell whose bound on d exceeds that reach is flagged too; a false flag gives D_t = 0 exactly. Each piece of a face or cell is labelled with its nearest feature exactly (the exact range of the difference of squared distances, a quadratic, over the piece; planar bisectors split it); a curved bisector (face-edge, face-vertex, edge-vertex, skew edges) that changes sign on a piece is an error: the run stops with the feature pair and the position, as no approximation is made ("exact" bounds such a piece, rule (a)). The offset side as "exact": only the parts on the offset side of the input count. The exit test is unchanged. Measured (laptop, 2026-10-14, cube outward, target 1e-2, bar 1e-4, serial): the first smoothing pass matches "exact" to rounding (same accepted and refused moves; the energy after each turn-1 group equal to 10 digits); front_flux exited resolved at turn 13 in 603 s, "exact" ran its 15 turns unresolved in 1691 s (sharing the machine for its first 8 turns); one evaluation of the band part of a front vertex (value, gradient, Hessian), median / p90 over the front vertices of three band states (turn 1 after the split, a stored band, the last turn): corner_bound 6-7 / 11-17 us, "exact" 55-238 / 424-809 us, front_flux 25-150 / 193-508 us; the value alone, front_flux 25-65 us against "exact" 19-32 us (the edge flux needs a weighted face moment of the line distance, which only the derivative-grade series computes). Most of it is the closed forms of the edge and vertex distances over the face parts near input edges (the near-parallel series); the labelling is 6-7 us. The cube outward has no region overlaps on its offset side: no cell is flagged and no D_t is evaluated. "exact_swept" (2026-10-22): "exact"'s energy (b(t) = J_t / target_distance; the settle test and every whole-mesh sum as under "exact"), but every operation's rule compares the AMIPS terms of the cells it changes plus the exact CHANGE of the band integral E* = int_B (d - delta) dV, which lives only where the band B changes and is integrated there by value() on small tets (TopoOffsetTetMesh, EXACT SWEPT CHANGE). A vertex moving from x0 to x1 (smoothing, the projected step, a collapse moving v1 onto v2): the sum over the faces (x, a, b) of B's boundary through the vertex (front faces, faces on the input, on the domain wall) of sign value(x0, x1, a, b), the face ordered with its normal out of B and the sign that of (x1 - x0) along that normal: a band cell moved differs from itself by one oriented tet per face through the vertex (the boundary of a 4-simplex is a 3-cycle, which integrates to 0 in R^3), and those between two band cells cancel; exact for mixed signs; a vertex on no boundary face of B: 0. A collapse changes no cell's label; it integrates its swept tets after the engine's own tests (before them, where most attempts are refused for inverting a cell, the screw piece 15's turn-1 collapse pass took 35.0 s against 3.3 s, measured on main, 2026-10-22). A swap: interior (one label over the ring) 0; a flip: the cone from a vertex over the boundary of (new band cells) - (old band cells), which for a surface flip (a,b,c) + (a,b,d) -> (a,c,d) + (b,c,d) is +/- value(a, b, c, d), one call. A split, and the split energy gate: 0. Under TetWild's rules (a TetWild pass) nothing changes: no sum rule. The smoother's search model is the function the move is judged on: the ring's AMIPS terms plus the swept change from the solve's start x0 to x (the same faces, signs and value() calls), whose gradient and Hessian are the band integral's at x by Reynolds' transport theorem over B's boundary faces through the vertex ("front_flux"'s face derivatives; defined at x0, where every swept tet is flat); where a curved bisector crosses a piece of such a face (or a face's pieces do not tile it) the derivatives at that x are "exact"'s, from the ring's band cells, and each pass logs how many derivative evaluations took them. Why: the former model, the 7-point rule of G = psi(d) grad d over the front faces, left out the jumps of G.n across the bisectors; with it (uk-sonic, screw piece 15, 8 threads, 5 turns, 2026-10-23) 10.6% of turn 2's front solves were refused after 4 halvings, and the ring max held at one vertex through turns 1-3 (7.03 / 5.96 / 5.71x against "exact"'s 8.73 / 3.20 / 2.66x). After the solve the move stands iff the ring's AMIPS change plus the exact swept change is <= 0, else the step is halved toward the start and tested again, up to 4 times, then refused (each pass logs the solves, the moves accepted at the full step and after 1-4 halvings, and the refusals). Tested (test_exact_swept): random moves of a vertex coned to a box of random band / background / input cells at the cube's corner and across the valley's bisector, the swept change against the change of the band cells' sum of value(); a collapse onto a box corner; 3-2 and 4-4 configurations with random band subsets; a surface 3-2 flip one value() call; value() over the 24 corner orders; the search model's value against the swept change bit for bit, its gradient against central differences of its value and, at the solve's start, against "exact"'s, at the cube's corner and across the valley's bisector; where a curved bisector crosses a front face, its derivatives against "exact"'s. "surface" (2026-10-27, Spencer's HackMD "Global surface integral"): NO band term; E = (w / stop_energy^3) sum_t vol AMIPS^3 + L / front_conv^2 sum over the offset faces f of O(f), L the bounding box diagonal (so both terms are volumes and both integrands are 1 at convergence), O(f) = (A_f / |S|) sum over the face's stencil points x_i (stencil_order, EXPERIMENTAL_stencil_type; the first converged runs: "fem", order 5, its points weighted by their lumped area weights) of (d(x_i) - target_distance)^2 (w = offset_amips_weight: the AMIPS term alone is weighted, so w is the ratio of the two terms; since 2026-10-28, before which the offset term was weighted 1 - w; the first converged "surface" runs on piece08 used w 100). Every rule compares the cells' sum plus the offset faces the operation changes: a collapse may not raise it (the faces through both ends before, the survivor's after), a swap must strictly lower it (a flip of the offset surface: its two faces out, two in), a split is not judged by it (since 2026-10-28 no split has an energy rule: only the length against the sizing field proposes one), a smoothing move may not raise it over the vertex's ring and faces (strictly lower for a held vertex's projected step). The front smoother minimises the AMIPS term over the ring plus the face term of the vertex's offset faces (exact gradient and Hessian). The settle test reads O*, the max over the stencil points of (d - target_distance)^2, with settle_stall_eps (see there). EXPERIMENTAL_label_cleanup's tip / at-target flips are taken iff they strictly lower the face term. "exact" is the default since 2026-10-29, the rule of the latest offset runs; "corner_bound" was before.

EXPERIMENTAL_settle_test · string · default "stall" · one of stall, bar

EXPERIMENTAL (2026-10-27), 3D, EXPERIMENTAL_band_volume_rule "surface" only ("bar" under another rule is refused), with EXPERIMENTAL_settle_refinement: which test decides that a turn has settled. "stall" (the default): the O* stall test, settled when O* < front_conv^2 or O*_prev - O* <= settle_stall_eps (O* - front_conv^2), O* the max over the front's stencil points of (d - target_distance)^2 (see settle_stall_eps). "bar" (Spencer: the volume rules' settle test "but with our surface integral"): settled when O_g = int over the front of (d - target_distance)^2 dS (the face term without its weight, by the faces' stencils) fell over the turn by less than front_conv^2 times the front's current area -- O_g of a front lying front_conv off the level set everywhere, as the volume rules' bar b = front_conv^2 A / (2 target_distance) is their band term's excess for such a front. A rise settles. settle_stall_eps is refused under "bar". The marking, halving and exit of a settled turn are EXPERIMENTAL_settle_mark's, unchanged.

EXPERIMENTAL_settle_mark · string · default "vertex_edge" · one of max, rms, vertex, vertex_edge, vertex_split, stencil

EXPERIMENTAL (2026-10-09), with EXPERIMENTAL_settle_refinement: which test marks a front face on a settled turn (the settle, the halving and the exit are unchanged). "max": the face's largest distance from the level set, max(inside_f, outside_f), exceeds front_conv (exact up to rounding, see EXPERIMENTAL_settle_refinement). "rms": the face's share of the offset term's excess exceeds the bar energy of its area. The share, to first order in d - target_distance (divergence theorem on G = (1/2)(d - delta)^2 grad d; exact for any band shape, folds included, up to the curvature and medial-axis term), is X_f = (1/2 delta) int_f (d - delta)^2 cos(theta) dA, theta the angle between the face's normal (pointing out of the band) and grad d, signed; the bar energy of the face is front_conv^2 area(f) / (2 delta). So f is marked when the mean over f of (d - delta)^2 cos(theta) exceeds front_conv^2. The mean is taken by the 3-edge-midpoint rule (weights 1/3), exact for quadratic integrands, so exact over a face whose points all have one input face's interior as nearest feature (d affine, grad d constant). Near an input edge or vertex (d curved) it is an approximation. Across a ridge of d (the medial axis, where two input faces are equally near: the inward offset of a convex edge, the outward offset of a concave one) it can miss the face's excess entirely: measured on the prism inward (2026-10-09), one face read 0.24 bar by this rule and 7.46 bars over a 91-point grid. A face along grad d (cos theta near 0) or turned back (cos theta < 0) has a share near 0 or negative and is not marked; the max test catches such a face. "vertex": a front vertex v is marked when |d(v) - target_distance| exceeds front_conv (d over all input triangles, exact at v), and its own sizing is halved; it says nothing about the points of a face between its corners. All three tests' numbers are logged on every settled turn, whichever marks (under "rms" and "vertex" the max test's outside_f is the former corner bound, see EXPERIMENTAL_settle_refinement). Tried and removed (2026-10-25, 2026-10-26): "variance" (refine a vertex only where the errors at its faces' corners and centroids vary by more than front_conv while their mean is within it) and "remainder" (halve the corners of a face whose error at its centroid differs from the mean of its corners' errors by more than front_conv). MEASURED: "variance" never refined the single-cell stage of cube_in at 0.45 (3.68 front_conv off for 40 turns); under "remainder", t100033 inward at 0.25 delta_1 (5%, 8 threads, EXPERIMENTAL_label_cleanup "one_sided_tips") did not resolve in 30 turns -- from turn 17 on, 1 or 2 vertices were over front_conv, the worst 1.06 to 1.14 front_conv short of a crease of the target, with its band and AMIPS forces equal and opposite and its sizing scalar unchanged at turns 10, 20 and 30 (the remainder test marked one face's corners elsewhere on 4 of those 14 turns) -- where "vertex" resolved the same configuration at turn 16; with the label clean-up, "vertex" resolved cube_in at 0.45 30 of 30 (5%, 8 threads). "vertex_edge" (2026-10-26, the default): "vertex", and also both ends of a front edge whose two ends are within front_conv of the level set and whose midpoint is not have their sizing halved; these marks refine only and do not hold the exit, which stays the vertex test. One [settle edge] line per settled turn. Why: such an edge spans a stretch of the level set its corners do not see, which the vertex test passes for good. MEASURED (t100033 inward at 0.5 delta_1, 10%): at 1 thread it resolved at turn 8 where "vertex" was held by a real fold to the 30-turn cap (1979 front vertices at turn 8 against 549, the halving spreading through the gradation); at 8 threads, one run each, it resolved at turn 8 in 70 s with 1934 front vertices and 52360 tets, "vertex_split" at turn 19 in 168 s with 1425 front vertices and 56687 tets, both with no real fold. Offline, the edge test flagged 0.02-6% of the front edges of final meshes (t100033 36 of 4020, t126196 18 of 5109, screw 4 of 17136) and every real-fold edge (5 of 5), most of the rest crease or sag edges with the midpoint 1.2-2 front_conv off. "vertex_split" (2026-10-26): "vertex", and also a front face whose three corners are within front_conv of the level set but one of its three edge midpoints or its centroid is not has the edge with the midpoint farthest from the level set split (no sizing change), serially, right before the first smoothing pass of the next turn, in the slot of EXPERIMENTAL_front_pocket_split (which it needs; refused otherwise) and before its pinned cells; an edge that turn's operation passes removed is skipped. The listed edges do not hold the exit, which stays the vertex test: they are split only if the run goes on, and a run that exits leaves them unsplit. MEASURED with a turn that listed edges not exiting (8 threads): at turns 14 to 17 the vertex test passed with no real fold, so the run would have exited at turn 14; the listed edges held it, and by turn 25, when it was stopped, the front had grown from 661 to 1228 vertices. One [settle split] line per settled turn and one where the edges are split. Why that slot: split at the end of the settled turn, the next turn's collapse pass undid the splits before any smoothing had moved the new vertices, restoring the geometry at no energy cost: MEASURED (t100033 inward at 0.5 delta_1, 10%, 1 thread), 12 of turn 2's 64 split vertices and 1 of turn 3's 44 were left after the next collapse pass, and the real folds went 0 at turn 5 to 12 at turn 9. Why: such a face spans a stretch of the level set its corners do not see, which the vertex test passes for good. Against "vertex_edge" (which halves the sizing instead), see there. Not gated by its energy gain: MEASURED offline on that run's final mesh, a split of any of its 40 such edges, the new vertex placed anywhere that keeps the cells valid, lowers the offset energy by at most 0.61 of the settle bar of the edge's two front faces, the real fold's edge by 0.27; in the 5% run that resolved, the fold went at the collapse the turn after the split. The face test against the edge test alone: on the final meshes of t100033 (two runs) and t126196, every such face but one (t126196, 1 of 27) has an edge midpoint over. Read only under EXPERIMENTAL_settle_refinement. "vertex_edge" is the default (2026-10-26; before it, "vertex"). "stencil" (2026-10-28, Spencer: "enforce that every stencil point is within front_conv for convergence"): a front face is marked when a point of its offset stencil (stencil_order, EXPERIMENTAL_stencil_type: the FEM stencil's points include the corners and the edges, the interior stencil's do not) lies at or beyond front_conv of the level set, (d - target_distance)^2 >= front_conv^2, or is unmeasurable -- the per-face max of the same points and values as O*, the "surface" rule's settle metric (settle_stall_eps); its three corners have their sizing halved (down to the sizing floor; a marked face with every corner at the floor is counted and warned about), and a marked face holds the exit. So the loop exits only when every stencil point of every front face is within front_conv (and, under the "surface" rule, that alone also settles the turn). No vertex or edge-midpoint test. One more field in the [settle] line's parenthesis: the stencil test's faces over and its max in bars.

EXPERIMENTAL_gap_refinement · bool · default false

EXPERIMENTAL (2026-10-10), 3D, needs EXPERIMENTAL_settle_refinement. On a settled turn a band cell with a front corner is also refined (its four corners' sizing halved) when md - d(centroid) > front_conv: md is the corner mean of the bounding triangle's distance (the corner bound's own min over input triangles, see EXPERIMENTAL_band_volume_rule), d(centroid) the exact distance at the cell's centroid. Where d is convex on the cell (everywhere outside a convex input) the cell's true mean of d is at least d(centroid) (Jensen), so Vol (md - d(centroid)) / target_distance bounds the corner bound's gap (bound minus true integral) from above; near a concave feature it bounds nothing. The exit also needs no such cell. Why: the gap depends on the corners' positions, and the smoother descends it with the energy. On the cylinder outward (2026-10-09) every fold of the front formed in a smoothing pass by a move that lowered the corner bound while the true integral plus AMIPS rose, and in one turn-11 pass 39 of 150 sampled front moves did that. false (the default) = only the face test refines.

EXPERIMENTAL_damping · bool · default false

EXPERIMENTAL (2026-10-15), 3D. Damped smoothing: every local solve of every smoothing pass of the loop (front vertices and every other vertex; the loop's every stage, init_optimize's loop and pre_smooth included) may move its vertex at most EXPERIMENTAL_damping_factor (default 0.5: half) times the length of the shortest edge at the vertex (from the vertex to any vertex of its one-ring), measured from where the vertex was when the solve started. A solve that goes further is pulled back along its step to that distance before the move's accept checks (inversion, envelope containment, the energy veto), and a held surface vertex's projected candidates beyond it are refused. Never in a TetWild pass -- optimize_construction's, the final pass -- nor in the repulsion passes before the march (since 2026-10-20; before, all of them were damped too). Each damped smoothing pass logs how many accepted moves ended on the bound ([damping]). false (the default) = no bound.

EXPERIMENTAL_damping_factor · float · default 0.5 · min 0

EXPERIMENTAL (2026-10-15), 3D, with EXPERIMENTAL_damping. The damping bound as a factor of the shortest edge at the vertex: each smoothing solve may move its vertex at most this times the length of its shortest edge (to any vertex of its one-ring), at the positions when the solve started. Must be > 0; a value other than the default with EXPERIMENTAL_damping false is refused with an error. 0.5 (the default) = half the shortest edge, the bound EXPERIMENTAL_damping had before this key existed.

EXPERIMENTAL_settle_refinement · bool · default true

EXPERIMENTAL (2026-10-09), 3D. Refinement and exit wait for the front to settle, then judge every front face by its distance from the level set d = target_distance. Each turn, after the energy criterion: dE = E_prev - E, E the offset energy -- the band term of the energy (see offset_amips_weight) summed over the band cells, without the AMIPS term, which changes wherever the mesh is improved whether the front moves or not (measured 2026-10-14, serial, Geneva settings: splits raised the whole energy through its AMIPS term by 0.7-3.6 b per turn on the cube outward) -- at the end of the turn, and E_prev at the end of the previous turn (turn 1: just before its first operation group); the bar energy b = front_conv^2 * A / (2 * target_distance), A the total area of the front faces: the excess of the offset term for a front of area A lying front_conv beyond the level set (per unit area the integral of s / target_distance over s from 0 to front_conv). The turn is settled when dE < b. An unsettled turn refines nothing and does not exit: the front is still moving, and a face judged now is judged where the front passes, not where it comes to rest. On a settled turn a front face f is marked when max(inside_f, outside_f) > front_conv, with inside_f = target_distance - min over f of d (exact: the min over input triangles P of the triangle-to-triangle distance) and outside_f = (max over f of d) - target_distance, exact up to rounding (EXPERIMENTAL_settle_mark "max"): f is cut by the input's nearest-feature regions (face prisms, edge wedges, vertex cones) as the exact band integral cuts a cell (EXPERIMENTAL_band_volume_rule); on a piece where one feature's distance is d, d is convex and its max is at a vertex of the piece; on a piece whose region boundaries are curved ("rule (a)") the min over its features of each one's max is used, an upper bound. The former outside_f, the min over input triangles P of the max of d_P over f's three corners, is one triangle's convex distance and overshoots by about s^2 / (2 target_distance) where f straddles a coplanar input edge by s: measured on the cube outward (settle_mark "max", exact rule), turn 28, all 8 faces it marked read 1.06-1.26 bars by it and -0.016 to +0.020 bars by the exact max; they alone blocked the exit at that turn. Under settle_mark "rms" and "vertex" outside_f is only logged and is still the former bound. Every marked face has the sizing scalar halved at its three corners (down to the sizing floor); a marked face with all three corners at the floor cannot be refined, is counted and warned about, and blocks the exit like every marked face. Exit: settled and no face marked. The run then ends as on the ordinary exit (the TetWild final pass when max AMIPS >= stop_energy), and this exit is the front half of the verdict. The energy criterion's own refinement (its refinable faces or vertices) and its exit test do not act; its turn line is still logged. One [settle] line per turn. true is the default; false = the energy criterion's refinement and exit.

EXPERIMENTAL_coarsen_guard · string · default "bar" · one of bar, no_rise

EXPERIMENTAL (2026-10-28), 3D, read only with coarsen_pass true (refused otherwise). What judges a coarsening collapse. "bar" (the default): the absolute bar described under coarsen_pass -- after the collapse every offset face through the survivor keeps the larger of its two cells' AMIPS / stop_energy and the root of its face measure (the face stencil's offset error in bars) at most 1 -- the front is collapsed only by length-limited passes, and coarsen_mesh() runs at the end of every TetWild pass (the optimize_construction pass and the final pass). "no_rise" (Spencer, 2026-10-28: coarsening in the final TetWild pass, "amips should reject if the max increases, offset error should reject if its sum increases"): coarsen_mesh() runs only for the final target, once the loop converges -- at the end of the final TetWild pass when that runs (max AMIPS >= stop_energy), after TetWild's bare collapse / swap sweeps, and otherwise on its own with the final pass's setup (the "final coarsening": the final envelopes, TetWild's rules, no iterations, no bare sweeps); never in the optimize_construction pass, never after a loop that did not converge. A collapse is kept only if (a tie passes) the max AMIPS over the cells it reshapes (the survivor's ring less v2's untouched cells) does not rise above the max over the collapsed vertex's old ring, and the offset faces it changes pass the offset half: the offset faces through the survivor that are new (after) against the offset faces through the edge's two ends that are gone (before), faces in both cancelling, an unmeasurable new face refusing. Under EXPERIMENTAL_settle_mark "stencil" (Spencer 2026-10-28: "so long as all stencil points are still under the tol, the collapse is fine"): the max over the after faces' stencil points (stencil_order, EXPERIMENTAL_stencil_type) of (d - target_distance)^2 is under front_conv^2, or no larger than the same max over the before faces. Otherwise: the offset face term (the stencil's integral of (d - target_distance)^2 over a face, weighted the bounding-box diagonal / front_conv_frac^2) summed over the after faces does not exceed its sum over the before faces. No AMIPS threshold: a kept collapse may leave a cell over stop_energy. The engine's no-rise test (AMIPS / stop_energy clamped at 1, over the region) then never refuses what this keeps. Front vertices are collapsed too (the bare sweeps still leave them). Logged as one [coarsening] line (accepted, refused per quantity, the max stencil-point error at the pass's start and after) beside the engine's coarsen lines.

EXPERIMENTAL_front_pocket_split · bool · default true

EXPERIMENTAL (2026-10-22), 3D. Keeps one invariant in the loop: no cell is PINNED when a smoothing pass starts. A pinned cell is a cell outside the input complex, band or not, whose four corners are all on the front (VertexExtra::m_is_on_offset) (since 2026-10-22 evening band cells too; before, only the cells outside the band). Right before every smoothing pass of optimize_offset_loop() (each operation group's passes, and pre_smooth's), every pinned cell has ONE of its edges split, the longest that is an edge of no live offset-surface face (edge_is_offset_surface_live() false), so the midpoint is not on the front and every cell around it has a corner that is not; the split is the loop's own (split_edge(): labels, tags, sizing and rests as in the split pass), and when it is refused the next longest such edge is tried. A pinned cell with no such edge (all six on the offset surface) has instead the longest edge of one of its faces that is not an offset face split (that face's edges are all on the offset surface): the split makes an edge from the midpoint to the face's third corner that is off the offset surface, in both of the cell's children, and the next round splits it -- a vertex off the front on that face, a corner of all four of the cell's descendants (simplicial_embedding()'s face split, made of two of the loop's edge splits). MEASURED before (cube_in, target 0.3): every such cell had exactly three faces on the offset surface and stood alone under a front vertex of valence 3. A pinned cell with all four faces on the offset surface (a one-cell component of its label) is not split and is counted. The scan is repeated until it finds no pinned cell with an edge to split, at most 10 rounds (a warning when the cap stops it); one [pocket split] line per application gives the pinned cells found and how many of them have all six edges (all four faces) on the offset surface, the edges split, the rounds, the pinned cells left (all six edges on the offset surface, or an edge off it with every split refused), and how many of the pinned cells found are BAND cells (all four corners on the front, inside the band). Why: only smoothing moves vertices, and no vertex joins or leaves the front during a smoothing pass, so a cell can only be flattened during smoothing, and only from a cell that is pinned when the pass starts. The band term depends only on the band as a region (under the exact rule, the integral of (d - target_distance) / target_distance over the band), so a vertex off the front does not change it (a vertex on the input complex carries no band term): the AMIPS term alone moves such a vertex, and it keeps the vertex's cells shaped. A pinned cell has no such corner: the band term pulls all four onto the level set d = target_distance, so the level set fixes its shape -- a sliver (relative volume of the order of curvature x edge length) -- and where the level set curves the wrong way the energy flattens it to zero volume, a fold of the front. Hence the invariant. A split of an edge off the front makes no pinned cell (every cell it makes has the midpoint, off the front, as a corner) and removes at least one. MEASURED 2026-10-22 on cube_in at target 0.3 (serial, 7 turns): the outside-the-band version converged in 5 turns with no fold against 7 unconverged turns and 104 folded faces. MEASURED 2026-10-23 on a 20-case suite (EXPERIMENTAL_band_volume_rule "exact", 4 threads, max_rounds 15; this key before the six-edge split was added), a fold being an edge of two front faces whose normals out of the band are within 30 degrees of opposite, and a fold on a pinned cell one whose two faces are faces of one flattened cell: off, 18 of 20 cases resolved; on, 20 of 20, and no fold on a pinned cell at the last turn. cube_in at target 0.3: not resolved in 15 turns, 174 folds (152 on a pinned cell) -> resolved at turn 3, no fold. Screw piece 15 at target 0.12: not resolved in 15 turns, 291 folds (270 on a pinned cell) -> resolved at turn 4, no fold. The six-edge split, MEASURED on cube_in at target 0.3 (serial, 7 turns): 18 pinned cells with all six edges on the offset surface found over the passes, all split within 2 rounds, none left; resolved at turn 5. The group is then run as its operation passes and one call per smoothing pass (as repulsion_rounds runs its groups), because the engine gives no point between a group's operation pass and its first smoothing pass, nor between two of its smoothing passes; besides the splits, the rounding sweep then runs after every smoothing pass instead of after the group's last, and each pass logs its own energy and time lines. Not in a TetWild pass, not before the march. false: nothing (the default until 2026-10-29).

EXPERIMENTAL_front_spanned_split · bool · default true

EXPERIMENTAL (2026-10-24), 3D, needs EXPERIMENTAL_front_pocket_split. Extends its invariant from cells to every simplex: no SPANNED simplex when a smoothing pass starts. A spanned simplex is an edge, face or cell outside the input complex that is on no offset-surface face (an edge on none, a face not one) while all its corners are on the front; a pinned cell is the spanned cell. Every cell around a spanned edge or face has one label, so the simplex lies inside that label's region, and where the level set is flat the front is a plane through its corners: a cell on it between it and the front has height at most twice the front's distance to the level set, so the band term flattens it as the front converges, though every such cell may have a corner off the front. In each round of front_pocket_split(), before the pinned cells: every spanned edge is split (its midpoint is off the front, a corner of every cell made); then every spanned face whose three edges are on the offset surface has its longest edge split, and the next round splits the edge from that midpoint to the face's third corner (the pinned cell's six-edge split). Lowest dimension first, so a face or cell is split only when no spanned simplex of lower dimension lies on it. Counted in the [pocket split] line as spanned edges / faces found and split. MEASURED before this (cube_in at target 0.45, 1% tolerance, the pocket split on, 1 thread, 40 turns): spanned edges and faces stay through the run (e.g. turn 16 after smoothing: 12 edges, 40 faces, 0 cells); cells on them have median h/l 0.02 to 0.05 against 0.28 to 0.39 for the other cells at the front (turns 9 to 37). false: nothing (the default until 2026-10-29).

EXPERIMENTAL_exit_no_folds · bool · default true

EXPERIMENTAL (2026-10-26; foldover faces only since 2026-10-27), 3D. The loop does not exit on a turn whose front has a real fold with a foldover face. A real fold: an offset-surface edge with exactly two offset faces whose normals out of the band are within 30 degrees of opposite (count_real_folds(), the fold work's definition of 2026-10-24: the edge of a zero-volume sheet). A foldover face: one whose normal, out of the band, is more than foldover_angle from the field's normal, grad d at its centroid (front_face_field_angle(), the debug frames' field_normal_angle); a face that cannot be measured counts as one. The exit test is otherwise unchanged; a held turn continues the loop. One [exit folds] line per turn gives the count of real folds, and one [exit folds foldover] line per turn with a fold the folds with a foldover face and the largest angle of their faces, with the key on or off. Why: the vertex exit tests only the front's vertices, and a flat sheet of inner cells lying within front_conv of the level set passes it. Measured (cube_in at 0.45, front_conv 5%, 30 runs, 8 threads): one run exited with an inner region of 0.5% of the target's volume, every front point within 0.76 front_conv, 5 real folds; 5-7 of each batch's 28-30 exits had 1-5 real folds. Why only the folds with a foldover face: where the level set itself has a ridge sharper than 30 degrees, the correct front is a fold whose faces follow the field, and holding the exit for every real fold (the key until 2026-10-27) held such runs to the turn cap. MEASURED offline (every turn of the 10-26 batch held for the folds: 117 turns of 31 runs, d exact): in the 4 runs held to the 30-turn cap (p70565 piece17 and input1, inward at 0.5 and 0.25 delta_1, 10%), 845 of 858 folds had both faces within 10 degrees of the field, on a level set 7.7 to 27 degrees sharp there (piece17's input has convex edges of 26.1 and 27.0 degrees; input1 a part just over 2 delta thick); the other 13, at 87 to 131 degrees, were at 2 early turns. In the 27 runs that resolved, every one of the 74 folds of their 32 held turns had a face 32 to 161 degrees from the field (median 108), though 73 had every corner, edge midpoint and centroid of both faces within front_conv of the level set: a test of the faces' distance instead would have let 31 of those 32 turns exit. The margin is thin on that side: the nearest such fold, 32.1 degrees (screw15 inward at 0.25 delta_1). MEASURED with this rule (those 31 runs, 8 threads): all 31 resolved; the 4 in 38 turns against 120 at the cap, each releasing once, 35 folds whose faces lay within 0.57 front_conv of the level set, within 13.1 degrees of the field, on a level set 7.7 to 31.4 degrees sharp there; the 27 released none and exited with no fold. false (the default until 2026-10-29): the exit test alone; the counts are still logged.

EXPERIMENTAL_label_cleanup · string · default "one_sided_at_target" · one of off, one_sided, one_sided_tips, one_sided_at_target, energy_decrease

EXPERIMENTAL (2026-10-26), 3D, with EXPERIMENTAL_front_pocket_split true (refused otherwise). The one exception to cell labels being fixed after construction. In every operation group of the loop (once per turn with interleaved_smoothing false), after its operation passes (split, collapse, swap) and before the condition-1 splits that precede its first smoothing pass, a cell on the band / non-band interface (a candidate: a cell with a face shared with a cell of the other label, neither of them the input complex) whose own geometry puts it on the other side of the level set d = target_distance takes the other label, where that keeps the topology of both. Why there: a flip with f = 1 or 2 (below) leaves the cell with all four corners on the front, a pinned cell, which those splits handle, and one with f = 3 removes a pinned cell; the smoothing pass then moves the new front in the same turn. The side test: "one_sided" (conservative): non-band -> band iff U(t) < target_distance, U the min over input triangles of the max of their distance over t's corners, an upper bound of d on t; band -> non-band iff d(c) - max_k |v_k - c| > target_distance, c the centroid, a lower bound of d on t (d is 1-Lipschitz). It reads d through the exact band integral, built under EXPERIMENTAL_band_volume_rule "exact" or "front_flux", EXPERIMENTAL_settle_mark "max" or DEBUG_output; otherwise it throws. Eligible cells only: every cell sharing a vertex with t is band or non-band, not plastic (use_rest_pose makes every non-band cell plastic, and with it the clean-up flips nothing), and of the one tag of its label there; no corner of t is on the input complex, on another tracked surface (a tag boundary, the box) or on the mesh boundary. The topology test, f = the faces of t shared with a cell of the other label: f = 0 refused (a new piece, or the front pinched at an edge or vertex); f = 1 refused if the corner opposite that face is in a cell of the other label; f = 2 refused if the edge on neither face is in a cell of the other label; f = 3 allowed (the fourth face becomes front); f = 4 refused (t is a whole piece; the flip deletes it). The other label then meets t in exactly its f faces, a disk. Serial passes over the candidates in tid order, each reading the labels as the flips before it left them, repeated until a pass flips nothing. They end: no vertex moves and a cell's side test reads its own corners alone, so a cell flips at most once (a second flip throws). After the flips, everything derived from the labels is re-derived where it changed: the closed labels and the cached front class of the flipped cells' faces, edges and corners, the corners' front flags and vertex order, the band-region map. One [label cleanup] line: passes; flips each way; what the last pass left (candidates, cells on the other side by the side test, of them skipped for eligibility and refused by topology per f); E before and after; the components (face adjacency) of the band and of the non-band region before and after, where a change throws; the front edges on more than two front faces before and after. MEASURED before the key (cube_in at target 0.45, 5% tolerance, 30-run batches): some runs end with thin non-band cells left beyond the level set, one with 117 cells reaching 5.6 tolerances out, where E is 1.2e-7 above its optimum against a settle bar of 3.5e-5, so nothing removes them. "off" (the default until 2026-10-29): nothing changes. "one_sided_tips" (2026-10-26): "one_sided", and also a cell with three faces on the other label whose four corners are all within front_conv of the level set flips iff that strictly lowers E: non-band -> band iff its band term (EXPERIMENTAL_band_volume_rule) is < 0, band -> non-band iff it is > 0 (the AMIPS term of a cell that is not plastic does not read its label, so a flip changes E by exactly the band term). Why: under "one_sided" 9 of 30 runs exited with a real fold, a blade of flat non-band cells reaching up to 0.07 front_conv into the target; a blade that no collapse, swap or smoothing removes is, when the operation passes end, one such cell (cube_in at 0.45, 5%). "one_sided_at_target" (2026-10-26; the default since 2026-10-29): "one_sided", and also any cell with a front face (1, 2 or 3 faces on the other label) whose four corners are all within front_conv of the level set flips iff that strictly lowers E, by the sign of its band term: the tip rule of "one_sided_tips" without its three-face condition. Why: in t126196 inward at 0.5 delta_1 (10%, 8 threads, "one_sided_tips") the run hit the 30-turn cap held by 3 real folds; from turn 4 on, the band cells carrying the folds' faces after the clean-up had all four corners within front_conv and a positive band term, all with two faces on the other label (one with one, at turn 25), which the tip rule refuses; at turn 30 the one-sided test read 14 to 16 front_conv short and the topology test allowed both cells. Removed 2026-10-26: "energy" (every candidate flipped by the sign of its band term alone). MEASURED (cube_in at 0.45, 5%, 30 runs, 8 threads, damping off): 23 of 30 resolved, every failure the non-band region reduced to one flat cell covering none of the target. "energy_decrease" (2026-10-28, Spencer; EXPERIMENTAL_band_volume_rule "surface" only, refused otherwise): no side test and no corner gate -- every cell with a front face whose flip strictly lowers the "surface" rule's face term over its four faces (a face shared with a cell of its own label becomes a front face, one shared with the other label stops being one; the flip changes E by exactly that, an eligible cell being not plastic) flips, if eligible and allowed by the topology test, the same tests as the other modes. A cell may flip again in a later pass (every flip strictly lowers E, so the passes end; at most 100 passes, with a warning).

Testing options

For the test suites. (1)

TESTING_allow_mult_comp · bool · default false

TESTING (2026-10-16), 3D. true = an input complex of more than one connected piece (connected components under vertex connectivity; the count is logged as 'Input complex: N connected piece(s)') is accepted. false (the default) = such an input is refused with an error when the offset potential is built, after the construction and before the optimization; with optimize_offset false no potential is built and nothing is refused. Accepted, the energy's band term (every EXPERIMENTAL_band_volume_rule) reads the distance to all the input's triangles, every piece at once, as it does for one piece, and a warning is logged.