Skip to content

Validation Reference

Marko Koljancic edited this page May 28, 2026 · 5 revisions

Home

Validation Reference

The nine validation checks Solarxy runs against every loaded model, the severity each can raise, what trips it, and how to surface results in the GUI, the analyze TUI, and CI. For the solarxy.toml schema that toggles and tunes these checks, see Configuration. For the CI pipeline recipes that publish findings to GitHub Actions / GitLab / Jenkins / Perforce, see CI/CD Integration.

The nine checks

flowchart TD
    classDef warn fill:#1F2430,stroke:#FFC44C,color:#FFC44C
    classDef err fill:#1F2430,stroke:#FF3333,color:#FF3333
    classDef both fill:#1F2430,stroke:#A06DFF,color:#CCCAC2
    classDef mod fill:#1F2430,stroke:#5C6773,color:#5C6773
    classDef io fill:#33415E,stroke:#FFC44C,color:#FFC44C

    L[Load model<br/>validate_raw_model_with_config]:::io
    L --> A[triangle_budget<br/>Warning or Error]:::both
    A --> B[normal_mismatch<br/>Error]:::err
    B --> C[flipped_normals<br/>Warning]:::warn
    C --> D{file is<br/>obj/gltf/glb?}
    D -->|yes| E[uv_presence<br/>Warning]:::warn
    D -->|no| F[index_buffer<br/>Error]:::err
    E --> F
    F --> G[material_refs<br/>Error]:::err
    G --> H[non_manifold_edges<br/>Error or Warning]:::both
    H --> I{allow_open_mesh?}:::mod
    I -->|true| J[suppress boundary warning]:::mod
    I -->|false| K[keep all warnings]:::both
    J --> M[degenerate_triangles<br/>Warning]:::warn
    K --> M
    M --> R[ValidationReport]:::io
Loading

Dispatch order, severity, and the allow_open_mesh modifier. UV checks only run for formats that carry UVs (OBJ, glTF, GLB).

The [validation] table in solarxy.toml toggles nine checks. The analyzer, the batch validator (--paths), and the GUI's Shift+V overlay all share this set. All are on by default except allow_open_mesh. Each row notes the issue severity it raises - Error or Warning.

Toggle Default What it flags
normal_mismatch on Error - a mesh declares per-vertex normals but the normal count does not match the vertex count.
flipped_normals on Warning - a triangle's averaged vertex normal points away from its geometric (winding-derived) normal, below the thresholds.flipped_normal_dot cutoff. Catches reversed normals left after a mirror modifier.
non_manifold_edges on Error / Warning - edges shared by 3+ triangles (error), and - unless allow_open_mesh is set - boundary/open edges with a single adjacent face (warning).
triangle_budget on Warning / Error - the model's total triangle count exceeds the per-category [budgets] value: a warning over budget, an error past the tolerance band (thresholds.triangle_budget_tolerance_percent).
allow_open_mesh off Modifier (not a check) - when true, suppresses the boundary/open-edge warning from non_manifold_edges so deliberately unsealed assets don't trip CI gates.
degenerate_triangles on Warning - triangles with near-zero area (collinear or coincident vertices).
material_refs on Error - a mesh's material index points to a slot beyond the model's material list.
uv_presence on Warning - a UV-coordinate count that doesn't match the vertex count (any format), or missing UVs entirely. The missing-UV check runs for OBJ / glTF / GLB only - STL and PLY are exempt.
index_buffer on Error - an empty index buffer, or an index count not divisible by three (a non-triangulated mesh).

Per-check causes, fixes, and overlay appearance live in Per-check reference below.

Severity and thresholds

Two numeric knobs live in [thresholds] of solarxy.toml:

Threshold Default What it controls
triangle_budget_tolerance_percent 20.0 The warning-to-error boundary for triangle_budget. Any triangle count over budget warns; a count above budget * (1 + tolerance%) is an error.
flipped_normal_dot -0.5 The dot-product cutoff for flipped_normals. A triangle is flagged when its averaged vertex normal dots below this value against the geometric (winding-derived) normal. The default -0.5 corresponds to roughly 120° between the two normals - a clear reversal, not a noisy small-angle case.

Worked example: the triangle budget grace band

Consider a prop_barrel_01.glb classified by [[filenames.rules]] as prop, with the default budgets.prop = 20000 and the default thresholds.triangle_budget_tolerance_percent = 20.0. The grace band is budget * (1 + tolerance / 100) = 20000 * 1.2 = 24000:

Triangle count Result Why
19,500 Clean Under budget.
20,001 Warning Over 20,000 but within the 24,000 grace band.
24,001 Error Past the grace band.

The grace band is your buffer for iteration. Tighten the tolerance percent toward 10.0 for stricter gates; loosen toward 30.0+ to let artists hit budget with less precision.

Where the results appear

The same ValidationReport data drives three surfaces:

  • The GUI - Shift+V paints colour-coded issue highlights on the mesh, and the Properties panel's Validation section lists each issue (click a row to fly the active camera to it).
  • The Analyze TUI - the Validation tab.
  • CI - batch validation publishes findings to PR annotations, JUnit reports, SARIF, or plain JSON via the --adapter and --adapter-format flags. See CI/CD Integration.

The in-viewport validation overlay

Press Shift+V to toggle the overlay. The overlay paints each affected mesh with a colour that maps to the highest-severity issue category on that mesh. When a mesh has both an error and a warning, the error category wins.

Category Issues that map to it Overlay colour
Error (catch-all) EmptyIndices, NonTriangulated, MissingTexture Red
InvalidMaterial InvalidMaterialRef Orange
NormalMismatch NormalMismatch, FlippedNormals Cyan
MissingUvs MissingUvs, UvMismatch Magenta
DegenerateTriangles DegenerateTriangles Yellow
NonManifoldEdge NonManifoldEdge Orange-red, painted on edges (not faces)

Face highlights render at 40% alpha so the underlying shading stays readable; the NonManifoldEdge edge highlight is 60% alpha because edges are thinner and need extra contrast.

The triangle_budget check has Model scope (not Mesh or Edge), so it does not paint the overlay. The finding still appears in the Properties panel's Validation section and in the Analyze TUI.

The overlay renders inside the MSAA main pass, so it antialiases like the rest of the geometry. It respects mesh visibility - hide a mesh in the Outliner and its overlay vanishes with it.

The Analyze TUI's Validation tab

solarxy-cli --mode analyze -m model.glb opens a four-tab TUI. The fourth tab lists every validation issue with severity tag, scope, and message. Navigate with:

Key Action
Tab / Shift+Tab Cycle tabs
1-4 Jump to a tab directly
j / k, ↓ / ↑ Scroll one line
g / G Top / bottom
PgDn / PgUp Scroll 20 lines
e Export a text report (prompts for a filename)
J Export a JSON report (prompts for a filename)
q / Esc Quit

Each row shows the severity tag, the scope (Mesh N, or Edge in Mesh N), and a descriptive message.

To compose the GUI and TUI views: load the model in solarxy, toggle the in-viewport overlay with Shift+V, and read the Properties panel's Validation section for the same finding list. Clicking a row in the Properties panel flies the active camera to the affected mesh.

The Analyze TUI's Validation tab

Per-check reference

Each subsection has the same five blocks: What it flags / Severity + Toggle / Common causes / How to fix / Overlay appearance. Fix guidance stays generic across DCC tools - the underlying operations (recompute normals, weld vertices, triangulate) exist in every DCC under different menu names.

normal_mismatch

What it flags: A mesh declares per-vertex normals, but the normal-array length does not match the vertex-array length.

Severity: Error. Toggle: validation.normal_mismatch (on by default).

Common causes: An exporter that dropped some normals; a custom geometry assembly that built a partial normal buffer; a file converter that stripped trailing data.

How to fix: Re-export the mesh with normals fully populated, or strip normals entirely so they are face-derived (legal for STL and PLY; OBJ and glTF expect normals when the format permits them).

Overlay appearance: Cyan face tint on the affected mesh, shared with flipped_normals.

flipped_normals

What it flags: A triangle's averaged vertex normal dots below thresholds.flipped_normal_dot (default -0.5, roughly 120°) against its geometric, winding-derived normal.

Severity: Warning. Toggle: validation.flipped_normals (on by default).

Common causes: A mirror modifier left without "flip normals" applied; an export pass that reversed face winding without flipping normals; manual vertex-normal edits that drifted from the face plane.

How to fix: Recompute normals outside (the standard DCC operation), or manually flip the affected face's normal or winding. For mirror-symmetric meshes that legitimately have opposing normals (mirrored hair cards, for example), loosen flipped_normal_dot toward -1.0 to suppress noisy flagging.

Overlay appearance: Cyan face tint on the affected mesh, shared with normal_mismatch.

non_manifold_edges

What it flags: Edges shared by 3 or more triangles, and - unless allow_open_mesh is set - boundary edges with exactly one adjacent face.

Severity: Error (3+-face edges); Warning (boundary edges). Toggle: validation.non_manifold_edges (on by default).

Common causes: Manual modeling that left T-junctions or unwelded vertices; boolean operations that did not clean up shared edges; mesh combines that overlapped at edges instead of stitching them.

How to fix: Weld duplicate vertices. For 3+-face edges, separate the overlapping faces or remove the extras. For boundary edges that are deliberate (foliage cards, cloth, hair planes), set allow_open_mesh = true in solarxy.toml so the warning does not fire.

Overlay appearance: Orange-red edges on the affected mesh - the only check that paints edges instead of faces, drawn at 60% alpha for readability against the shaded surface.

triangle_budget

What it flags: The model's total triangle count exceeds the budget for its category. The grace band before the error threshold is budget * (1 + thresholds.triangle_budget_tolerance_percent / 100). See Severity and thresholds for the worked example.

Severity: Warning (over budget); Error (past the grace band). Toggle: validation.triangle_budget (on by default).

Common causes: A vendor delivered a higher-detail mesh than the budget allows; a subdivision pass was not applied in reverse before export; a hero asset was misclassified into the prop category by [[filenames.rules]].

How to fix: Decimate the mesh (LOD pass, manual retopology, or a remesh modifier), or re-categorize the asset via [[filenames.rules]] if it really belongs in a higher-budget category. The tolerance band is your buffer for iteration - artists see warnings; only the error band blocks CI.

Overlay appearance: None. The check has Model scope, so it does not paint the in-viewport overlay. The finding appears in the Properties panel's Validation section and in the Analyze TUI.

allow_open_mesh

What it does: Modifier, not a check. When true, suppresses the boundary-edge warning from non_manifold_edges.

Severity: N/A. Toggle: validation.allow_open_mesh (off by default - the only check off by default).

When to flip on: Your pipeline ships deliberately unsealed assets - foliage cards, hair planes, cloth ribbons, anything single-sided. Without this, every leaf card fires a boundary-edge warning.

How to apply: Flip on in solarxy.toml when assets are deliberately open-mesh; leave off when assets should all be watertight. Do not blanket-enable just to silence noise on solid assets that happen to have unintended boundary edges - that hides genuine problems on the rest of your library.

Overlay appearance: No overlay. It is a modifier on non_manifold_edges.

degenerate_triangles

What it flags: Triangles with near-zero area - collinear vertices, two coincident vertices, or all three coincident.

Severity: Warning. Toggle: validation.degenerate_triangles (on by default).

Common causes: Modeling that left zero-area faces after a vertex weld; subdivision that created near-degenerate triangles at poles; importer artifacts at format boundaries.

How to fix: Run a "remove degenerate faces" or "delete zero-area faces" command in your DCC. For Solarxy's purposes, a triangle is degenerate when its cross-product magnitude approaches zero.

Overlay appearance: Yellow face tint on the affected mesh.

material_refs

What it flags: A mesh's material index points to a slot beyond the model's material list - a stale index pointing nowhere.

Severity: Error. Toggle: validation.material_refs (on by default).

Common causes: A mesh assignment to a material that was later deleted; an exporter bug that wrote material indices without updating the material list; a manual .mtl edit that removed a material a mesh still references.

How to fix: Re-assign the mesh to a valid material, or restore the missing material. For OBJ files, check that usemtl directives line up with newmtl declarations in the .mtl.

Overlay appearance: Orange face tint on the affected mesh.

uv_presence

What it flags: A UV-coordinate count that does not match the vertex count, or missing UVs entirely. Runs only for OBJ, glTF, and GLB - STL and PLY are exempt because those formats have no UVs by definition.

Severity: Warning. Toggle: validation.uv_presence (on by default).

Common causes: An export pass that dropped UVs; a mesh assembled without unwrapping; a custom geometry-build pipeline that did not propagate UVs.

How to fix: Unwrap the mesh in your DCC and re-export. Even a trivial planar projection is better than no UVs for assets that will receive textures downstream.

Overlay appearance: Magenta face tint on the affected mesh.

index_buffer

What it flags: An empty index buffer (vertices but no triangles), or an index count not divisible by three (a non-triangulated mesh).

Severity: Error. Toggle: validation.index_buffer (on by default).

Common causes: An export pass that wrote the vertex buffer but skipped the index buffer; a mesh containing quads or n-gons in a format that requires triangles; a generated mesh that was not triangulated.

How to fix: Triangulate the mesh in your DCC before exporting. Most tools have a "Triangulate" modifier or export option. The glTF spec specifically requires triangles - enable triangulation at export time when targeting glTF / GLB.

Overlay appearance: Red face tint on the affected mesh.

See also: Configuration · CI/CD Integration · CLI Reference · Tutorial: Build a custom validation policy · Troubleshooting

Clone this wiki locally