{
  "type": "object",
  "description": "Builds and edits multi-region meshes. With surfaces (3D) or curve networks (2D) as input, it meshes them all into one tet or triangle mesh with a region per input, overlaps included, and optimizes it like tetwild and triwild. With a tagged .msh as input, operation selects an edit: remeshing improves the mesh; fill_holes_topo, tight_seal_topo, keep_lcc, resolve_overlaps, replace_tags and tag_priority edit its regions.",
  "properties": {
    "application": {
      "type": "string",
      "description": "Application name must be simwild.",
      "enum": [
        "simwild"
      ]
    },
    "input": {
      "type": "array",
      "description": "List of input files. A .msh is an already-tagged mesh and is used as-is. Anything else is inserted by VolumeRemesher: a triangular surface mesh in 3D, or an OBJ curve network ('v'/'l' polylines) in 2D. See /dimension.",
      "minimum": 1,
      "items": {
        "type": "string",
        "description": "One input file."
      }
    },
    "dimension": {
      "type": "string",
      "description": "Whether the input is 2D or 3D, as a string. 'auto' looks at the first input: a .msh decides from its own content, an OBJ with 'f' records is a 3D surface, an OBJ with 'l' records and no 'f' is a 2D curve network. A curve network and a surface are both .obj, so the extension alone cannot separate them. Setting \"2\" or \"3\" forces the route and errors if the file disagrees, rather than failing later with a confusing parse error.",
      "default": "auto",
      "enum": [
        "auto",
        "2",
        "3"
      ]
    },
    "input_names": {
      "type": "array",
      "description": "List of names for the input meshes. If no names are assigned, the meshes will be named tag_0, tag_1, etc.",
      "default": [],
      "items": {
        "type": "string",
        "description": "Name for one input mesh."
      }
    },
    "input_type": {
      "type": "array",
      "description": "One entry per input, saying what its tag marks. 'volume': the input encloses a volume, and the tets inside it get its name as a tag. 'surface': the input stays a surface, and the embedded faces on it get its name as a face tag. Empty (the default): every input is a volume. 3D surface inputs only.",
      "default": [],
      "items": {
        "type": "string",
        "description": "What that input's tag marks: the tets it encloses, or the faces on it.",
        "enum": [
          "volume",
          "surface"
        ]
      }
    },
    "operation": {
      "type": "string",
      "description": "What to do. 'remeshing' meshes the inputs, or improves the quality of a tagged .msh; the others edit the regions of a tagged .msh, each with its own options below. Depending on the operation, more parameters might be required.",
      "default": "remeshing",
      "enum": [
        "remeshing",
        "fill_holes_topo",
        "tight_seal_topo",
        "keep_lcc",
        "resolve_overlaps",
        "replace_tags",
        "tag_priority"
      ]
    },
    "output": {
      "type": "string",
      "description": "Output file name (without extension).",
      "default": "out"
    },
    "input_transform": {
      "type": "array",
      "description": "Transformation matrices for the inputs. Transformations are optional and don't need to be described for all inputs.",
      "default": [],
      "items": {
        "type": "array",
        "description": "Homogeneous transformation matrix for one input: 4x4 for a 3D input, 3x3 for a 2D one. An empty list means identity.",
        "maximum": 4,
        "items": {
          "type": "array",
          "description": "One row of the matrix.",
          "minimum": 3,
          "maximum": 4,
          "items": {
            "type": "number",
            "description": "One entry of the row."
          }
        }
      }
    },
    "input_dir": {
      "type": "string",
      "description": "Directory where the input files are located. This is injected by the application and should not be set by the user.",
      "default": ""
    },
    "skip_simplify": {
      "type": "boolean",
      "description": "If true, input simplification will be skipped. Only surface input may be simplified. If the input is already a tet mesh (.msh), it will never be simplified.",
      "default": false
    },
    "tag_from_winding_number": {
      "type": "boolean",
      "description": "How the cells of the arrangement are tagged. True evaluates the winding number of every input at each cell's centroid. False propagates the tags across the arrangement's own surface provenance instead: combinatorial and exact, starting from a cell of the padded box (which is outside everything) and flipping membership of an input whenever a face belonging to that input is crossed. The provenance route needs every input surface to be closed, and says which crossings disagree rather than returning a tagging that depends on the traversal order. 3D surface input only -- a .msh input arrives already tagged.",
      "default": true
    },
    "split_connected_components": {
      "type": "boolean",
      "description": "Treat each connected component of an input file as an input in its own right, rather than the file as a whole. What counts as an input is what gets its own tag column and its own winding-number evaluation, so a file holding several disjoint closed surfaces is tagged as one region with this off, and as one region per component with it on. Components are vertex-connected. Note that the inputs then outnumber the files, so /input_names -- which is applied by position -- no longer lines up with the filenames, and components past the end of that list fall back to the generic tag_N names. 3D surface input only: a .msh input arrives already tagged, and the 2D curve path has its own reader.",
      "default": false
    },
    "use_sample_envelope": {
      "type": "boolean",
      "description": "Use the sampled envelope instead of the exact one. Governs both the 3D surface envelope and, since the exact envelope gained support for edges, the order-2 (open boundary and non-manifold) curve envelope and the 2D polyline envelope -- all three are containment tests against the same input at the same epsilon, so they follow one flag. The sampled test places points along the query and asks whether each is within eps of the input, so it cannot see what happens between two samples and shrinks its acceptance radius to compensate; the exact one decides coverage by the union of the eps-neighbourhoods without sampling, so it uses the full eps and is strictly sharper.",
      "default": false
    },
    "simplify_use_sample_envelope": {
      "type": "boolean",
      "description": "Use the sampled envelope instead of the exact one for the input simplification only -- in 3D, of the input surface before insertion without preserve_topology, of the tet mesh after insertion with it (operation remeshing); in 2D, of the input curves before insertion without preserve_topology; in every case unless skip_simplify; the optimization keeps whatever use_sample_envelope selects. Off by default, like use_sample_envelope; the opt-in buys speed, since the exact envelope dominates the cost of simplifying a large input. Note the sampled test cannot see what happens between two samples, and on thin lattice geometry the sampled simplification has been observed to over-collapse badly (in tetwild).",
      "default": false
    },
    "num_threads": {
      "type": "integer",
      "description": "Number of threads. 0 runs sequentially and gives the same result on every run; with more threads the order of the operations depends on the schedule, and the result can change slightly from run to run.",
      "default": 0
    },
    "max_iterations": {
      "type": "integer",
      "description": "Maximum iterations before stopping.",
      "default": 80
    },
    "eps_rel": {
      "type": "number",
      "description": "Envelope thickness relative to the bounding box diagonal.",
      "default": 0.002
    },
    "eps": {
      "type": "number",
      "description": "Absolute envelope thickness. If this value is negative, the relative envelope thickness is used to compute the absolute one.",
      "default": -1
    },
    "eps_simplify_rel": {
      "type": "number",
      "description": "Envelope thickness relative to the bounding box for the initial simplification. Will be ignored if skip_simplify is true or the input is already a tet mesh.",
      "default": 0.0002
    },
    "optimize_envelope_around_simplified": {
      "type": "boolean",
      "description": "Build the optimizer's envelope around the SIMPLIFIED geometry at the remaining tolerance (eps - simplify_eps) instead of around the original input at the full eps. The deviation budget is identical by the triangle inequality, but the geometry starts at the CENTRE of the envelope it is judged against rather than somewhere inside it. The envelope is a hard veto rather than a penalty, so a mesh handed over close to the boundary has most of its moves refused. Costs elements: holding the surface within eps/2 of the simplified geometry is stricter than eps of the input wherever simplification smoothed detail away, measured at 2-3x the output tets on tetwild at unchanged final quality. Off by default.",
      "default": false
    },
    "eps_simplify": {
      "type": "number",
      "description": "Absolute envelope thickness for the initial simplification. If this value is negative, the relative envelope thickness is used to compute the absolute one. Will be ignored if skip_simplify is true or the input is already a tet mesh.",
      "default": -1
    },
    "simplify_boundary_envelope": {
      "type": "boolean",
      "description": "Let the input simplification move the open boundary of the surface, as long as every boundary edge stays within order2_envelope_ratio * eps_simplify of the input's boundary edges (the same scaling tetwild uses). When false, every boundary vertex is frozen, which also freezes whatever lies between boundary loops packed too tightly to leave a free vertex. Boundary that the simplification opens away from the input's is not judged by the tube. Only affects the simplification before insertion, which runs only when preserve_topology is false and skip_simplify is false; under preserve_topology the surface is simplified after insertion against the order-2 envelope instead.",
      "default": true
    },
    "remove_duplicate_eps": {
      "type": "number",
      "description": "Remove duplicate vertices in the input meshes that are closer than this fraction of the bounding box diagonal. Applied to each input file separately, right after it is read and before it is split into connected components -- unmerged coincident vertices would otherwise leave one surface looking like several. 0 merges only exactly coincident vertices; negative skips the pass entirely.",
      "default": 0
    },
    "order2_envelope_ratio": {
      "type": "number",
      "description": "Envelope thickness for the order-2 envelope -- the open boundaries of the surface and non-manifold edges -- as a fraction of the one used for the surface. Kept below 1 deliberately, unlike the surface envelope which uses the full eps. The two are not symmetric: on the surface a wider envelope REMOVES a constraint that was blocking collapses, whereas a boundary curve has no such blockage to relieve, so the extra room is only freedom for the boundary to wander within eps -- more geometry to resolve at the same final quality. Measured in tetwild on Thingi10K 36075 at stop_energy 5: 14 iterations and 31125 tets at 0.5, against 24 iterations and 41463 tets at 1.0, both converging to a max energy of about 4.95. 1.0 gives the boundary curves the full eps.",
      "default": 0.5
    },
    "length": {
      "type": "number",
      "description": "Absolute target edge length. If this value is negative, the relative length is used to compute the absolute one.",
      "default": -1
    },
    "length_rel": {
      "type": "number",
      "description": "Target edge length relative to the bounding box diagonal.",
      "default": 0.05
    },
    "sizing_field": {
      "type": "array",
      "description": "Prescribe a length for a specific region defined by tags. The length can be absolute (length) or relative (length_rel). The smallest prescribed length will be used for each vertex. Example: [{\"tags\": \"tag_0 & tag_1\", \"length\": 0.1}, {\"tags\": \"tag_2\", \"length_rel\": 0.05}]",
      "default": [],
      "items": {
        "type": "object",
        "description": "A region and its target length.",
        "properties": {
          "tags": {
            "type": "string",
            "description": "A region of tags."
          },
          "length": {
            "type": "number",
            "description": "Absolute target edge length for the region.",
            "default": -1
          },
          "length_rel": {
            "type": "number",
            "description": "Target edge length relative to the bounding box for the region.",
            "default": 0.05
          }
        },
        "required": [
          "tags"
        ]
      }
    },
    "quality_field": {
      "type": "array",
      "description": "Prescribe a target quality for a specific region defined by tags. The last prescribed quality will be used for each element. Example: [{\"tags\": \"tag_0 & tag_1\", \"quality\": 100}, {\"tags\": \"tag_2\", \"quality\": 50}]",
      "default": [],
      "items": {
        "type": "object",
        "description": "A region and its target quality.",
        "properties": {
          "tags": {
            "type": "string",
            "description": "A region of tags."
          },
          "quality": {
            "type": "number",
            "description": "Target quality for the region.",
            "minimum": 0
          }
        },
        "required": [
          "tags",
          "quality"
        ]
      }
    },
    "stop_energy": {
      "type": "number",
      "description": "Target energy. If every cell (tet in 3D, triangle in 2D) has an energy below this, the optimization stops.",
      "default": 10
    },
    "stop_at_float": {
      "type": "boolean",
      "description": "Stop when all vertices are in floating point precision, i.e., no more rational numbers are needed. stop_energy will be ignored when this is true.",
      "default": false
    },
    "preserve_topology": {
      "type": "boolean",
      "description": "Preserve topology of input.",
      "default": true
    },
    "project_line_search_steps": {
      "type": "integer",
      "description": "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.",
      "default": 12
    },
    "project_line_search_nested_steps": {
      "type": "integer",
      "description": "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.",
      "default": 0
    },
    "smoothing_mode": {
      "type": "string",
      "description": "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 3D edges and curve segments. Rests a w_amips-proportional distance off the input.",
      "default": "projected",
      "enum": [
        "projected",
        "exact"
      ]
    },
    "w_amips": {
      "type": "number",
      "description": "Relative weight of the AMIPS quality term against the envelope term during smoothing; the envelope weight is 1 - w_amips. The small default makes smoothing primarily about staying on the surface, with quality as a secondary preference.",
      "default": 0.0001,
      "minimum": 0,
      "maximum": 1
    },
    "num_smoothing_passes": {
      "type": "integer",
      "description": "Smoothing passes per optimization iteration when interleaved_smoothing is disabled. Matches the Wild driver.",
      "default": 2,
      "minimum": 0
    },
    "interleaved_smoothing": {
      "type": "boolean",
      "description": "Run smoothing after each topology phase, matching TetWild and TriWild, instead of batching it after all topology operations.",
      "default": true
    },
    "interleaved_smoothing_passes": {
      "type": "integer",
      "description": "Smoothing passes after each topology phase when interleaved_smoothing is enabled.",
      "default": 1,
      "minimum": 0
    },
    "check_surface_folds": {
      "type": "boolean",
      "description": "Refuse a collapse, swap or smoothing that folds the tracked surface over: one that leaves more tracked faces facing against, or flat to, the surface it replaces (the tracked faces around it, their area vectors summed) or the surface it leaves than there were, or more such faces in the stars of the surface vertices it touches. Exact (orient3d). A fold the input has can be undone, but none is made. Splits are not checked: they move no vertex. Turns colored_smoothing off.",
      "default": false
    },
    "colored_smoothing": {
      "type": "boolean",
      "description": "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": true
    },
    "write_vtu": {
      "type": "boolean",
      "description": "Write not just MSH but also VTU output.",
      "default": false
    },
    "log_file": {
      "type": "string",
      "description": "Logs are not just printed on the terminal but also saved in this file.",
      "default": ""
    },
    "report": {
      "type": "string",
      "description": "A JSON file that stores information about the result and the method execution, e.g., runtime.",
      "default": ""
    },
    "history_output": {
      "type": "string",
      "description": "3D only: a 2D run refuses it. Record every change to the tracked surface -- the interfaces between unlike tags -- from the mesh the input initialises to the output, and save the history here (see wmtk/history): the tracking component rebuilds the map between the two from it. The mesh the history starts from is written next to the output as <output>_history_input.msh; on the memory route it is the output mesh history_input. On the memory route no file is written: any non-empty value records the history, and the bytes of the file it would be are returned in the report as history (bytes in Python; history::write_history or wm.write_history saves them). Empty: no recording.",
      "default": ""
    },
    "volume_history_output": {
      "type": "string",
      "description": "3D only, with history_output. Also record the tets around every operation that changes the tracked surface, and save them here (see wmtk/history/VolumeHistory.hpp): with the surface history, the tracking component rebuilds a map of the volume, region by region (a region: the tets with the same tags), from the mesh the surface history starts from (<output>_history_input.msh; on the memory route the output mesh history_input) to the output. On the memory route no file is written: any non-empty value records the volume history, and the bytes of the file it would be are returned in the report as volume_history (bytes in Python; history::write_history or wm.write_history saves them). It takes many times the room of the surface history. Empty: no recording.",
      "default": ""
    },
    "DEBUG_output": {
      "type": "boolean",
      "description": "Write the mesh as debug_{}.vtu after every operation.",
      "default": false
    },
    "DEBUG_sanity_checks": {
      "type": "boolean",
      "description": "Perform sanity checks after every operation. This can be very slow and should only be used for debugging.",
      "default": false
    },
    "allow_surface_swap": {
      "type": "boolean",
      "description": "Allow surface edge swapping.",
      "default": true
    },
    "check_surface_topology": {
      "type": "boolean",
      "description": "Check if the surface topology is preserved after each operation. This can be very slow and should only be used for debugging.",
      "default": false
    },
    "verbose_quality_stats": {
      "type": "boolean",
      "description": "Log the per-tag quality breakdown after every optimization iteration: for each entry of /quality_field, and for the cells no entry matches, the number of cells, their max and average absolute quality, the target applied to them, and the max quality relative to that target. Groups are listed worst-relative-first, so the top line names the region holding the optimization back. Off by default: it adds one log line per quality-field entry per iteration, which is noise unless a particular tagged region is failing to converge. The figures are collected in the sweep that computes the mesh-wide max/avg anyway, so enabling this costs logging, not computation.",
      "default": true
    },
    "stuck_refine_stall_eps": {
      "type": "number",
      "description": "Refine when the last iteration's improvement was at most this fraction of the distance the max energy still has to cover: (prev_max - max) <= eps * (max - stop_energy). Equivalently, refine unless the mesh is on course to reach the target within about 1/eps more iterations. 0 => only when it does not improve at all.",
      "default": 0.1
    },
    "stuck_refine_cooldown": {
      "type": "integer",
      "description": "After a refinement, skip this many improvement iterations before refining again (let the operations act on the new sizing field). 1 by default: refining every iteration can outrun the operations entirely -- on triwild20k 189017 at eps_rel 1e-4 the mesh runs away to 5.8M faces and never converges at 0, and converges at 9.9994 in 35 iterations at 1. Costs ~5% iterations across the challenging models at no wall-time cost.",
      "default": 1
    },
    "stuck_refine_num_worst": {
      "type": "integer",
      "description": "Number of worst tets whose neighborhoods are refined when stuck. 0 => refine all tets with energy above the filter_energy.",
      "default": 0,
      "minimum": 0
    },
    "stuck_refine_rings": {
      "type": "integer",
      "description": "Graph rings around each worst tet included in the stuck refinement. 0 => only the worst tet itself, 1 => its neighbors, 2 => neighbors of neighbors, etc.",
      "default": 0,
      "minimum": 0
    },
    "stuck_refine_factor": {
      "type": "number",
      "description": "Multiplicative reduction of the sizing scalar per stuck refinement (0.5 => halve).",
      "default": 0.5
    },
    "stuck_refine_min_scalar": {
      "type": "number",
      "description": "Lower bound on the sizing scalar during stuck refinement.",
      "default": 0.001
    },
    "stuck_refine_gradation": {
      "type": "number",
      "description": "Gradation cap for the monotone sizing smoothing: adjacent sizings may differ by at most this factor (smoothing only lowers values).",
      "default": 2.0
    },
    "stuck_refine_force_split": {
      "type": "boolean",
      "description": "When the max energy stalls, split each worst tet's longest edge once, bypassing the split length gate, to unstick a sliver without changing the sizing field. Adds at most one split per worst tet per stall.",
      "default": true
    },
    "skip_good_regions": {
      "type": "boolean",
      "description": "Only smooth vertices incident to a tet whose energy is at least skip_good_regions_margin * stop_energy; skip smoothing already-good regions. OFF by default: smoothing is the only phase that improves quality without changing connectivity, and the premise that a vertex surrounded by good elements has nothing to gain is not reliable -- for surface vertices the move is driven by the envelope term rather than by element quality, which is why active_vertices() has to append every surface vertex unconditionally to keep the filter from freezing them outright. With the filter off that special case stops carrying the correctness of the pass. Measured in 2D on 122839 at stop_energy 20, filtering also cost more than it saved: 60 iterations ending at max energy 21.03 with it on, against convergence to 19.9998 in 54 iterations and less wall time (875s vs 947s) with it off. Note the threshold scales with stop_energy, so the filter skips far more at 100 than at 20.",
      "default": false
    },
    "skip_good_regions_margin": {
      "type": "number",
      "description": "A tet is 'active' (worth smoothing around) when its energy is at least this fraction of stop_energy.",
      "default": 0.9
    },
    "fill_holes_tags": {
      "type": "array",
      "description": "For fill_holes_topo: list of tag values used to fill enclosed connected components (processed in order).",
      "default": [],
      "items": {
        "type": "string",
        "description": "An intersection of tag values to fill holes."
      }
    },
    "fill_holes_threshold": {
      "type": "number",
      "description": "For fill_holes_topo: only fill a connected component if its size (area in 2D, volume in 3D, in model units) is less than this threshold. Negative value means no threshold (fill all enclosed components).",
      "default": -1
    },
    "tight_seal_tag_sets": {
      "type": "array",
      "description": "For tight_seal_topo: list of tag sets. Each inner list defines a group of tags whose enclosed holes are filled; each set is exactly two tag names, e.g. [[\"tag_0\", \"tag_1\"]].",
      "default": [],
      "items": {
        "type": "array",
        "description": "One tag set: a list of tag values that are treated as a group for hole filling.",
        "minimum": 2,
        "maximum": 2,
        "items": {
          "type": "string",
          "description": "An intersection of tags."
        }
      }
    },
    "tight_seal_threshold": {
      "type": "number",
      "description": "For tight_seal_topo: only fill a hole cluster if its total size (area in 2D, volume in 3D, in model units) is less than this threshold. Negative value means no threshold.",
      "default": -1
    },
    "keep_lcc_tags": {
      "type": "array",
      "description": "For keep_lcc: list of tag values for which only the largest connected component is kept.",
      "default": [],
      "items": {
        "type": "string",
        "description": "An intersection of tags whose smaller connected components will be removed."
      }
    },
    "keep_lcc_num": {
      "type": "integer",
      "description": "Number of largest connected components that are kept. By default, only the largest one is kept.",
      "default": 1
    },
    "resolve_overlaps_tags": {
      "type": "array",
      "description": "Resolve intersections between tags by filling the areas with intersections with the nearest of the intersecting tags.",
      "default": [],
      "items": {
        "type": "array",
        "description": "Two Boolean expressions that define the two intersecting regions.",
        "minimum": 2,
        "maximum": 2,
        "items": {
          "type": "string",
          "description": "A Boolean expression that defines a region of tags."
        }
      }
    },
    "replace_tags_in": {
      "type": "array",
      "description": "Replace the tags by the output tags.",
      "default": [],
      "items": {
        "type": "string",
        "description": "An intersection of tags that should be replaced."
      }
    },
    "replace_tags_out": {
      "type": "array",
      "description": "The output tags (written as intersection) that should replace the input tags. For each input tag, an output tag must be provided. The order of the output tags should correspond to the order of the input tags.",
      "default": [],
      "items": {
        "type": "string",
        "description": "An intersection of tags that should replace the input tags."
      }
    },
    "tag_priority": {
      "type": "array",
      "description": "List of tags for the tag_priority operation. Tags earlier in the list have higher priority.",
      "default": [],
      "items": {
        "type": "string",
        "description": "A tag value."
      }
    },
    "coarsen_pass": {
      "type": "boolean",
      "description": "Run a final coarsening pass after the optimization has converged. The ordinary collapse judges a candidate on the raw post-collapse geometry -- the worst moment in the operation's life, before smoothing has absorbed any of the damage -- so a collapse that would be fine once its neighbourhood relaxes is never taken, and the converged mesh keeps vertices it does not need. This pass takes the collapse optimistically, re-smooths coarsen_smooth_ring around the merged vertex, and keeps it only if the worst element in the region it touched is no worse than before; every element outside that region is untouched, so 'no worse locally' is exactly 'no worse globally'. Alternates with ordinary smoothing passes, as the main optimization does.",
      "default": false
    },
    "coarsen_local_smoothing_passes": {
      "type": "integer",
      "description": "Smoothing sweeps over the ring, inside each candidate collapse, before judging it. 0 by default, which means the pass judges the collapse on its raw post-collapse geometry and never smooths inside the operation. At the level of a single candidate that matters -- the composite's reject rate rises from 25% to 39% without it -- but it does not cost coarsening, because the pass runs to a fixed point: a candidate the smoothing would have rescued is replaced by another the pass finds instead, and the relaxation the mesh actually needs comes from the global smoothing between rounds. What it costs is time. The smoothed ball averaged 98 vertices on tetwild's octocat, so at the old default of 2 sweeps one candidate ran ~200 nonlinear smoothing solves and the pass ran 1.4 million of them to accept 5173 collapses. Over seven tetwild models, dropping this to 0 (with coarsen_max_inner_passes 1) made the pass 9.7x to 31.9x faster for about 1 percentage point of cell reduction.",
      "default": 0
    },
    "coarsen_smooth_ring": {
      "type": "integer",
      "description": "Radius smoothed inside the collapse, and the radius the accept test measures over. The lock claims one more ring than this. With coarsen_local_smoothing_passes at 0 nothing is smoothed, so this only sets how wide a region the accept test compares; a wider region is a more permissive test (it admits untouched cells to both the before and after maxima, which is still sound) so ring 2 coarsens slightly more, but it also makes the pass lock 3 rings per operation where the ordinary collapse locks 2, which is the worst thing for its parallel scaling. 1 by default: on octocat that is 2.3x faster than ring 2 for 1.5 percentage points of cell reduction, and it puts the lock footprint back in line with every other pass.",
      "default": 1
    },
    "coarsen_global_smoothing_passes": {
      "type": "integer",
      "description": "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.",
      "default": 1
    },
    "coarsen_max_rounds": {
      "type": "integer",
      "description": "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 -- the dirty-epoch retry only re-offers failures next to a SUCCESSFUL collapse, so a whole-mesh smoothing pass is invisible to it, and a rejected composite is rolled back in full so rejections never accumulate progress within a round. Returns decay fast: measured over the 16 challenging triwild models at five rounds, accepted collapses by round were 68.8% / 21.7% / 7.2% / 1.8% / 0.5%. Every round costs a full pre-check sweep over all edges plus a smoothing pass, so the default is two -- they carry 90.5% of the coarsening, and the three that would follow are worth 9.5% for 60% of the pass's budget.",
      "default": 2
    },
    "coarsen_max_inner_passes": {
      "type": "integer",
      "description": "Cap on the collapse pass's own dirty-epoch retry loop within one coarsening round; 0 is uncapped. Distinct from coarsen_max_rounds, which counts collapse+global-smoothing alternations. This counts the passes the retry loop makes inside a single collapse pass, re-offering failures whose neighbourhood a successful collapse disturbed. That filter asks only whether the neighbourhood MOVED, not whether it moved helpfully, so a productive first pass re-offers most of the mesh. 1 by default, i.e. one pass per round and no retry within it: on octocat the first pass found 5110 collapses in 135.8s and the three that followed found 27 in 38.4s. Rounds still repeat the pass, and those do pay off, because the global smoothing between them moves the fixed point in a way this filter cannot see.",
      "default": 1
    },
    "coarsen_unbounded": {
      "type": "boolean",
      "description": "Coarsen as far as the quality guarantee allows, instead of stopping at the target edge length. The pass answers 'how few elements can hold this max energy', and that is a more aggressive question than it sounds -- the answer ignores how big the elements become. Measured on the registered tetwild models it takes them from 348k to 60k cells (-82.7%) at max energy raised on none of them, against -43.2% with this off, because a converged mesh is sized by length_rel and the adaptive sizing field rather than by what the quality target strictly requires. On by default: the element count is the thing worth having. Turned off, the pass leaves alone any edge already at or past the collapse threshold (0.8 * the target length, the same one the ordinary collapse uses), since collapsing it only makes its neighbours longer still. The sizing FIELD is deliberately not applied either way: that is the optimizer's own refinement scratch work, and honouring it here would leave the pass unable to undo refinement that turned out to be unnecessary.",
      "default": true
    },
    "envelope": {
      "type": "string",
      "description": "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.",
      "default": ""
    },
    "$schema": {
      "type": "string"
    }
  },
  "required": [
    "application",
    "input"
  ],
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://wildmeshing.github.io/wildmeshing-toolkit/schemas/simwild.json",
  "title": "simwild",
  "additionalProperties": false
}
