artpipe: decimation to 6k faces tore 60% of the mesh surface open — relax it, and replace the simplifier #861

Open
opened 2026-07-26 23:31:22 +00:00 by spikerj · 1 comment
Owner

What went wrong

While hunting the hex-tower-defence freeze, asset size was the leading suspect,
so two constraints were tightened hard. #860 later showed the freeze was an
Angular effect re-subscribing to the art loader's httpResources and looping —
it died identically on 4.55 MB / 17,215 triangles as on 109 MB. Size was never
involved.

The constraints stayed, and they cost real quality.

Measured damage from the decimation

Same asset, exported both ways, analysed by welding vertices by position and
counting edge adjacency:

undecimated target_faces: 6000
triangles 137,799 17,215
surface vertices 68,903 14,744
boundary edges 27 of 206,709 — 0.0% 22,268 of 36,947 — 60.3%
non-manifold edges 0 15
degenerate tris 2 0

The source came out essentially watertight. Taking it to 6,000 faces tore 60% of
the surface open — the result reads as a point cloud rather than a model, which
is exactly how it looked in game.

Blender's collapse decimate at ~99% reduction cannot survive these meshes. They
are AI-generated (the enrichment report tags them non-manifold) and were never
clean enough for that ratio.

Relaxed

Pipelinetarget_faces 6,000 → 150,000, reframed as a ceiling. A
typical ~140k export passes through essentially untouched; a pathological ~850k
one still gets capped. It must stay > 0: art_pipe reads it as
int(params.get("target_faces") or 0) and decimates only when positive, and
omitting it is what shipped 109 MB assets with no UVs (#855) — decimation also
gates the exporter's vertex-weld and normal-recalc pass.

Client budgets — per asset 24 MB → 192 MB, triangles 60,000 → 2,000,000,
loadout 96 MB → 384 MB. Still a genuine backstop (a browser that OOMs parsing a
GLB dies with nothing in the console, so an absurd asset should fail one slot
loudly), but generous, and no longer refusing legitimate work.

The refuse-before-download path is kept: when the manifest declares a size there
is no reason to pull bytes that will be discarded.

The actual optimization work, still to do

Conservative export remains desirable — the point is that this decimator is the
wrong instrument. A simplifier that preserves boundaries and UVs would give real
reduction without shredding:

  • meshoptimizer / gltfpack — quadric simplification with boundary and UV
    preservation, plus vertex-cache optimization and optional quantization. Would
    plausibly reach 30-50% of the triangles at far better visual fidelity than
    Blender collapse manages at 12%.
  • Consider it as an export post-step rather than replacing the Blender stage.

Acceptance: an export at a materially reduced triangle count with boundary
edges still under ~1%, verified with the same edge-adjacency check used above.

Notes

  • Do not lower target_faces again to "optimize" before a better simplifier
    lands; it only opens the surface.
  • Existing assets exported at 6,000 faces are already damaged and need
    re-exporting once this deploys.
## What went wrong While hunting the hex-tower-defence freeze, asset size was the leading suspect, so two constraints were tightened hard. #860 later showed the freeze was an Angular effect re-subscribing to the art loader's `httpResource`s and looping — it died identically on 4.55 MB / 17,215 triangles as on 109 MB. **Size was never involved.** The constraints stayed, and they cost real quality. ## Measured damage from the decimation Same asset, exported both ways, analysed by welding vertices by position and counting edge adjacency: | | undecimated | `target_faces: 6000` | |---|---|---| | triangles | 137,799 | 17,215 | | surface vertices | 68,903 | 14,744 | | **boundary edges** | **27 of 206,709 — 0.0%** | **22,268 of 36,947 — 60.3%** | | non-manifold edges | 0 | 15 | | degenerate tris | 2 | 0 | The source came out essentially watertight. Taking it to 6,000 faces tore 60% of the surface open — the result reads as a point cloud rather than a model, which is exactly how it looked in game. Blender's collapse decimate at ~99% reduction cannot survive these meshes. They are AI-generated (the enrichment report tags them `non-manifold`) and were never clean enough for that ratio. ## Relaxed **Pipeline** — `target_faces` 6,000 → **150,000**, reframed as a ceiling. A typical ~140k export passes through essentially untouched; a pathological ~850k one still gets capped. It must stay `> 0`: art_pipe reads it as `int(params.get("target_faces") or 0)` and decimates only when positive, and omitting it is what shipped 109 MB assets with no UVs (#855) — decimation also gates the exporter's vertex-weld and normal-recalc pass. **Client budgets** — per asset 24 MB → 192 MB, triangles 60,000 → 2,000,000, loadout 96 MB → 384 MB. Still a genuine backstop (a browser that OOMs parsing a GLB dies with nothing in the console, so an absurd asset should fail one slot loudly), but generous, and no longer refusing legitimate work. The refuse-before-download path is kept: when the manifest declares a size there is no reason to pull bytes that will be discarded. ## The actual optimization work, still to do Conservative export remains desirable — the point is that this decimator is the wrong instrument. A simplifier that preserves boundaries and UVs would give real reduction without shredding: - **meshoptimizer / gltfpack** — quadric simplification with boundary and UV preservation, plus vertex-cache optimization and optional quantization. Would plausibly reach 30-50% of the triangles at far better visual fidelity than Blender collapse manages at 12%. - Consider it as an export post-step rather than replacing the Blender stage. **Acceptance:** an export at a materially reduced triangle count with boundary edges still under ~1%, verified with the same edge-adjacency check used above. ## Notes - Do not lower `target_faces` again to "optimize" before a better simplifier lands; it only opens the surface. - Existing assets exported at 6,000 faces are already damaged and need re-exporting once this deploys.
Author
Owner

Audited against origin/master + artpipe origin/mainhalf done. Staying open for the simplifier replacement.

Landed (the "relax it" half) — spikersoft-backend PR #490:

  • SpikerSoft.EventHandlers.ArtPipeProcessor/appsettings.json:166"target_faces": "150000", i.e. a ceiling rather than a quality target.
  • Client-side budgets that make the relaxation safe: art-asset-game-loader.service.ts:55 (192 MB) and :57 (2,000,000 tris); hex-tower-defence.component.ts:32 (384 MB loadout). Refuse-before-download preserved.

Not landed (the "replace the simplifier" half). git grep -rn -iE 'gltfpack|meshopt|pyfqmr|fast_simplification|quadric' returns nothing in SpikerSoft.EventHandlers.ArtPipeProcessor or SpikerSoft.Business/Domain/ArtStudio, and nothing in artpipe origin/main except an unrelated hit in rignet_infer.py:353. The only trace is a comment deferring it at appsettings.json:163-164.

Consequently the ticket's stated acceptance criterion is unmet: there is no export at a materially reduced triangle count with boundary edges under ~1%, and no edge-adjacency/boundary-edge verification exists anywhere in either repo to measure it with. Raising target_faces avoids the tearing by not decimating much — it doesn't give us a simplifier that can decimate without tearing.

⚠️ Deploy caveat: per #856 comment 13152, the last prodstages rebuild was 2026-07-26 and carried #486's target_faces: 6000. PR #490's relaxation to 150000 merged after it. If no rebuild has run since, prod is still decimating at 6,000 — the exact setting this ticket documents as tearing 60% of the surface open. Worth checking the deployed image digest before assuming the relaxation is live.

Audited against `origin/master` + artpipe `origin/main` — **half done. Staying open for the simplifier replacement.** **Landed (the "relax it" half)** — spikersoft-backend PR #490: - `SpikerSoft.EventHandlers.ArtPipeProcessor/appsettings.json:166` — `"target_faces": "150000"`, i.e. a ceiling rather than a quality target. - Client-side budgets that make the relaxation safe: `art-asset-game-loader.service.ts:55` (192 MB) and `:57` (2,000,000 tris); `hex-tower-defence.component.ts:32` (384 MB loadout). Refuse-before-download preserved. **Not landed (the "replace the simplifier" half).** `git grep -rn -iE 'gltfpack|meshopt|pyfqmr|fast_simplification|quadric'` returns **nothing** in `SpikerSoft.EventHandlers.ArtPipeProcessor` or `SpikerSoft.Business/Domain/ArtStudio`, and nothing in artpipe `origin/main` except an unrelated hit in `rignet_infer.py:353`. The only trace is a *comment* deferring it at `appsettings.json:163-164`. Consequently the ticket's stated acceptance criterion is unmet: there is no export at a materially reduced triangle count with boundary edges under ~1%, and **no edge-adjacency/boundary-edge verification exists anywhere in either repo** to measure it with. Raising `target_faces` avoids the tearing by not decimating much — it doesn't give us a simplifier that can decimate without tearing. ⚠️ **Deploy caveat:** per #856 comment 13152, the last `prodstages` rebuild was 2026-07-26 and carried #486's `target_faces: 6000`. PR #490's relaxation to 150000 merged after it. If no rebuild has run since, **prod is still decimating at 6,000** — the exact setting this ticket documents as tearing 60% of the surface open. Worth checking the deployed image digest before assuming the relaxation is live.
Sign in to join this conversation.